@86d-app/subscriptions
v0.0.30
Published
Subscription plans and recurring billing module for 86d commerce platform
Maintainers
Readme
[!WARNING] This project is under active development and is not ready for production use. Please proceed with caution. Use at your own risk.
Subscriptions Module
📚 Documentation: 86d.app/docs/modules/subscriptions
Subscription plan and subscriber management for the 86d commerce platform. Handles recurring billing cycles, trial periods, and subscription lifecycle. Payment processing is delegated to your payment provider.
Installation
npm install @86d-app/subscriptionsUsage
import subscriptions from "@86d-app/subscriptions";
import { createModuleClient } from "@86d-app/core";
const client = createModuleClient([
subscriptions({
currency: "USD",
}),
]);Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| currency | string | undefined | Default currency for subscription plans |
Subscription Lifecycle
subscribe() → active
└─ if plan.trialDays > 0 → trialing
expireSubscriptions() → expired (currentPeriodEnd < now)
cancelSubscription() → cancelled (immediate or at period end)
renewSubscription() → active (advances period dates)Billing Intervals
| Interval | Description |
|---|---|
| day | Daily billing |
| week | Weekly billing |
| month | Monthly billing |
| year | Annual billing |
intervalCount multiplies the interval (e.g. interval: "month", intervalCount: 3 = quarterly).
Store Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /subscriptions/subscribe | Subscribe to a plan |
| GET | /subscriptions/me | Get subscriptions for an email address |
| POST | /subscriptions/me/cancel | Cancel a subscription |
Query parameters for GET /subscriptions/me: email (required)
Admin Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /admin/subscriptions | List all subscriptions |
| GET | /admin/subscriptions/:id | Get a subscription by ID |
| GET | /admin/subscriptions/plans | List all plans |
| POST | /admin/subscriptions/plans/create | Create a subscription plan |
| PUT | /admin/subscriptions/plans/:id/update | Update a plan |
| DELETE | /admin/subscriptions/plans/:id/delete | Delete a plan |
Controller API
// ── Plans ──────────────────────────────────────────────────────────────
controller.createPlan(params: {
name: string;
description?: string;
price: number; // in cents
currency?: string;
interval: SubscriptionInterval;
intervalCount?: number; // default 1
trialDays?: number;
isActive?: boolean;
}): Promise<SubscriptionPlan>
controller.getPlan(id: string): Promise<SubscriptionPlan | null>
controller.listPlans(params?: {
activeOnly?: boolean;
take?: number;
skip?: number;
}): Promise<SubscriptionPlan[]>
controller.updatePlan(id: string, params: {
name?: string;
description?: string;
price?: number;
trialDays?: number;
isActive?: boolean;
}): Promise<SubscriptionPlan | null>
controller.deletePlan(id: string): Promise<boolean>
// ── Subscriptions ──────────────────────────────────────────────────────
// Subscribe a customer to a plan
// Sets status to "trialing" if the plan has trialDays > 0
controller.subscribe(params: {
planId: string;
email: string;
customerId?: string;
}): Promise<Subscription>
controller.getSubscription(id: string): Promise<Subscription | null>
controller.getSubscriptionByEmail(params: {
email: string;
planId?: string;
}): Promise<Subscription | null>
// Cancel immediately or flag for end-of-period cancellation
controller.cancelSubscription(params: {
id: string;
cancelAtPeriodEnd?: boolean;
}): Promise<Subscription | null>
// Advance period dates to the next billing cycle
controller.renewSubscription(id: string): Promise<Subscription | null>
// Mark overdue subscriptions as expired — call periodically (e.g. cron)
// Scans active and trialing subscriptions; returns the count expired
controller.expireSubscriptions(): Promise<number>
controller.listSubscriptions(params?: {
email?: string;
planId?: string;
status?: SubscriptionStatus;
take?: number;
skip?: number;
}): Promise<Subscription[]>Example: Full Subscription Flow
// Create a monthly plan with a 14-day trial
const plan = await controller.createPlan({
name: "Pro",
price: 1999,
interval: "month",
trialDays: 14,
});
// Subscribe a customer
const sub = await controller.subscribe({
planId: plan.id,
email: "[email protected]",
customerId: "cust_123",
});
// sub.status === "trialing"
// On webhook from payment provider: renew
await controller.renewSubscription(sub.id);
// Customer cancels at end of period
await controller.cancelSubscription({ id: sub.id, cancelAtPeriodEnd: true });
// Cron job: expire overdue subscriptions
const count = await controller.expireSubscriptions();Types
type SubscriptionInterval = "day" | "week" | "month" | "year";
type SubscriptionStatus = "active" | "trialing" | "cancelled" | "expired" | "past_due";
interface SubscriptionPlan {
id: string;
name: string;
description?: string;
price: number;
currency: string;
interval: SubscriptionInterval;
intervalCount: number;
trialDays?: number;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
interface Subscription {
id: string;
planId: string;
customerId?: string;
email: string;
status: SubscriptionStatus;
currentPeriodStart: Date;
currentPeriodEnd: Date;
trialStart?: Date;
trialEnd?: Date;
cancelledAt?: Date;
cancelAtPeriodEnd: boolean;
createdAt: Date;
updatedAt: Date;
}Store Components
SubscriptionPlans
Displays a grid of available subscription plans with subscribe functionality. Fetches its own data.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| email | string | — | Pre-fill subscriber email |
| customerId | string | — | Customer ID for authenticated subscriptions |
| title | string | — | Section heading |
| onSubscribed | (subscription) => void | — | Callback after successful subscription |
Usage in MDX
<SubscriptionPlans />
<SubscriptionPlans email={customerEmail} title="Choose a Plan" />MySubscriptions
Lists a customer's active subscriptions with cancellation options. Fetches its own data.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| email | string | — | Customer email (required) |
| title | string | — | Section heading |
Usage in MDX
<MySubscriptions email={customerEmail} />
<MySubscriptions email={customerEmail} title="Your Subscriptions" />Typically placed on a customer account page.
PlanCard
Individual subscription plan card. Used internally by SubscriptionPlans.
Props
| Prop | Type | Description |
|------|------|-------------|
| plan | SubscriptionPlan | Plan object with id, name, description, price, currency, interval, intervalCount, trialDays, isActive |
| onSubscribe | (planId: string) => void | Callback when subscribe button is clicked |
| subscribing | boolean | Loading state for the subscribe action |
Usage in MDX
<PlanCard plan={plan} onSubscribe={handleSubscribe} subscribing={false} />SubscriptionCard
Individual active subscription card with status and cancel options. Used internally by MySubscriptions.
Props
| Prop | Type | Description |
|------|------|-------------|
| subscription | Subscription | Subscription object with id, planId, email, status, currentPeriodStart/End, cancelAtPeriodEnd, createdAt |
| cancelling | boolean | Loading state for the cancel action |
| onCancel | (atPeriodEnd: boolean) => void | Callback to cancel. true = cancel at period end, false = cancel immediately |
Usage in MDX
<SubscriptionCard subscription={sub} cancelling={false} onCancel={handleCancel} />