Skip to content

Stripe Integration

Overview

MysticX uses Stripe for subscription billing and one-time credit pack purchases, integrated through the Better Auth Stripe plugin.

Products

Web Subscriptions

ProductMonthlyYearlyYearly Credit Grant
Gold$19.99/mo (6,000 SE/mo)$89.99/yr (62% off)100,000 SE one-time
Diamond$69.99/mo (30,000 SE/mo)$299.99/yr (64% off)1,000,000 SE one-time

Yearly plans deposit credits as a one-time lump sum when the subscription is created or renewed. Legacy yearly subscribers on the old pricing ($167.92/$587.92) continue to receive monthly-style grants until they migrate to the new yearly price IDs.

Credit Packs (One-Time — Web Only)

PackCreditsPrice
Taster250$1.99
Mini600$3.99
Starter2,000$9.99
Best Value4,000$14.99

Gold subscribers receive +10% bonus credits; Diamond subscribers +15% — at the same price.

Credit packs use ad-hoc price_data (no pre-created Stripe Price IDs needed).

Setup

1. Stripe Dashboard Configuration

Products and Prices

Create products in Stripe Dashboard:

  • Gold Monthly — $19.99/mo recurring
  • Gold Yearly — $89.99/yr recurring (new — STRIPE_PRICE_GOLD_YEARLY_V2)
  • Diamond Monthly — $69.99/mo recurring
  • Diamond Yearly — $299.99/yr recurring (new — STRIPE_PRICE_DIAMOND_YEARLY_V2)

Set the Price IDs in environment variables. The old yearly Price IDs (STRIPE_PRICE_GOLD_YEARLY / STRIPE_PRICE_DIAMOND_YEARLY) remain configured for backward compatibility with legacy subscribers.

Webhook Endpoint

Point the webhook to: {APP_URL}/api/auth/stripe/webhook

Required events:

  • checkout.session.completed
  • invoice.payment_succeeded
  • customer.subscription.updated
  • customer.subscription.deleted

Customer Portal

Enable the Stripe Customer Portal with these settings:

  • Proration: immediately for upgrades
  • Downgrades: at period end
  • Cancellations: at period end
  • Allow customers to update payment methods

2. Environment Variables

env
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Subscription Lifecycle

New Subscription

  1. User clicks "Subscribe" on pricing page
  2. App creates a Stripe Checkout Session via Better Auth
  3. Stripe redirects to checkout
  4. On completion, webhook fires checkout.session.completed
  5. App updates user tier and creates subscription record
  6. Credits are granted (monthly grant + any proration)

Upgrade (Gold → Diamond)

  1. Stripe processes mid-cycle proration (immediate charge for difference)
  2. Webhook fires customer.subscription.updated
  3. App updates tier to DIAMOND
  4. Credit differential is granted

Downgrade (Diamond → Gold)

  1. Change takes effect at period end (no immediate charge)
  2. Webhook fires at next billing cycle
  3. App updates tier to GOLD

Cancellation

  1. User cancels via Customer Portal or app
  2. Subscription status changes to cancel_at_period_end
  3. Access continues until period end
  4. Webhook fires customer.subscription.deleted at period end
  5. App updates tier to FREE

Failed Payment

  1. Stripe retries per dunning schedule (recommended: 3 retries over 2 weeks)
  2. If all retries fail, subscription is canceled
  3. App handles graceful degradation

One-Time Credit Purchases

  1. User selects a credit pack in the CreditPurchaseModal
  2. App creates a Stripe Checkout Session with ad-hoc price_data
  3. On completion, webhook grants credits with tier bonus
  4. Transaction is recorded in CreditTransaction (idempotent by invoice ID)

Security

  • Rate limiting: 5 purchase attempts per 60 seconds per user
  • Idempotency: All credit grants check existing CreditTransaction by invoice ID
  • Metadata integrity: Checkout sessions include user ID and pack metadata for verification
  • Webhook signature verification: All webhooks validated against STRIPE_WEBHOOK_SECRET

Polling on Return

After Stripe redirects back to the app:

  1. Client polls subscription/credit status (up to 5 attempts, 2-second intervals)
  2. Handles race conditions where webhook arrives after redirect
  3. Shows success state once confirmed

Key Implementation Notes

  • Stripe Customer ID is created automatically on first purchases via Better Auth plugin
  • Customer ID is persisted on the user record (stripeCustomerId)
  • All subscription state changes flow through webhooks (never direct API polling for state)
  • The membership page at /membership shows current plan, upgrade/downgrade options, and manage subscription button

Internal documentation for MysticX team