# How the Billing System Works in Awesome-GPT-Image-2: Stripe and Supabase Integration

> Discover how awesome-gpt-image-2's billing system integrates Stripe and Supabase for seamless subscription and credit pack payments. Learn about the secure checkout and webhook handling.

- Repository: [苍何/awesome-gpt-image-2](https://github.com/freestylefly/awesome-gpt-image-2)
- Tags: how-to-guide
- Published: 2026-09-08

---

**The billing system in awesome-gpt-image-2 leverages Stripe for payment processing and Supabase for data persistence, implementing a dual-product architecture that handles both recurring membership subscriptions and one-time credit pack purchases through a secure checkout flow and idempotent webhook handlers.**

The repository `freestylefly/awesome-gpt-image-2` implements a complete billing subsystem designed for AI image generation services. It combines Stripe's payment infrastructure with Supabase's database to manage two distinct product types: recurring membership plans that grant monthly credits and one-off credit packs for immediate use. Understanding this architecture is essential for developers looking to integrate similar payment flows or customize the existing credit allocation logic.

## Architecture Overview and Data Model

The system maintains all billing state within Supabase while delegating payment processing to Stripe. The core tables include `membership_plans` and `credit_packs` for product definitions, `profiles` for storing Stripe customer IDs, `payment_orders` for transaction tracking, `user_memberships` for subscription status, and `credit_transactions` for audit trails of credit balance changes.

This separation of concerns allows the application to handle complex subscription lifecycles while keeping financial data synchronized across services.

## Fetching the Product Catalog

The billing flow begins at `GET /api/billing/plans`, implemented in [`api/billing/plans.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/plans.js). This endpoint invokes `getBillingCatalog` from [`api/_lib/billing.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/billing.js) (lines 9-30), which queries the `membership_plans` and `credit_packs` tables to retrieve active products.

The helper functions `formatPlan` and `formatPack` normalize database rows into structured responses, including pricing, currency, and metadata. The endpoint also indicates which checkout providers—such as Stripe or Alipay—are available for each product.

```javascript
// Example: retrieve billing catalog
fetch('/api/billing/plans')
  .then(r => r.json())
  .then(data => {
    console.log('Available plans:', data.plans);
    console.log('Available credit packs:', data.packs);
  });

```

## Creating Checkout Sessions

When a user selects a product, the frontend POSTs to [`api/billing/checkout.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/checkout.js). The handler uses `readJsonBody` to parse and validate the request, then `getBillingProduct` retrieves the exact product definition from the catalog.

The system calls `getOrCreateStripeCustomer` to either reuse an existing Stripe customer ID stored in the `profiles` table or create a new one via Stripe's API. Simultaneously, `createPaymentOrder` inserts a pending record into `payment_orders` to track the transaction state.

The `checkoutLineItem` function maps the product's price, currency, and metadata to Stripe's line item format. The handler then creates a Stripe Checkout Session using `stripe.checkout.sessions.create`, updates the order via `markOrderCheckoutCreated`, and returns the session URL to the client.

```javascript
async function startCheckout(productType, productId) {
  const body = { productType, productId };
  const resp = await fetch('/api/billing/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
  const json = await resp.json();
  if (json.ok) {
    // Redirect user to Stripe Checkout
    window.location.href = json.url;
  } else {
    console.error('Checkout error:', json.error);
  }
}

```

## Processing Stripe Webhooks

The [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js) handler receives Stripe events and verifies signatures using `stripe.webhooks.constructEvent`. For `checkout.session.completed` events, the system delegates to `handleCheckoutCompleted`, which branches based on product type:

- **Credit packs**: `completeCreditPackOrder` grants the purchased credits via `grantCredits` and marks the order as completed.
- **Memberships**: `upsertMembershipFromSubscription` creates or updates the `user_memberships` record, while `grantMembershipCredits` allocates the initial monthly credit allowance.

For recurring revenue, `invoice.payment_succeeded` events trigger credit grants for each billing cycle. Subscription lifecycle changes—`customer.subscription.updated` and `customer.subscription.deleted`—synchronize membership status and cancellation dates with the `user_memberships` table.

```javascript
// Inside webhook.js – simplified flow
if (event.type === 'checkout.session.completed') {
  await handleCheckoutCompleted(client, stripe, event.data.object);
}

```

## Credit Allocation and Idempotency

The core credit logic resides in [`api/_lib/billing.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/billing.js). The `grantCredits` function invokes the Supabase RPC `grant_user_credits` to atomically update user balances. For membership benefits, `grantMembershipCredits` implements idempotency protection by calling `hasTransactionWithMetadata` to check `credit_transactions` for existing entries with identical Stripe event IDs.

If no duplicate exists, the system proceeds with the credit grant using the monthly amount defined in the `membership_plans` table. This pattern ensures that network retries or webhook redeliveries never result in duplicate credit allocations.

## Subscription Management Portal

Authenticated users manage their subscriptions through [`api/billing/portal.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/portal.js). This endpoint retrieves the user's `stripeCustomerId` from their profile and calls `stripe.billingPortal.sessions.create` to generate a secure session URL.

Users can upgrade or downgrade plans, update payment methods, or cancel subscriptions directly through Stripe's hosted interface, eliminating the need for custom subscription management UI components.

```javascript
// Open subscription management UI
async function openPortal() {
  const resp = await fetch('/api/billing/portal', { method: 'POST' });
  const { url } = await resp.json();
  if (url) window.location.href = url;
}

```

## Summary

- The billing system in awesome-gpt-image-2 uses Stripe Checkout for payment collection and Supabase for persistent state management.
- Two product types exist: recurring **membership plans** with monthly credits and one-time **credit packs**.
- The checkout flow in [`api/billing/checkout.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/checkout.js) creates `payment_orders` records and Stripe sessions before redirecting users.
- Webhook handlers in [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js) finalize orders, grant credits through `grantCredits`, and synchronize subscription status.
- Credit granting includes idempotency checks via `hasTransactionWithMetadata` to prevent duplicate allocations from webhook retries.
- Users manage subscriptions through Stripe's Billing Portal generated by [`api/billing/portal.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/portal.js).

## Frequently Asked Questions

### How does awesome-gpt-image-2 handle recurring subscription payments?

Recurring payments trigger Stripe `invoice.payment_succeeded` webhooks. The handler calls `grantMembershipCredits` to allocate monthly credits defined in the `membership_plans` table, first verifying against duplicates using `hasTransactionWithMetadata`. This keeps the `user_memberships` and `credit_transactions` tables synchronized with Stripe's billing cycle.

### What happens when a user purchases a credit pack versus a membership?

For credit packs, `handleCheckoutCompleted` invokes `completeCreditPackOrder`, which immediately grants credits via `grantCredits` and marks the `payment_orders` record complete. For memberships, the system calls `upsertMembershipFromSubscription` to create or update the subscription record in `user_memberships`, then grants the initial monthly credits through `grantMembershipCredits`.

### How does the system prevent duplicate credit grants from webhook retries?

The `grantMembershipCredits` function queries `credit_transactions` via `hasTransactionWithMetadata` to detect existing transactions with identical Stripe event metadata. If found, the operation aborts; otherwise, it proceeds with the `grant_user_credits` RPC call. This pattern ensures idempotent processing even during network failures or webhook redeliveries.

### Which database tables store the billing state in awesome-gpt-image-2?

Supabase tables include `membership_plans` and `credit_packs` for product catalogs, `profiles` for Stripe customer IDs, `payment_orders` for transaction status, `user_memberships` for subscription details, and `credit_transactions` for credit balance changes. These tables maintain consistency with Stripe through the webhook handlers in [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js).