Subscriptions

Manage customer subscriptions, handle lifecycle events, and check subscription status.

Subscription Lifecycle

Subscriptions flow through these states:

trialingactivepast_duecanceled
  • trialing — Free trial period (payment method may or may not be required)
  • active — Subscription is paid and active
  • past_due — Payment failed, in grace period
  • paused — Temporarily paused by customer or admin
  • canceled — Subscription ended

Get Current Subscription

Retrieve the active subscription for a customer. Use the expand parameter to include plan details.

Using the SDK

typescript
import { StackBE } from '@stackbe/sdk';

const stackbe = new StackBE({
  apiKey: process.env.STACKBE_API_KEY!,
  appId: process.env.STACKBE_APP_ID!,
});

// Get subscription with plan details
const subscription = await stackbe.subscriptions.get(customerId, {
  expand: ['plan'],
});

console.log(subscription);
// {
//   id: 'sub_xyz789',
//   status: 'active',
//   planId: 'plan_pro',
//   plan: {
//     id: 'plan_pro',
//     name: 'Pro Plan',
//     priceCents: 2900,
//     interval: 'month',
//     entitlements: { api_calls: 10000, premium_support: true }
//   },
//   currentPeriodStart: '2025-01-15T00:00:00Z',
//   currentPeriodEnd: '2025-02-15T00:00:00Z',
//   cancelAtPeriodEnd: false
// }

Via API

bash
curl "https://api.stackbe.io/v1/subscriptions/current?customerId=cust_abc123&expand=plan" \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "x-stackbe-tenant-id: your_tenant_id"

Check Access (Recommended)

Use checkAccess() to determine if a customer should have access. This respects grace periods for failed payments.

typescript
// Returns true if subscription is active OR in grace period
const hasAccess = await stackbe.subscriptions.checkAccess(customerId);

if (!hasAccess) {
  // Redirect to pricing page or show upgrade prompt
  return redirect('/pricing');
}

// Customer has access, continue with your app logic

Why use checkAccess? When a payment fails, you typically want to give customers a grace period (configured in your app settings) rather than immediately cutting off access. checkAccess() handles this automatically.

Cancel Subscription

Cancel a subscription. By default, access continues until the end of the billing period.

typescript
// Cancel at end of billing period (recommended)
await stackbe.subscriptions.cancel(subscriptionId);

// Cancel immediately (rare - use for refunds, fraud, etc.)
await stackbe.subscriptions.cancel(subscriptionId, {
  immediate: true,
});

Via API

bash
# Cancel at period end
curl -X POST "https://api.stackbe.io/v1/subscriptions/sub_xyz789/cancel" \
  -H "Authorization: Bearer sk_live_your_api_key"

# Cancel immediately
curl -X POST "https://api.stackbe.io/v1/subscriptions/sub_xyz789/cancel" \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"immediate": true}'

Pause & Resume

Allow customers to temporarily pause their subscription. Billing stops during the pause.

typescript
// Pause subscription
await stackbe.subscriptions.pause(subscriptionId);

// Resume subscription
await stackbe.subscriptions.resume(subscriptionId);

Note: Paused subscriptions don't have access to paid features. Use checkAccess() to handle this correctly.

Change Plan (Upgrade/Downgrade)

To change a customer's plan, create a new checkout session. Stripe handles proration automatically.

typescript
// Create checkout for plan change
const { url } = await stackbe.checkout.createSession({
  customerId: customerId,
  planId: 'plan_enterprise',  // New plan
  successUrl: 'https://yourapp.com/settings?upgraded=true',
  cancelUrl: 'https://yourapp.com/settings',
});

// Redirect customer to checkout
redirect(url);

When upgrading, the customer pays the prorated difference immediately. When downgrading, they receive credit toward future invoices.

Dunning & Payment Recovery

When a payment fails, StackBE automatically enters dunning mode. Check the dunning status to show appropriate UI.

typescript
const dunning = await stackbe.subscriptions.getDunningStatus(subscriptionId);

console.log(dunning);
// {
//   inDunning: true,
//   daysPastDue: 3,
//   gracePeriodDays: 7,
//   gracePeriodEndsAt: '2025-01-22T00:00:00Z',
//   hasAccess: true,  // Still in grace period
//   nextRetryAt: '2025-01-18T00:00:00Z'
// }

if (dunning.inDunning) {
  // Show payment update prompt
  showPaymentUpdateBanner({
    daysRemaining: dunning.gracePeriodDays - dunning.daysPastDue,
  });
}

List Past Subscriptions

Retrieve subscription history for a customer.

typescript
// Get all subscriptions (including canceled)
const history = await stackbe.customers.getSubscriptionHistory(customerId);

// Returns array of subscriptions with status, dates, and plan info

Subscription Object

typescript
interface Subscription {
  id: string;
  customerId: string;
  organizationId?: string;  // For B2B/team subscriptions
  planId: string;
  plan?: Plan;              // Included with expand=plan
  status: 'trialing' | 'active' | 'past_due' | 'paused' | 'canceled';
  currentPeriodStart: string;
  currentPeriodEnd: string;
  cancelAtPeriodEnd: boolean;
  canceledAt?: string;
  trialEndsAt?: string;
  createdAt: string;
}

Next Steps