Billing — Subscriptions & Payments

reauth handles Stripe subscriptions for your users. Plans, checkout, and subscription status are all managed through the SDK.

Setup

  1. Connect your Stripe account in the reauth dashboard
  2. Create subscription plans (code, name, price, interval, features)
  3. Choose test or live mode

Plans

Plans are configured in the dashboard. Each plan has:

FieldDescription
codeMachine-readable identifier (e.g., "pro")
nameDisplay name (e.g., "Pro Plan")
priceCentsPrice in cents (e.g., 1999 for $19.99)
currencyISO currency code (e.g., "usd")
interval"monthly" | "yearly" | "custom"
intervalCountBilling cycles per interval
trialDaysFree trial period
featuresArray of plan features (boolean or numeric)
planType"self_service" (checkout) or "sales" (contact URL)
creditsAmountCredits included with subscription
contactUrlURL for sales-type plans

Fetching Plans

const plans = await reauth.getPlans();
// Returns public plans sorted by displayOrder

plans.forEach(plan => {
  console.log(`${plan.name}: $${plan.priceCents / 100}/${plan.interval}`);
  plan.features.forEach(f => {
    if (f.featureType === 'boolean') {
      console.log(`  ✓ ${f.name}`);
    } else {
      console.log(`  ${f.numericValue} ${f.unitLabel ?? f.name}`);
    }
  });
});

Checkout

Self-Service Plans

// Option 1: Create checkout and redirect manually
const { checkoutUrl } = await reauth.createCheckout(
  'pro',                                    // plan code
  'https://yourdomain.com/billing/success', // success URL
  'https://yourdomain.com/pricing',         // cancel URL
);
window.location.href = checkoutUrl;

// Option 2: Convenience method (browser-only)
// Uses current URL as both success and cancel URL
await reauth.subscribe('pro');

Sales Plans

For planType: "sales" plans, redirect to the contact URL instead:

if (plan.planType === 'sales') {
  window.location.href = plan.contactUrl!;
} else {
  await reauth.subscribe(plan.code);
}

Subscription Status

From Session (No API Call)

Subscription data is embedded in the JWT and available in the session:

const session = await reauth.getSession();
if (session.subscription) {
  session.subscription.status;           // "active", "trialing", "none", etc.
  session.subscription.plan_code;        // "pro"
  session.subscription.plan_name;        // "Pro Plan"
  session.subscription.current_period_end; // Unix timestamp
  session.subscription.cancel_at_period_end;
  session.subscription.trial_ends_at;
}

From Server SDK (No API Call)

const result = await reauth.authenticate(request);
if (result.valid && result.user) {
  const { subscription } = result.user;
  subscription.status;   // "active"
  subscription.planCode; // "pro" (camelCase in server SDK)
}

Full Subscription Details

const sub = await reauth.getSubscription();
// {
//   id: string | null,
//   planCode: string | null,
//   planName: string | null,
//   status: SubscriptionStatus,
//   currentPeriodEnd: number | null,
//   trialEnd: number | null,
//   cancelAtPeriodEnd: boolean | null,
// }

Subscription Lifecycle

(no subscription) → trialing → active → canceled
                       ↓          ↓
                    active    past_due → paused → canceled
StatusMeaning
noneNo subscription
trialingIn free trial period
activePaid and current
past_duePayment failed, in grace period
pausedGrace period expired, access suspended
canceledSubscription ended
incompleteFirst payment pending
incomplete_expiredFirst payment failed
unpaidUnpaid after retries

Cancellation

Cancel at the end of the current billing period:

await reauth.cancelSubscription();
// Subscription remains active until currentPeriodEnd
// cancelAtPeriodEnd becomes true

Billing Portal

Open the Stripe customer portal for self-service subscription management:

await reauth.openBillingPortal('https://yourdomain.com/settings');
// Redirects to Stripe portal (browser-only)

The portal lets users:

  • Update payment method
  • View invoices
  • Cancel or resume subscription

Org-Scoped Billing

Subscriptions are scoped to the active organization. When a user switches orgs, switchOrg() returns the subscription for that org:

const result = await reauth.switchOrg('team-org-uuid');
result.subscription.status;   // May differ from personal org
result.subscription.planCode;

See organizations.md for more on org context.

Feature Gating

Use plan features for access control:

const plans = await reauth.getPlans();
const session = await reauth.getSession();

const currentPlan = plans.find(p => p.code === session.subscription?.plan_code);
const apiCalls = currentPlan?.features.find(f => f.code === 'api_calls');

if (apiCalls?.featureType === 'numeric') {
  console.log(`Allowed API calls: ${apiCalls.numericValue}`);
}