Moosyl logo

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 paid

Or 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:

  1. Creates an invoice for the price's amount, with a payment request for it.
  2. Moves the subscription to past_due and sets the next billing date one interval later.
  3. When that payment completes, marks the invoice paid and the subscription active again.

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

StatusMeaning
trialingIn a trial. Nothing to pay yet.
activePaid for the current period.
past_dueAn invoice is waiting to be paid.
pending_cancellationCancelled, but still paid up. Becomes cancelled when the period ends.
cancelledCancelled. No more invoices.
expiredReached the expiresAt date you set.
pausedPaused.

Cancel a subscription

await moosyl.cancelSubscription(subscription.id);
  • If the subscription is past_due or trialing, it's cancelled immediately.
  • Otherwise it becomes pending_cancellation and stays usable until the end of the current period, then becomes cancelled.

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.

MethodEndpointDescription
GET/productsList products with their prices
POST/productsCreate a product
GET/products/:idGet a product with its prices
PATCH/products/:idUpdate a product
PATCH/products/:id/archiveArchive a product
POST/pricesCreate a price
GET/prices/:idGet a price
PATCH/prices/:idUpdate a price
PATCH/prices/:id/archiveArchive a price
GET/customersList customers
POST/customersCreate a customer
PATCH/customers/:idUpdate a customer
GET/subscriptionsList subscriptions (filter by status)
POST/subscriptionsCreate a subscription
GET/subscriptions/:idGet a subscription
POST/subscriptions/:id/cancelCancel a subscription
POST/subscriptions/by-external-userCreate a subscription by external user ID
GET/subscriptions/by-external-user/:externalUserIdGet a user's subscription
GET/invoicesList invoices

Request and response details are in the API reference.

Next steps