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

> Discover how GPT-Image2 manages membership billing with Stripe subscriptions and Supabase. Learn about recurring memberships, credit packs, and webhook event processing.

- Repository: [苍何/awesome-gpt-image-2](https://github.com/freestylefly/awesome-gpt-image-2)
- Tags: architecture
- Published: 2026-08-29

---

**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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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:

```javascript
// 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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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:

```javascript
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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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:

```javascript
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:

```javascript
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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/plans.js) to display active products and pricing.
- **Checkout creation** in [`api/billing/checkout.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/alipay/checkout.js) and [`api/_lib/alipay.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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.