How GPT-Image2 Manages Membership Billing: Stripe Subscription Architecture Explained

GPT-Image2 uses a Stripe-backed subscription model integrated with Supabase to handle membership billing, supporting both recurring memberships and one-time credit packs through checkout sessions and webhook event processing.

Membership billing in GPT-Image2 is designed around a serverless architecture that connects Stripe's payment APIs with Supabase data persistence. The system retrieves active plans from database tables, creates secure checkout sessions for new subscriptions, and processes Stripe webhooks to maintain accurate membership state and grant monthly credits—all while supporting both Stripe and Alipay as payment providers.

Retrieving the Billing Catalog

The billing flow begins when the frontend requests available plans via GET /api/billing/plans. In api/billing/plans.js, the server validates the Supabase configuration and authenticates the user through getAuthContext before calling getBillingCatalog from the core billing library.

The catalog generation queries two Supabase tables for active products:

// From api/_lib/billing.js
const { data: plans } = await client
  .from('membership_plans')
  .select('*')
  .eq('active', true);

const { data: packs } = await client
  .from('credit_packs')
  .select('*')
  .eq('active', true);

Each row is formatted using formatPlan and formatPack helpers, which construct human-readable labels and calculate monthly credit allowances. The endpoint returns a JSON payload containing the formatted plans and packs arrays, the current user profile, and a checkoutAvailable boolean indicating whether Stripe or Alipay can be used.

Creating Checkout Sessions

When a user selects a product, the frontend posts to POST /api/billing/checkout (handled in api/billing/checkout.js). This endpoint validates the request, looks up the specific product via getBillingProduct, and ensures a Stripe customer exists through getOrCreateStripeCustomer.

The checkout creation process involves several steps:

  1. Product lookup: The system queries either membership_plans or credit_packs based on the productType parameter.
  2. Order creation: A pending record is inserted into the payment_orders table via createPaymentOrder.
  3. Session construction: The checkoutLineItem helper builds the line item configuration, differentiating between subscription mode for memberships and payment mode for credit packs.

The Stripe Checkout session is created with specific metadata linking the transaction back to the internal user and product IDs:

const sessionPayload = {
  mode: productType === 'membership' ? 'subscription' : 'payment',
  customer: customerId,
  line_items: [checkoutLineItem(product)],
  success_url: `${appUrl}/?billing=success&session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${appUrl}/?billing=cancelled`,
  metadata: {
    userId,
    productType,
    productId: product.id,
    orderId: order.id
  }
};

const session = await stripe.checkout.sessions.create(sessionPayload);

After creation, the order status updates to checkout_created via markOrderCheckoutCreated, and the session URL returns to the client for redirection.

Processing Stripe Webhooks

The webhook handler in api/billing/webhook.js processes Stripe events with body parsing disabled (api: { bodyParser: false }) to preserve signature verification. The endpoint uses stripe.webhooks.constructEvent to validate incoming payloads before processing three critical event types:

Checkout Session Completed For memberships, the handler retrieves the full subscription object and calls upsertMembershipFromSubscription to persist the subscription details. It then invokes grantMembershipCredits to award the monthly credit allowance. For credit packs, it triggers completeCreditPackOrder to finalize the one-time purchase.

Invoice Payment Succeeded This handles recurring subscription renewals. When the billing reason is not subscription_create, the system grants credits for the new billing period.

Subscription Updated or Deleted These events trigger membership state updates in the user_memberships table, ensuring the status, current_period_start, and current_period_end fields remain synchronized with Stripe.

Persisting Membership State and Granting Credits

The core billing logic resides in api/_lib/billing.js, which manages the user_memberships table and credit allocation. When upsertMembershipFromSubscription processes a Stripe subscription, it maps the Stripe metadata to database fields:

const membershipData = {
  user_id: userId,
  plan_id: productId,
  stripe_subscription_id: subscription.id,
  status: subscription.status,
  current_period_start: new Date(subscription.current_period_start * 1000).toISOString(),
  current_period_end: new Date(subscription.current_period_end * 1000).toISOString(),
  cancel_at_period_end: subscription.cancel_at_period_end
};

Credit granting follows an idempotent pattern. The grantMembershipCredits function first checks for existing transactions using hasTransactionWithMetadata to prevent duplicate grants, then calls the Supabase RPC grant_user_credits to atomically credit the user's account:

import { grantMembershipCredits } from './api/_lib/billing';

await grantMembershipCredits(supabaseClient, {
  userId: 'user-123',
  planId: 'plan_basic',
  source: 'subscription_renewal',
  metadata: { stripeSubscriptionId: 'sub_xxx' }
});

Summary

  • Catalog retrieval queries membership_plans and credit_packs tables via api/billing/plans.js to display active products and pricing.
  • Checkout creation in api/billing/checkout.js generates Stripe Checkout sessions with embedded metadata for secure transaction tracking.
  • Webhook processing handles checkout.session.completed, invoice.payment_succeeded, and subscription lifecycle events to maintain state synchronization.
  • Membership persistence stores subscription details in user_memberships with idempotent credit granting through grantMembershipCredits and the grant_user_credits RPC.
  • Payment flexibility includes optional Alipay support through separate endpoints in api/billing/alipay/.

Frequently Asked Questions

What payment providers does GPT-Image2 support for membership billing?

GPT-Image2 primarily uses Stripe for processing both subscription memberships and one-time credit pack purchases. The system also includes optional Alipay integration through dedicated endpoints in api/billing/alipay/checkout.js and api/_lib/alipay.js, allowing users to complete credit pack purchases through alternative payment methods while maintaining the same Supabase-backed order tracking.

How does the system prevent duplicate credit grants when processing webhooks?

The grantMembershipCredits function in api/_lib/billing.js implements idempotency checks by querying existing transactions via hasTransactionWithMetadata before calling the grant_user_credits Supabase RPC. This ensures that even if Stripe sends duplicate webhook events for the same invoice or checkout completion, the user receives credits only once, maintaining data integrity and preventing accounting errors.

What happens to a user's membership when their Stripe subscription is updated or canceled?

When Stripe emits customer.subscription.updated or customer.subscription.deleted events, the webhook handler in api/billing/webhook.js triggers upsertMembershipFromSubscription to immediately synchronize the user_memberships table. This updates fields including status, current_period_end, and cancel_at_period_end, ensuring the application accurately reflects whether the user has active access or is scheduled for downgrades at period end.

Can users purchase credits without committing to a recurring membership?

Yes, GPT-Image2 supports one-time credit pack purchases alongside recurring memberships. When the checkout endpoint receives productType: 'credit_pack', it creates a Stripe Checkout session in payment mode rather than subscription mode. The webhook handler then routes these completions through completeCreditPackOrder instead of the membership upsert flow, granting a fixed credit amount without creating a recurring subscription record.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →