Tiered SaaS
Basic and Pro belong to one subscription family. A plan change schedules terms; it does not grant access or create payment.
import { Access, Billing, Limits, Offer, Purchase, ScheduleSubscriptionPlanChangeCommand, checkAccess, createCheckout, processPaymentNotification, registerOffers, scheduleSubscriptionPlanChange, type SubscriptionPlanChangeQuote,} from "@byfungsi/sapa";
const basicAccess = yield * Access.entitlements({ entitlements: ["workspace.use"], duration: "purchased_period", });const basicLimit = yield * Limits.resource({ resource: "workspace.project", quantity: 5, entitlement: "workspace.use", });const basicPurchase = yield * Purchase.parse({ key: "workspace.plan", revision: 1, provides: [basicAccess, basicLimit], });const basicBilling = yield * Billing.subscription({ family: "workspace", interval: "month", price: { currency: "IDR", amount: 100_000n }, });const basic = yield * Offer.parse({ key: "workspace.basic", revision: 1, purchase: basicPurchase, billing: basicBilling, });
const proAccess = yield * Access.entitlements({ entitlements: ["workspace.use", "analytics.advanced"], duration: "purchased_period", });const proLimit = yield * Limits.resource({ resource: "workspace.project", quantity: 100, entitlement: "workspace.use", });const proPurchase = yield * Purchase.parse({ key: "workspace.plan", revision: 2, provides: [proAccess, proLimit], });const proBilling = yield * Billing.subscription({ family: "workspace", interval: "month", price: { currency: "IDR", amount: 300_000n }, });const pro = yield * Offer.parse({ key: "workspace.pro", revision: 1, purchase: proPurchase, billing: proBilling, });
yield * registerOffers([basic, pro]);yield * createCheckout({ type: "offer", offerKey: basic.key, offerRevision: basic.revision, quantity: null, provider: "midtrans", customer, successUrl, cancelUrl, clientIp: null, idempotencyKey: "workspace-basic-42", });
// Build this quote server-side from the authenticated subscription head.const scheduleUpgrade = (quote: SubscriptionPlanChangeQuote) => scheduleSubscriptionPlanChange( ScheduleSubscriptionPlanChangeCommand.make({ quote, command: { idempotencyKey: "workspace-upgrade-42", quoteId: quote.id, expectedVersion: quote.expectedVersion, }, }), );
yield * scheduleUpgrade(acceptedProQuote);yield * createCheckout({ type: "subscription_renewal", subscriptionId: acceptedProQuote.subscriptionId, provider: "midtrans", customer, successUrl, cancelUrl, clientIp: null, idempotencyKey: "workspace-renewal-42-2026-10", });yield * processPaymentNotification({ provider: "midtrans", payload: rawProviderPayload, });const hasPro = yield * checkAccess({ externalCustomerId: "customer-42", entitlement: "analytics.advanced", });acceptedProQuote must retain current and target terms, the expected history
version, next-period boundary, and expiry. Never accept a quote assembled by an
untrusted browser. The upgrade becomes current only when the renewal settles.