Subscriptions
Bill customers on a schedule with Moosyl: create products, prices, customers and subscriptions, and get an invoice and a payment request every billing cycle.
Subscriptions let you charge customers every week, month or year. Moosyl keeps track of billing dates and, every cycle, creates an invoice and a payment request for your customer to pay with their bank app.
There are four resources:
- Product: what you sell, e.g. "Pro plan".
- Price: how much and how often, e.g. 1 000 MRU monthly.
- Customer: who pays, identified by your own user ID.
- Subscription: a customer subscribed to a price.
All subscription endpoints need your secret key. The examples use moosyl-sdk; you can also call the HTTP API directly or manage everything from the dashboard.
import { Moosyl } from "moosyl-sdk";
const moosyl = new Moosyl(process.env.MOOSYL_SECRET_KEY!);1. Create a product
const product = await moosyl.createProduct({
name: "Pro plan",
description: "Full access to all features",
});2. Create a price
A price belongs to a product and has an amount in whole MRU and an interval: weekly, monthly or yearly. The SDK has no price methods yet, so use the HTTP API (or the dashboard):
const res = await fetch("https://api.moosyl.com/prices", {
method: "POST",
headers: {
Authorization: process.env.MOOSYL_SECRET_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
productId: product.id,
amount: 1000,
interval: "monthly",
}),
});
const { data: price } = await res.json();3. Create a customer
externalUserId is the user's ID in your system. It's unique per organization.
const customer = await moosyl.createCustomer({
externalUserId: "user_abc123",
phone: "22222222", // optional
});4. Subscribe the customer
const subscription = await moosyl.createSubscription({
customerId: customer.id,
priceId: price.id,
});
// subscription.status === "past_due" until the first invoice is paidOr do steps 3 and 4 in one call. The customer is created if it doesn't exist yet:
const subscription = await moosyl.createSubscriptionByExternalUser({
externalUserId: "user_abc123",
priceId: price.id,
phone: "22222222", // optional, used when creating the customer
});5. Collect the first payment
A new subscription without a trial starts as past_due with its first invoice already created. Each invoice has a payment request whose transactionId is the invoice's id. Send your customer to pay it, for example with a checkout session:
const { data: invoices } = await moosyl.listInvoices({ subscriptionId: subscription.id });
const invoice = invoices[0];
const { checkoutUrl } = await moosyl.createCheckoutSession({
paymentRequestId: invoice.paymentRequestId!,
successUrl: "https://example.com/billing/done",
cancelUrl: "https://example.com/billing",
expiresInMinutes: 60,
});When the payment completes, the invoice becomes paid and the subscription becomes active. You'll get a subscription-updated webhook.
Trials
Start with a trial to give access before the first payment. No invoice is created during the trial.
await moosyl.createSubscription({
customerId: customer.id,
priceId: price.id,
trial: true,
trialPeriod: "1_month", // "1_week", "1_month" or "1_year"
});Or pass an exact trialEnd date instead of trialPeriod. The subscription is trialing until the trial ends; then the first invoice is created as described below.
Billing cycle
On each nextBillingDate, for subscriptions that are active or trialing, Moosyl:
- Creates an invoice for the price's amount, with a payment request for it.
- Moves the subscription to
past_dueand sets the next billing date one interval later. - When that payment completes, marks the invoice
paidand the subscriptionactiveagain.
So a subscription is past_due from the moment a new invoice is created until it's paid. Moosyl doesn't charge the customer automatically: send them to pay each invoice's payment request, and use subscription-updated webhooks to grant or remove access.
Renewals bill in Production
Products belong to the environment of the key that created them. Prices, customers, subscriptions and invoices belong to your whole organization. Renewal invoices are always created in your Production environment, even if you created the subscription with a Sandbox key. See Billing objects for how each object is scoped.
Statuses
| Status | Meaning |
|---|---|
trialing | In a trial. Nothing to pay yet. |
active | Paid for the current period. |
past_due | An invoice is waiting to be paid. |
pending_cancellation | Cancelled, but still paid up. Becomes cancelled when the period ends. |
cancelled | Cancelled. No more invoices. |
expired | Reached the expiresAt date you set. |
paused | Paused. |
Cancel a subscription
await moosyl.cancelSubscription(subscription.id);- If the subscription is
past_dueortrialing, it's cancelled immediately. - Otherwise it becomes
pending_cancellationand stays usable until the end of the current period, then becomescancelled.
Look up a user's subscription
const subscription = await moosyl.getSubscriptionByExternalUser("user_abc123");Invoices
List invoices, optionally filtered by subscriptionId, externalUserId or id:
const { data: invoices, pagination } = await moosyl.listInvoices({
externalUserId: "user_abc123",
});An invoice's status is pending, paid, void or refunded, and paymentRequestId points to the payment request the customer pays.
HTTP endpoints
All endpoints need your secret key in the Authorization header.
| Method | Endpoint | Description |
|---|---|---|
GET | /products | List products with their prices |
POST | /products | Create a product |
GET | /products/:id | Get a product with its prices |
PATCH | /products/:id | Update a product |
PATCH | /products/:id/archive | Archive a product |
POST | /prices | Create a price |
GET | /prices/:id | Get a price |
PATCH | /prices/:id | Update a price |
PATCH | /prices/:id/archive | Archive a price |
GET | /customers | List customers |
POST | /customers | Create a customer |
PATCH | /customers/:id | Update a customer |
GET | /subscriptions | List subscriptions (filter by status) |
POST | /subscriptions | Create a subscription |
GET | /subscriptions/:id | Get a subscription |
POST | /subscriptions/:id/cancel | Cancel a subscription |
POST | /subscriptions/by-external-user | Create a subscription by external user ID |
GET | /subscriptions/by-external-user/:externalUserId | Get a user's subscription |
GET | /invoices | List invoices |
Request and response details are in the API reference.
Next steps
- Billing objects: fields and endpoints for products, prices, customers and invoices
- Webhooks: react to
subscription-updated - Checkout Session: collect invoice payments with a hosted page
- TypeScript SDK: every SDK method