# How Stripe Is Integrated for Billing in awesome-gpt-image-2

> Learn how awesome-gpt-image-2 integrates Stripe for subscription billing and credit pack purchases using checkout sessions, a billing portal, and secure webhook handlers. Discover the server-side implementation.

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

---

**awesome-gpt-image-2 uses Stripe as its primary payment provider for both subscription-based memberships and one-off credit-pack purchases, implementing a server-side architecture with checkout sessions, a billing portal, and signed webhook handlers.**

The repository `freestylefly/awesome-gpt-image-2` implements a robust **Stripe billing integration** that handles recurring subscriptions and one-time purchases through a layered, secure architecture. By combining Stripe Checkout, the Billing Portal, and webhook event processing, the application manages payment flows while keeping user entitlements and credit balances synchronized in Supabase. This server-side approach ensures PCI-compliant transaction handling and real-time state management across the entire billing lifecycle.

## Configuration and Stripe Client Setup

The integration activates when the environment variable `STRIPE_SECRET_KEY` is present. In [`api/_lib/billing.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/billing.js), the helper `isStripeConfigured()` checks for this variable (#L8‑L10), ensuring billing features only appear when properly configured.

A **singleton Stripe client** is lazily instantiated by `getStripeClient()` (#L12‑L19) using the secret key and a fixed API version (`2026-02-25.clover`). This client is reused by all billing endpoints to maintain connection efficiency and consistent configuration.

## Checkout Flow for Subscriptions and Credit Packs

The checkout flow supports both membership subscriptions and one-time credit pack purchases through a unified endpoint.

### Creating the Checkout Session

The **checkout endpoint** at [`api/billing/checkout.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/checkout.js) validates the incoming request, loads the requested product via `getBillingProduct()`, and ensures a Stripe customer exists through `getOrCreateStripeCustomer()`. It then creates a **payment order** in Supabase using `createPaymentOrder()` before initiating the Stripe session.

The session is built with `checkoutLineItem(product)` (which structures the price data, currency, and metadata) and sent to Stripe via `stripe.checkout.sessions.create()` (#L60‑L104):

```js
// /api/billing/checkout.js – simplified flow
import { getStripeClient, getBillingProduct, getOrCreateStripeCustomer,
         createPaymentOrder, markOrderCheckoutCreated, checkoutLineItem } from '../../_lib/billing';

export default async function handler(req, res) {
  const stripe = getStripeClient();                     // ← Stripe client
  const product = await getBillingProduct(client, type, id);
  const customerId = await getOrCreateStripeCustomer({ client, stripe, user, profile });

  const order = await createPaymentOrder(client, { userId, product, customerId });

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

  await markOrderCheckoutCreated(client, order.id, session);
  res.json({ ok: true, url: session.url, orderId: order.id });
}

```

The resulting session URL is returned to the frontend, which redirects the user to Stripe’s hosted checkout page to complete payment securely.

### Customer Management

During the checkout process, `getOrCreateStripeCustomer()` checks the `profiles` table for an existing `stripeCustomerId`. If none exists, it calls `stripe.customers.create()` and stores the new ID in Supabase, linking all future transactions to this customer record.

## Self-Service Billing Portal

For subscription management, the **portal endpoint** at [`api/billing/portal.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/portal.js) generates a temporary session URL using Stripe’s Billing Portal. The handler calls `stripe.billingPortal.sessions.create()` (#L35‑L41) with the user’s stored `stripeCustomerId` and a return URL:

```js
// /api/billing/portal.js
import { getStripeClient, getAppUrl } from '../../_lib/billing';

export default async function handler(req, res) {
  const stripe = getStripeClient();
  const session = await stripe.billingPortal.sessions.create({
    customer: auth.profile.stripeCustomerId,
    return_url: `${getAppUrl(req)}/?billing=portal_return`
  });
  res.json({ ok: true, url: session.url });
}

```

This allows users to manage subscriptions, view invoices, and update payment methods without custom UI development.

## Webhook Processing and Event Handling

Stripe sends asynchronous events to [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js) to notify the system of payment outcomes. After validating the webhook signature using `stripe.webhooks.constructEvent()` (#L20‑L22), the handler dispatches to specific helpers based on the event type.

### Handling Checkout Completion

The `checkout.session.completed` event triggers `handleCheckoutCompleted()` (#L21‑L34). This function distinguishes between credit-pack and membership purchases, updates the `payment_orders` table, and grants purchased credits by inserting records into `credit_transactions`. Metadata attached to the Stripe session—specifically `orderId`, `userId`, and `productId`—enables reliable mapping back to Supabase records.

### Recurring Payments and Subscription Changes

For ongoing subscriptions, three additional events ensure data synchronization:

- **invoice.payment_succeeded** → `handleInvoicePaid()` (#L76‑L82) updates membership status and refreshes credit balances when recurring payments process successfully.
- **customer.subscription.updated** and **customer.subscription.deleted** → `handleSubscriptionChanged()` (#L100‑L102) synchronizes the subscription state to the `user_memberships` table, handling cancellations, upgrades, or billing cycle changes.

```js
// /api/billing/webhook.js – core logic
import { getStripeClient, readRawBody, isStripeConfigured } from '../../_lib/billing';
import { getSupabaseAdminClient } from '../../_lib/supabase';

export default async function handler(req, res) {
  const stripe = getStripeClient();
  const rawBody = await readRawBody(req);
  const event = stripe.webhooks.constructEvent(
    rawBody,
    req.headers['stripe-signature'],
    process.env.STRIPE_WEBHOOK_SECRET
  );

  switch (event.type) {
    case 'checkout.session.completed':
      await handleCheckoutCompleted(client, stripe, event.data.object);
      break;
    case 'invoice.payment_succeeded':
      await handleInvoicePaid(client, stripe, event.data.object);
      break;
    case 'customer.subscription.updated':
    case 'customer.subscription.deleted':
      await handleSubscriptionChanged(client, event.data.object);
      break;
  }
  res.json({ ok: true });
}

```

## Data Persistence and Idempotency

All billing-related data lives in **Supabase tables** including `payment_orders`, `profiles`, `user_memberships`, and `credit_transactions`. Helper functions in [`api/_lib/billing.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/billing.js)—such as `createPaymentOrder()`, `markOrderCheckoutCreated()`, `grantCredits()`, and `upsertMembershipFromSubscription()`—encapsulate CRUD operations.

These helpers ensure **idempotency** by checking existing order statuses before processing, preventing duplicate credit grants or subscription activations if Stripe sends multiple webhook events for the same transaction. Metadata carriers on Stripe objects provide the internal IDs necessary to maintain consistency between Stripe’s state and the application’s database records.

## Summary

- **Configuration-driven activation**: The integration enables only when `STRIPE_SECRET_KEY` is present, with a singleton client pattern for efficient resource usage.
- **Dual purchase support**: Handles both one-time credit packs and recurring membership subscriptions through a unified checkout endpoint.
- **Self-service management**: Leverages Stripe’s Billing Portal to provide customers with subscription and invoice management without custom UI.
- **Reliable synchronization**: Signed webhooks process checkout completions, invoice payments, and subscription changes to keep Supabase records and user entitlements in sync.
- **Idempotent operations**: Database helpers ensure safe, repeatable processing of payment events to prevent data inconsistencies.

## Frequently Asked Questions

### How does awesome-gpt-image-2 verify Stripe webhook authenticity?

The application verifies webhooks using `stripe.webhooks.constructEvent()` in [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js#L20)‑L22), which validates the request signature against the `STRIPE_WEBHOOK_SECRET` environment variable. This cryptographic verification ensures the event originated from Stripe and was not tampered with in transit.

### What database tables store billing information in this integration?

Billing data persists across four primary Supabase tables: `payment_orders` tracks transaction attempts, `profiles` stores `stripeCustomerId` links, `user_memberships` maintains active subscription states, and `credit_transactions` records granted or consumed credits. Helper functions in [`api/_lib/billing.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/billing.js) manage all interactions with these tables.

### How does the system distinguish between credit packs and membership subscriptions?

The `checkoutLineItem()` helper and checkout session logic check `product.type` to set the Stripe session mode. For memberships, it uses `mode: 'subscription'`, while credit packs use `mode: 'payment'`. Webhook handlers later reference this metadata in `handleCheckoutCompleted()` to apply the correct business logic—either inserting a subscription record or incrementing the credit balance.

### Can users manage their subscriptions without contacting support?

Yes, the [`api/billing/portal.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/portal.js) endpoint generates a temporary URL to Stripe’s hosted Billing Portal using `stripe.billingPortal.sessions.create()`. Authenticated users can access this to update payment methods, view invoices, and cancel or upgrade subscriptions independently, with the `return_url` parameter ensuring they land back in the application afterward.