Le **Firebase Cloud Functions (v2)** su Google Cloud Run offrono un ambiente serverless scalabile per eseguire logica di business riservata senza esporla al dispositivo mobile. Una delle integrazioni più diffuse nelle app iOS è la gestione dei **Webhooks di Stripe** per processare pagamenti, rinnovi di abbonamenti e cancellazioni in modo idempotente e sicuro.
1. Inizializzazione del Progetto TypeScript
Nella cartella functions/ del progetto Firebase, installiamo le dipendenze ufficiali per Stripe e Firebase Admin SDK:
npm install firebase-admin firebase-functions@latest stripe
npm install --save-dev typescript @types/node
2. Gestione del Webhook HTTP in TypeScript
È fondamentale verificare la firma stripe-signature inviata nell'header HTTP per essere certi che la richiesta provenga da Stripe e non da terzi malintenzionati:
import { onRequest } from "firebase-functions/v2/https";
import { setGlobalOptions } from "firebase-functions/v2";
import * as admin from "firebase-admin";
import Stripe from "stripe";
admin.initializeApp();
setGlobalOptions({ region: "europe-west1", maxInstances: 10 });
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", {
apiVersion: "2023-10-16",
});
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET || "";
export const stripeWebhook = onRequest({ cors: false }, async (req, res) => {
const sig = req.headers["stripe-signature"];
if (!sig) {
res.status(400).send("Firma webhook mancante.");
return;
}
let event: Stripe.Event;
try {
// Utilizziamo il buffer grezzo per la verifica HMAC della firma
event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
} catch (err: any) {
console.error(`Errore verifica webhook Stripe: ${err.message}`);
res.status(400).send(`Webhook Error: ${err.message}`);
return;
}
// Gestione idempotente degli eventi Stripe
switch (event.type) {
case "customer.subscription.created":
case "customer.subscription.updated": {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionChange(subscription);
break;
}
case "customer.subscription.deleted": {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionCanceled(subscription);
break;
}
default:
console.log(`Evento non gestito: ${event.type}`);
}
res.status(200).json({ received: true });
});
async function handleSubscriptionChange(subscription: Stripe.Subscription) {
const customerId = subscription.customer as string;
const status = subscription.status;
const currentPeriodEnd = new Date(subscription.current_period_end * 1000);
// Cerca il documento dell'utente in Firestore tramite il cliente Stripe
const usersQuery = await admin.firestore()
.collection("users")
.where("stripeCustomerId", "==", customerId)
.limit(1)
.get();
if (usersQuery.empty) {
console.error(`Utente non trovato per customer Stripe: ${customerId}`);
return;
}
const userDoc = usersQuery.docs[0];
await userDoc.ref.update({
subscriptionStatus: status,
subscriptionPlan: subscription.items.data[0]?.price.id,
expiresAt: admin.firestore.Timestamp.fromDate(currentPeriodEnd),
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
});
console.log(`Abbonamento aggiornato con successo per UID: ${userDoc.id}`);
}
async function handleSubscriptionCanceled(subscription: Stripe.Subscription) {
const customerId = subscription.customer as string;
const usersQuery = await admin.firestore()
.collection("users")
.where("stripeCustomerId", "==", customerId)
.limit(1)
.get();
if (!usersQuery.empty) {
await usersQuery.docs[0].ref.update({
subscriptionStatus: "canceled",
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
});
}
}
3. Vantaggi delle Cloud Functions v2
- Timeout prolungato: Fino a 60 minuti per job in background.
- Concorrenza elevata: Una singola istanza può gestire fino a 80 richieste contemporanee riducendo i cold start.
- Integrazione Secret Manager: Gestione sicura delle chiavi segrete Stripe senza inserirle nel codice.
Conclusione
Con Cloud Functions v2 ed i webhook di Stripe in TypeScript ottieni un'infrastruttura di pagamento solida ed inaccessibile a manomissioni. Per richiedere l'integrazione di sistemi di pagamento nella tua app, contatta diallooyunus@gmail.com.