Integrate Stripe payments and subscriptions via Firebase Cloud Functions with webhook handling
Full Stripe integration: Android SDK → Firebase Functions → Stripe API → Firestore sync. Secret keys never touch the Android client. All payment operations are server-side.
Before starting, gather project context silently:
PORTFOLIO.md if it exists in the project root or parent directories for product/team contextcat package.json 2>/dev/null || cat build.gradle.kts 2>/dev/null || cat Podfile 2>/dev/null to detect stackgit log --oneline -5 2>/dev/null for recent changesls src/ app/ lib/ functions/ 2>/dev/null to understand project structureAndroid App
└── StripeDataSource (Android SDK + PaymentSheet)
↓ Firebase Callable Function
Cloud Functions
├── createPaymentIntent / createSubscription
├── createSetupIntent (save card)
├── stripeWebhook (HTTP — Stripe → Firebase)
└── createPortalSession (manage subscription)
↓ Stripe API (secret key, server-side only)
Stripe
└── Events → stripeWebhook → Firestore sync
| Need | Primary Function |
|---|---|
| Subscriptions (recurring) | createSubscription |
| One-time payment | createPaymentIntent |
| Save payment method | createSetupIntent |
| Manage subscription | createPortalSession |
| Webhook handling | stripeWebhook |
| Android UI (PaymentSheet) | PaymentSheet SDK |
| Full paywall screen | All of above |
Client (Android) can:
Client CANNOT:
Publishable Key → Android app (safe to expose)
Secret Key → Firebase Secret Manager only (never in client)
Webhook Secret → Firebase Secret Manager only
Price IDs → Firebase Remote Config (safe to expose)
Store price IDs in Firebase Remote Config so they can be updated without app release:
// Android — fetch from Remote Config
val priceIds = mapOf(
"starter_monthly" to remoteConfig.getString("stripe_price_starter_monthly"),
"pro_monthly" to remoteConfig.getString("stripe_price_pro_monthly"),
"pro_annual" to remoteConfig.getString("stripe_price_pro_annual")
)
Always implement idempotent handlers for:
// Critical — must handle
'checkout.session.completed' // Initial subscription
'customer.subscription.updated' // Plan changes, renewals
'customer.subscription.deleted' // Cancellation
'invoice.payment_succeeded' // Successful payment
'invoice.payment_failed' // Failed payment
// Recommended
'customer.subscription.trial_will_end' // 3 days before trial ends
'payment_method.attached' // New payment method saved
interface UserSubscription {
stripeCustomerId: string;
subscriptionId: string;
status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'unpaid';
planId: string;
priceId: string;
currentPeriodStart: Timestamp;
currentPeriodEnd: Timestamp;
cancelAtPeriodEnd: boolean;
trialEnd: Timestamp | null;
createdAt: Timestamp;
updatedAt: Timestamp;
}
Success: 4242 4242 4242 4242
Decline: 4000 0000 0000 0002
Requires auth: 4000 0025 0000 3155
Insufficient: 4000 0000 0000 9995
You MUST generate actual Stripe integration code using Write:
functions/src/stripe/webhook.ts — handles all critical Stripe events:
checkout.session.completed → provision accessinvoice.paid → extend subscriptioninvoice.payment_failed → notify user, grace periodcustomer.subscription.deleted → revoke accesscharge.refunded → handle refund logicfunctions/src/stripe/create-checkout.ts — creates Stripe Checkout sessionsfunctions/src/stripe/customer-portal.ts — creates billing portal sessionssrc/types/subscription.ts — TypeScript types for plans, subscription statesfunctions/src/stripe/sync-to-firestore.ts — syncs Stripe data to Firestorefirestore.rulesdata/repository/SubscriptionRepository.kt — checks subscription statusData/Repositories/SubscriptionRepository.swift — checks subscription statusBefore generating, Grep for existing Stripe references (stripe|Stripe|subscription|checkout) to understand current integration state.