From 6a5559fa0ae5d683acca0449e99b6449c6d5cda0 Mon Sep 17 00:00:00 2001 From: luciano Date: Thu, 7 May 2026 14:04:16 +0200 Subject: [PATCH] fix(billing): tratta env vars vuote come unset (soft-degradation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le `STRIPE_*` env vars in ADR 0004 sono opzionali — quando il provider non è ancora configurato l'app deve continuare a funzionare. Lo schema Zod però usava `.string().min(1).optional()` che fallisce con stringhe vuote: `STRIPE_SECRET_KEY=` in un .env diventa `process.env.STRIPE_SECRET_KEY = ''`, presente ma length=0, quindi rifiutato. Aggiunto `emptyAsUndefined` che traduce stringhe vuote → `undefined`. Il check di `isBillingConfigured()` rimane invariato (accetta solo valori non-empty). Sblocca il boot del backend con env Stripe vuote in produzione. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/api/src/modules/billing/env.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/api/src/modules/billing/env.ts b/apps/api/src/modules/billing/env.ts index b458081..27fdd1c 100644 --- a/apps/api/src/modules/billing/env.ts +++ b/apps/api/src/modules/billing/env.ts @@ -1,19 +1,27 @@ import { z } from 'zod'; /** - * ADR 0004 — env vars Stripe. Tutte opzionali: se mancano, i flussi di - * billing rispondono 503 (come abbiamo fatto per VAPID in ADR 0003). - * Questo permette al PO di sviluppare features non-billing senza dover - * configurare Stripe localmente. + * ADR 0004 — env vars Stripe. Tutte opzionali: se mancano (o sono stringhe + * vuote), i flussi di billing rispondono 503 (come per VAPID in ADR 0003). + * Questo permette al PO di sviluppare/deployare features non-billing senza + * configurare Stripe. + * + * `emptyAsUndefined`: una env file scritta come `STRIPE_SECRET_KEY=` viene + * letta da Node come stringa vuota; la trattiamo equivalente a `undefined`. */ +const emptyAsUndefined = z + .string() + .optional() + .transform((value) => (value && value.length > 0 ? value : undefined)); + const billingEnvSchema = z.object({ - STRIPE_SECRET_KEY: z.string().min(1).optional(), - STRIPE_WEBHOOK_SECRET: z.string().min(1).optional(), - STRIPE_PRICE_ID_MONTHLY: z.string().min(1).optional(), - STRIPE_PRICE_ID_YEARLY: z.string().min(1).optional(), + STRIPE_SECRET_KEY: emptyAsUndefined, + STRIPE_WEBHOOK_SECRET: emptyAsUndefined, + STRIPE_PRICE_ID_MONTHLY: emptyAsUndefined, + STRIPE_PRICE_ID_YEARLY: emptyAsUndefined, // URL di base usato per le redirect post-checkout. Deve coincidere col - // dominio del frontend (apps/web). - BILLING_RETURN_URL: z.string().url().optional(), + // dominio del frontend (apps/web). Non valida l'URL se è stringa vuota. + BILLING_RETURN_URL: emptyAsUndefined.pipe(z.string().url().optional()), }); export type BillingEnv = z.infer;