fix(billing): build su VPS — ExtendedPrismaClient, apiVersion Stripe
Tre fix che emergono solo durante il build di produzione (typecheck stretto, niente tsx watch lazy):
- `packages/db` ora ri-esporta Subscription / BillingWebhookEvent / SubscriptionStatus / BillingInterval / Achievement, così non serve importare da `@prisma/client` direttamente da apps/api (che non ce l'ha tra le deps).
- `apps/api/src/modules/billing/{service,webhook.routes}.ts` usano ExtendedPrismaClient (il client post-`$extends` di prisma-field-encryption) invece di PrismaClient base. Il client esteso ha tipi diversi per via dell'estensione, e la tipizzazione stretta lo richiede.
- `apiVersion` di Stripe portata a `2026-04-22.dahlia` (la versione corrente in stripe@22.x). La precedente `2025-09-30.clover` era già obsoleta nei tipi.
Lint pulito, 99/99 test verdi, build api ok in locale.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
11d73eae69
commit
d98f95b7ca
4 files changed
+14
-9
No files matched your search
@@ -1,10 +1,10 @@
|
|||||||
|
import type { ExtendedPrismaClient, Subscription } from '@ketopath/db';
|
||||||
import {
|
import {
|
||||||
computeTrialEndsAt,
|
computeTrialEndsAt,
|
||||||
isProActive,
|
isProActive,
|
||||||
type SubscriptionSnapshot,
|
type SubscriptionSnapshot,
|
||||||
type SubscriptionStatus as SharedStatus,
|
type SubscriptionStatus as SharedStatus,
|
||||||
} from '@ketopath/shared';
|
} from '@ketopath/shared';
|
||||||
import type { PrismaClient, Subscription } from '@prisma/client';
|
|
||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import type Stripe from 'stripe';
|
import type Stripe from 'stripe';
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export function toSnapshot(sub: Subscription | null): SubscriptionSnapshot | nul
|
|||||||
* dalla data di registrazione. Idempotente: se esiste già, non tocca nulla.
|
* dalla data di registrazione. Idempotente: se esiste già, non tocca nulla.
|
||||||
*/
|
*/
|
||||||
export async function ensureSubscription(
|
export async function ensureSubscription(
|
||||||
prisma: PrismaClient,
|
prisma: ExtendedPrismaClient,
|
||||||
userId: string,
|
userId: string,
|
||||||
signupAt: Date,
|
signupAt: Date,
|
||||||
): Promise<Subscription> {
|
): Promise<Subscription> {
|
||||||
@@ -45,13 +45,13 @@ export async function ensureSubscription(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getSubscription(
|
export async function getSubscription(
|
||||||
prisma: PrismaClient,
|
prisma: ExtendedPrismaClient,
|
||||||
userId: string,
|
userId: string,
|
||||||
): Promise<Subscription | null> {
|
): Promise<Subscription | null> {
|
||||||
return prisma.subscription.findUnique({ where: { userId } });
|
return prisma.subscription.findUnique({ where: { userId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isUserPro(prisma: PrismaClient, userId: string): Promise<boolean> {
|
export async function isUserPro(prisma: ExtendedPrismaClient, userId: string): Promise<boolean> {
|
||||||
const sub = await getSubscription(prisma, userId);
|
const sub = await getSubscription(prisma, userId);
|
||||||
return isProActive(toSnapshot(sub));
|
return isProActive(toSnapshot(sub));
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ function mapInterval(
|
|||||||
* payload N volte senza side effect.
|
* payload N volte senza side effect.
|
||||||
*/
|
*/
|
||||||
export async function applyStripeSubscription(
|
export async function applyStripeSubscription(
|
||||||
prisma: PrismaClient,
|
prisma: ExtendedPrismaClient,
|
||||||
stripeSub: Stripe.Subscription,
|
stripeSub: Stripe.Subscription,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const userId = stripeSub.metadata?.userId;
|
const userId = stripeSub.metadata?.userId;
|
||||||
@@ -153,7 +153,7 @@ export async function applyStripeSubscription(
|
|||||||
* Invocata da uno scheduler giornaliero (vedi notifications/scheduler).
|
* Invocata da uno scheduler giornaliero (vedi notifications/scheduler).
|
||||||
* Restituisce il numero di record aggiornati.
|
* Restituisce il numero di record aggiornati.
|
||||||
*/
|
*/
|
||||||
export async function expireFinishedTrials(prisma: PrismaClient, now: Date = new Date()) {
|
export async function expireFinishedTrials(prisma: ExtendedPrismaClient, now: Date = new Date()) {
|
||||||
const res = await prisma.subscription.updateMany({
|
const res = await prisma.subscription.updateMany({
|
||||||
where: {
|
where: {
|
||||||
status: 'TRIALING',
|
status: 'TRIALING',
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function getStripe(): Stripe {
|
|||||||
_stripe = new Stripe(billingEnv.STRIPE_SECRET_KEY, {
|
_stripe = new Stripe(billingEnv.STRIPE_SECRET_KEY, {
|
||||||
// Pin esplicito: niente sorprese quando Stripe rilascia una nuova
|
// Pin esplicito: niente sorprese quando Stripe rilascia una nuova
|
||||||
// apiVersion. Aggiornare insieme allo `stripe` package.
|
// apiVersion. Aggiornare insieme allo `stripe` package.
|
||||||
apiVersion: '2025-09-30.clover',
|
apiVersion: '2026-04-22.dahlia',
|
||||||
typescript: true,
|
typescript: true,
|
||||||
appInfo: { name: 'KetoPath', version: '0.0.0' },
|
appInfo: { name: 'KetoPath', version: '0.0.0' },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { PrismaClient } from '@prisma/client';
|
import type { ExtendedPrismaClient } from '@ketopath/db';
|
||||||
import type { FastifyPluginAsync } from 'fastify';
|
import type { FastifyPluginAsync } from 'fastify';
|
||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import type Stripe from 'stripe';
|
import type Stripe from 'stripe';
|
||||||
@@ -88,7 +88,7 @@ export const stripeWebhookRoutes: FastifyPluginAsync = async (parent) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function dispatchEvent(
|
async function dispatchEvent(
|
||||||
prisma: PrismaClient,
|
prisma: ExtendedPrismaClient,
|
||||||
stripe: Stripe,
|
stripe: Stripe,
|
||||||
event: Stripe.Event,
|
event: Stripe.Event,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export { prisma } from './client.js';
|
|||||||
export type { ExtendedPrismaClient } from './client.js';
|
export type { ExtendedPrismaClient } from './client.js';
|
||||||
export {
|
export {
|
||||||
ActivityLevel,
|
ActivityLevel,
|
||||||
|
BillingInterval,
|
||||||
CookingTime,
|
CookingTime,
|
||||||
Difficulty,
|
Difficulty,
|
||||||
FastStatus,
|
FastStatus,
|
||||||
@@ -12,9 +13,12 @@ export {
|
|||||||
Phase,
|
Phase,
|
||||||
Prisma,
|
Prisma,
|
||||||
Role,
|
Role,
|
||||||
|
SubscriptionStatus,
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
export type {
|
export type {
|
||||||
Account,
|
Account,
|
||||||
|
Achievement,
|
||||||
|
BillingWebhookEvent,
|
||||||
DeviceToken,
|
DeviceToken,
|
||||||
FastEvent,
|
FastEvent,
|
||||||
Ingredient,
|
Ingredient,
|
||||||
@@ -26,6 +30,7 @@ export type {
|
|||||||
Recipe,
|
Recipe,
|
||||||
RecipeIngredient,
|
RecipeIngredient,
|
||||||
Session,
|
Session,
|
||||||
|
Subscription,
|
||||||
User,
|
User,
|
||||||
Verification,
|
Verification,
|
||||||
WeightEntry,
|
WeightEntry,
|
||||||
|
|||||||
Reference in new issue
Block a user