Subscriptions
Recurring billing with fixed and seat-based prices, trials, proration, discounts and bank-invoice collection.
A Subscription (sub_…) bills a Customer for one or more Prices on a schedule. Borga owns the cycle: at the end of each period it builds an invoice for what was used, books it in your accounting system, and collects it from the customer's saved card or by bank invoice. Failed charges are retried automatically.
Building blocks
| Object | Purpose |
|---|---|
Product (prod_) | What you sell, for example "Pro plan". |
Price (price_) | How much and how often: unit_amount, currency, recurring.interval. A product can have several prices. |
Customer (cus_) | Who pays. Needs a saved card for charge_automatically, or a kennitala for send_invoice. |
Subscription (sub_) | The customer's commitment to a set of prices. |
Subscription item (si_) | One price within the subscription, with a quantity (seats). |
Create the catalogue once:
const product = await borga.products.create({
name: "Pro plan",
description: "Everything in Starter plus priority support",
});
const monthly = await borga.prices.create({
product: product.id,
type: "recurring",
currency: "ISK",
unit_amount: 4990, // 4.990 kr. per seat per month
recurring: { interval: "month", interval_count: 1 },
});
const yearly = await borga.prices.create({
product: product.id,
type: "recurring",
currency: "ISK",
unit_amount: 49900,
recurring: { interval: "year" },
});Every item in a subscription must share the same billing interval and currency. Quarterly and half-yearly plans use interval: "month" with interval_count 3 or 6.
Billing model
Borga bills in arrears: when a period ends, it issues an invoice for that period (licensed items times quantity, metered usage, prorations and one-off items) and collects it. The invoice appears as draft, is finalised in your accounting provider, then paid.
A trial period is free. The subscription is trialing until the trial ends, becomes active, and the first paid period is billed when it ends.
Start a subscription
From a checkout session
The simplest path when the customer does not have a saved card yet. Pass subscription on a hosted session: the checkout collects and saves the card, charges the session amount immediately, and creates the subscription when the payment succeeds.
// One hosted session collects the card, charges the first period and
// starts the subscription. The card is saved automatically.
const session = await borga.paymentSessions.create({
amount: 4990,
currency: "ISK",
customer: "cus_8Jk2Lm4Np6Qr8St0Uv2Wx4Yz",
subscription: {
items: [{ price: "price_2Ab4Cd6Ef8Gh0Ij2Kl4Mn6Op", quantity: 3 }],
},
return_url: "https://yoursite.is/billing/welcome",
cancel_url: "https://yoursite.is/pricing",
});
// Redirect to session.url. Listen for subscription.created.The session amount is a separate, immediate charge recorded on the subscription's metadata as initial_payment_id. Because periods are billed when they end, use it for a setup fee or a small verification amount rather than the first period. You receive payment.succeeded, payment_method.attached, subscription.created and subscription.active.
From the API
When the customer already has a saved card (see Saved cards), create the subscription directly. The customer's default payment method is charged each period.
// The customer already has a saved card (payment_method.attached fired).
const subscription = await borga.subscriptions.create({
customer: "cus_8Jk2Lm4Np6Qr8St0Uv2Wx4Yz",
items: [{ price: "price_2Ab4Cd6Ef8Gh0Ij2Kl4Mn6Op", quantity: 3 }],
collection_method: "charge_automatically",
trial_period_days: 14,
metadata: { plan: "pro" },
});
console.log(subscription.status); // "trialing"
console.log(subscription.current_period_end);curl https://api.borga.is/v1/subscriptions \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"customer": "cus_8Jk2Lm4Np6Qr8St0Uv2Wx4Yz",
"items": [{ "price": "price_2Ab4Cd6Ef8Gh0Ij2Kl4Mn6Op", "quantity": 3 }],
"collection_method": "charge_automatically",
"trial_period_days": 14
}'Collect by bank invoice
Set collection_method to send_invoice and a days_until_due. Each period Borga issues a krafa through PayDay to the customer's kennitala instead of charging a card. Requires bank invoices to be enabled; see Bank invoices.
// Bill by bank invoice: each period Borga issues a krafa through the
// merchant's accounting provider and the customer pays it in their bank.
const subscription = await borga.subscriptions.create({
customer: "cus_8Jk2Lm4Np6Qr8St0Uv2Wx4Yz", // needs a kennitala
items: [{ price: "price_2Ab4Cd6Ef8Gh0Ij2Kl4Mn6Op" }],
collection_method: "send_invoice",
days_until_due: 14,
});Change a subscription
const id = "sub_9Qw1Er3Ty5Ui7Op9As1Df3Gh";
// Change seats; Borga prorates the difference on the next invoice.
const [item] = (await borga.subscriptions.retrieve(id)).items;
await borga.subscriptionItems.update(item.id, {
quantity: 5,
proration_behavior: "create_prorations",
});
// Add a one-off charge to the next invoice.
await borga.invoiceItems.create({
customer: "cus_8Jk2Lm4Np6Qr8St0Uv2Wx4Yz",
subscription: id,
amount: 15000,
description: "Onboarding workshop",
});
// Cancel when the period ends (reversible with uncancel).
await borga.subscriptions.cancel(id, { at_period_end: true });
await borga.subscriptions.uncancel(id);
// Pause and resume billing.
await borga.subscriptions.pause(id);
await borga.subscriptions.resume(id);- Seats. Update the item's
quantity. Withproration_behavior: "create_prorations"(default) the difference for the rest of the period is added to the next invoice;always_invoicebills it right away;noneskips proration. - Add or remove prices. Create or delete subscription items with the same
proration_behavioroptions. - One-off charges. Create an invoice item on the subscription; it lands on the next invoice. Negative amounts are credits.
- Discounts.
discounttakesamount_offorpercent_off. Today the onlydurationisonce, applied to the next invoice. - Cancel.
cancelwithat_period_end: truesetscancel_at_period_endand bills a final invoice for the current period;uncancelreverses it. Without the flag the subscription ends immediately. - Pause and resume.
pausestops billing;resumepicks up with the same anchor.
Every change emits subscription.updated; see the event catalog.
Statuses
| Status | Meaning |
|---|---|
trialing | In a free trial. Becomes active when it ends. |
active | Billing normally. |
past_due | A charge failed and retries were exhausted, or a bank invoice went overdue. Billing continues; a later successful payment returns it to active. |
unpaid | Reserved for future dunning policy; not set today. |
paused | Paused by you. No invoices are created. |
canceled | Ended. Final invoice issued if the cancellation was at period end. |
Failed payments
When a card charge for an invoice fails, Borga emits invoice.payment_failed and retries after 1, 3, 5 and 7 days (cumulative days 1, 4, 9 and 16 after the first failure). Each attempt emits another invoice.payment_failed with the attempt number, and the last one carries final: true. When the schedule is exhausted the subscription moves to past_due and subscription.past_due fires. Use these events to prompt the customer to update their card; a new saved card set as default is used by the next attempt.
For send_invoice subscriptions, an invoice that passes its due date emits invoice.overdue and also moves the subscription to past_due.
Invoices for subscriptions
Each cycle emits invoice.created, then invoice.finalized once the accounting provider has accepted it, then invoice.paid or invoice.payment_failed. Invoice line items carry the price's accounting_code if set, so revenue lands on the right account. See Invoicing and the Invoices API.