Skip to content

Subscriptions

SAPA recurring offers use explicit periods. A settled initial checkout creates a subscription; a subscription_renewal checkout can establish a later manual period.

import { createCheckout } from "@byfungsi/sapa";
const renewal = createCheckout({
type: "subscription_renewal",
subscriptionId: "sub-00000000-0000-4000-8000-000000000001",
provider: "midtrans",
customer: {
externalId: "customer-42",
email: "owner@example.com",
name: "Workspace Owner",
},
successUrl: "https://merchant.example/billing/renewed",
cancelUrl: "https://merchant.example/billing/renewal-cancelled",
clientIp: null,
idempotencyKey: "subscription-42-renewal-2026-10",
});

Changes apply at the next period boundary. A complete quote retains current and target terms, an effective time, an expiry, and the expected subscription version. scheduleSubscriptionPlanChange accepts the quote together with an idempotent acceptance command.

The target must:

  • be recurring;
  • belong to the same subscription family;
  • use the same currency;
  • be an exact registered offer revision;
  • have enough target seat capacity for existing assignments.

Quote mismatch, expiry, and version conflict require a fresh quote. Never apply only a mutable “latest plan” identifier at renewal time—the accepted complete terms are the historical contract.

Benefits with duration: "purchased_period" are active only inside settled periods. A late renewal starts a new period at settlement; it does not silently backfill an unpaid gap. Use checkAccess for a boolean and explainAccess when support or policy code needs provenance.

Metered postpaid subscriptions use a separate enrollment and invoice lifecycle; see metered billing.