How your customers pay

roi bills against a card your processor holds — roi itself never sees a number, only the processor-minted references. Getting that first card on file has three shapes, from zero code to full control. All three end in the same place: the card is the customer's default payment instrument, and every invoice a subscription issues collects from it automatically.

01zero code

Subscribe link

Mint a hosted checkout page for a plan and put the URL behind the button on your pricing page. The visitor enters an email, stores a card on your processor's secure page, and comes back subscribed. Nothing is charged at signup — plans bill in arrears, so the first invoice arrives at the end of the first period and collects on its own.

curl https://api.billroi.com/v1/checkout-links \
  -H "Authorization: Bearer $ROI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan_id": "<plan uuid>"}'

# → { "url": "https://api.billroi.com/s/…" }  ← this goes behind your Subscribe button

Links are multi-use — one link serves every visitor — and revocable without touching existing subscribers. You can also mint and copy them from the plan page in the cockpit, no API call involved.

02your pages, roi's session

roi-brokered redirect

Keep signup entirely inside your product and let roi broker the card capture. Create a capture session, redirect the customer to the returned URL — your processor's hosted page, so card entry stays in PCI SAQ-A territory — then, on return to your site, read the vaulted card back and start the subscription.

The TypeScript SDK is not on npm yet, so the import below will not resolve today. It is shown because it is the shortest way to read the flow; every call in it is a plain HTTPS request documented in the reference, and the API is the product. Saying so here rather than letting someone find out at npm install.

import { ROI } from "@roi-billing/sdk";
const roi = new ROI({ apiKey: process.env.ROI_API_KEY });

// 1. Your signup handler: open a session and redirect.
const session = await roi.paymentMethods.captureSession({
  connector: "stripe", currency: "USD", customerId: customer.id,
  approvedUrl: "https://app.example.com/billing/return",
});
redirect(session.redirect_url);

// 2. Your return handler: vault the card, then subscribe.
await roi.paymentMethods.fromSession({
  customerId: customer.id, connector: "stripe", sessionId,
});
await roi.subscriptions.create({ customerExternalId: "workspace_42", planId });

The capture is a $0, customer-present transaction that establishes the recurring mandate — the thing every later merchant-initiated rebill legally hangs off. Redirect is the deliberate design: hosted pages refuse to be framed, because a card form inside an iframe on your page would drag you into card-data scope.

03pass the token

Bring your own checkout

Already running Stripe Checkout, Elements, or a processor vault flow? Keep it. Hand roi the references your flow produced and they become the customer's billing instrument — at creation, or any time after.

// New customer, refs known up front:
await roi.customers.create({
  externalId: "workspace_42", email: "billing@acme.test",
  connector: "stripe", pspCustomerRef: "cus_…", pspPaymentMethodRef: "pm_…",
});

// Existing customer, card captured later by your own flow:
await roi.paymentMethods.attach("workspace_42", {
  connector: "stripe", pspCustomerRef: "cus_…", pspPaymentMethodRef: "pm_…",
});

Attach is idempotent — re-sending the same reference refreshes the stored card instead of duplicating it — and takes "default": false when you are storing a backup card rather than switching billing to it.

04the fallback that upgrades itself

No card yet? Invoices carry their own pay page

A customer with no stored card isn't stuck: their invoices resolve to invoice mode and each one carries a hosted, tokenized pay page. When the customer pays one, the card they used is vaulted and becomes their default — so the next invoice collects automatically. One payment turns a manual relationship into an automatic one, and the same mechanism is how dunning recovery replaces a failing card.

Start in test modeAPI reference