A3 integrates Stripe across 17+ backend endpoint files, a shared utility module, and frontend Checkout via @stripe/stripe-js. This skill covers every Stripe resource, webhook event, Connect platform pattern, the full checkout flow, subscription lifecycle, and error handling used in A3.
Architecture Overview
Backend File Map
File
Stripe Resource
Purpose
functions/src/stripe/accounts.ts
stripe.accounts
Connect account CRUD
functions/src/stripe/account-links.ts
stripe.accountLinks
Connect onboarding links
functions/src/stripe/balances.ts
stripe.balance
Account balance retrieval
関連 Skill
functions/src/stripe/charges.ts
stripe.charges
Charge listing and retrieval
functions/src/stripe/checkout/sessions.ts
stripe.checkout.sessions
Checkout Session creation
functions/src/stripe/coupons.ts
stripe.coupons
Coupon CRUD
functions/src/stripe/customers.ts
stripe.customers
Customer CRUD
functions/src/stripe/events.ts
stripe.webhooks
Webhook event ingestion
functions/src/stripe/invoices.ts
stripe.invoices
Invoice operations
functions/src/stripe/login-links.ts
stripe.accounts
Express dashboard login links
functions/src/stripe/payouts.ts
stripe.payouts
Payout listing
functions/src/stripe/payment-intents.ts
stripe.paymentIntents
PaymentIntent operations
functions/src/stripe/payment-methods.ts
stripe.paymentMethods
PaymentMethod listing/detach
functions/src/stripe/prices.ts
stripe.prices
Price CRUD
functions/src/stripe/products.ts
stripe.products
Product CRUD
functions/src/stripe/promotion-codes.ts
stripe.promotionCodes
Promotion code CRUD
functions/src/stripe/subscriptions.ts
stripe.subscriptions
Subscription lifecycle
functions/src/utils/stripe.ts
Stripe client init
Shared Stripe instance
Frontend Files
File
Purpose
app/services/stripe.js
Ember service wrapping @stripe/stripe-js
app/components/checkout-*.gts
Checkout UI components
app/routes/checkout.ts
Checkout route handler
app/routes/checkout-success.ts
Post-checkout success route
app/routes/checkout-cancel.ts
Checkout cancellation route
Shared Stripe Utility — utils/stripe.ts
The shared utility initializes a single Stripe SDK instance used by every endpoint file.
// functions/src/utils/stripe.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-06-20',
typescript: true,
});
export default stripe;
Key Points
Single instance: Every backend file imports stripe from this utility. Never instantiate Stripe elsewhere.
API version pinning: The apiVersion is pinned to prevent breaking changes. Update this only during a coordinated migration.
TypeScript mode: typescript: true enables full type inference on all Stripe API responses.
Environment variable: STRIPE_SECRET_KEY is set in Cloud Functions environment config, not in .env files committed to source.
Test mode vs live mode: The secret key prefix sk_test_ vs sk_live_ determines the mode. A3 uses separate Firebase projects for staging/production, each with their own keys.
Stripe Connect — Accounts & Onboarding
A3 uses Stripe Connect (Express accounts) to enable platform payouts to service providers.
When a user upgrades or downgrades mid-cycle, A3 uses proration_behavior: 'create_prorations'. This creates proration line items on the next invoice. The options are:
create_prorations — default, adjusts next invoice
none — no adjustment
always_invoice — immediately invoice the proration
This is the most critical file. It receives all Stripe webhook events and dispatches them.
// functions/src/stripe/events.ts
import stripe from '../utils/stripe';
import { Request, Response } from 'express';
export async function handleWebhook(req: Request, res: Response) {
const sig = req.headers['stripe-signature'] as string;
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutCompleted(event.data.object);
break;
case 'customer.subscription.created':
await handleSubscriptionCreated(event.data.object);
break;
case 'customer.subscription.updated':
await handleSubscriptionUpdated(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionDeleted(event.data.object);
break;
case 'invoice.payment_succeeded':
await handleInvoicePaymentSucceeded(event.data.object);
break;
case 'invoice.payment_failed':
await handleInvoicePaymentFailed(event.data.object);
break;
case 'account.updated':
await handleAccountUpdated(event.data.object);
break;
case 'payout.paid':
await handlePayoutPaid(event.data.object);
break;
case 'payout.failed':
await handlePayoutFailed(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.json({ received: true });
}
Webhook Signature Verification
Critical: Always verify the webhook signature using stripe.webhooks.constructEvent. This requires access to the raw request body (req.rawBody). In Cloud Functions, this is available when the function is configured to parse raw body.
Webhook Events Handled in A3
Event
Handler
Firestore Update
checkout.session.completed
Fulfill purchase, activate subscription
users/{uid}.subscription
customer.subscription.created
Record subscription start
subscriptions/{subId}
customer.subscription.updated
Update plan, status changes
subscriptions/{subId}
customer.subscription.deleted
Revoke access
users/{uid}.subscription
invoice.payment_succeeded
Record payment, extend access
invoices/{invId}
invoice.payment_failed
Trigger dunning flow
users/{uid}.paymentStatus
account.updated
Update Connect status
users/{uid}.stripeConnect
payout.paid
Record successful payout
payouts/{payoutId}
payout.failed
Alert user of payout failure
payouts/{payoutId}
Idempotency
Stripe may send the same event multiple times. A3 handles this by:
Storing event.id in Firestore stripe_events/{eventId}.
A3 uses destination charges (the platform creates the charge, Stripe automatically transfers funds minus the application fee). This is the recommended approach for marketplaces where the platform controls the checkout experience.
Environment Variables Required
Variable
Description
STRIPE_SECRET_KEY
sk_test_... or sk_live_...
STRIPE_PUBLISHABLE_KEY
pk_test_... or pk_live_... (frontend)
STRIPE_WEBHOOK_SECRET
whsec_... for webhook signature verification
Common Patterns and Best Practices
Always use metadata: Attach firebaseUid and organizationId to every Stripe object. This enables Firestore lookups from webhook handlers.
Expand sparingly: Use expand only when you need nested objects. Each expansion costs API latency.
Pagination: Always handle has_more in list operations. Use starting_after for cursor-based pagination.
Idempotency keys: For POST operations that create resources, pass idempotencyKey to prevent duplicates on retries.
Amounts are in cents: unit_amount: 2999 means $29.99. Always convert before display.