fix(billing): tratta env vars vuote come unset (soft-degradation)

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) <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.7 committed 2026-05-07 14:04:16 +02:00
1 parent e76074869b
commit 6a5559fa0a
1 file changed
+18 -10
+18 -10
View File
@@ -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<typeof billingEnvSchema>;