Firebase & Stripe Backend

Firebase Cloud Functions v2 in TypeScript: Webhooks Stripe e Trigger Backend per App iOS

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.

Yunus Diallo (DialloDev)

Fondatore di Dywtal Digital a Parma (Italia). Esperto in architetture Cloud Native, microservizi Node.js/TypeScript ed integrazioni Stripe & Firebase.