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. Subscriptions started from a checkout session prepay their first period instead; see below.
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 as the first period upfront, and creates the subscription when the payment succeeds.
// One hosted session collects the card, charges the first period upfront
// and starts the subscription. The card is saved automatically. Set
// `amount` to one period of the items: 3 seats at 4.990 kr.
const session = await borga.paymentSessions.create({
amount: 14970,
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 subscription binds to a Customer resolved from the payer's email. Pass customer or customer_email when you know it; leave both out and the checkout adds an email field to the card form and saves what the payer enters before the payment is submitted.
Set amount to one period of the items (quantity times unit amount, summed). Its invoice carries one line per price at the product's VAT rate, or one line per rate when amount differs from the prices' total (a promotion code, say), unless you send line_items or a tax_rate of your own. Because the first period is prepaid, the invoice at the end of that period carries no licensed lines, only metered usage, prorations and one-off items; every later period is billed in arrears as normal. The payment id is recorded on the subscription's metadata as initial_payment_id. 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, and the invoice is marked paid when the bank confirms. Requires PayDay connected, ISK prices and a customer with a kennitala; 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. Because the period-end invoice bills the quantity the item has then, an increase is billed for the whole period and needs no proration. A decrease charges the dropped seats for the part of the period already used: withproration_behavior: "create_prorations"(default) on the next invoice, withalways_invoiceright away, and withnonenot at all. Changes during a trial are free; in a first period prepaid at checkout, the rest of the period is prorated. - Add or remove prices. Create or delete subscription items. Adding works like an increase, removing like a decrease, with the same
proration_behavioroptions. - One-off charges. Create an invoice item on the subscription; it lands on the next invoice as a line. A negative amount is a credit: it is not a line but goes into the customer's credit balance, applied to the next invoice after tax.
- Credits. What a customer is owed for a prorated decrease or a paused stretch is not a line on the next invoice. The invoice keeps its full lines and VAT, the credit is applied to it after tax as
credit_applied, and onlyamount_dueis collected; whatever the invoice does not use waits for the one after. A bank claim is issued for the amount due. - Discounts. A coupon is a percentage or a fixed amount off the cycle invoice, for the next invoice, a number of months or for good; customers redeem one with a promotion code, or you apply it to the subscription by id. The subscription keeps a snapshot of the discount. The discount comes off the lines' total, is booked as a negative line per VAT rate so the provider sees it, and VAT is computed on what the customer pays. Every discount is a coupon, so a one-off reduction for one customer is a coupon with one redemption. Price a subscription with or without a code using quote.
- Cancel.
cancelwithat_period_end: truesetscancel_at_period_endand bills a final invoice for the current period;uncancelreverses it. Without the flag the subscription ends now and a final invoice bills the usage recorded so far plus the licensed items for the part of the period that was used (the same rule as lowering a quantity mid-period). - Pause and resume.
pausestops the licensed charge, not the clock: every period is still invoiced when it ends, with usage in full and the licensed items only for the time before the pause, so a fully paused month with no usage produces no invoice.resumecredits the paused stretch of the current period against the next invoice's amount due.
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. Usage is still recorded and invoiced at period end; the licensed charge stops. |
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). The schedule and what happens when it runs out are set per merchant in the dashboard, under Settings → Subscription billing. 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, or, if you chose to cancel, it is canceled and subscription.canceled fires with data: { id, cancellation_reason: "payment_failed" }.
Use these events to prompt the customer for another card. Send them a customer portal link: they can pay the failed invoice there with a new card, which becomes their default for later charges. A card the customer saves anywhere else also replaces a default that charges are failing on, and the failed invoices are retried with it within a minute. So does setting a default yourself.
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.