SaaS subscription
import { Access, Billing, Offer, Purchase, checkAccess, createCheckout, processPaymentNotification, registerOffers,} from "@byfungsi/sapa";
const membershipAccess = yield * Access.entitlements({ entitlements: ["app.use", "projects.unlimited"], duration: "purchased_period", });const membershipPurchase = yield * Purchase.parse({ key: "saas.pro.access", revision: 1, provides: [membershipAccess], });const membershipBilling = yield * Billing.subscription({ family: "saas", interval: "month", price: { currency: "IDR", amount: 149_000n }, });const membership = yield * Offer.parse({ key: "saas.pro", revision: 1, purchase: membershipPurchase, billing: membershipBilling, });
yield * registerOffers([membership]);const checkout = yield * createCheckout({ type: "offer", offerKey: membership.key, offerRevision: membership.revision, quantity: null, provider: "midtrans", customer: { externalId: "customer-42", email: "buyer@example.com", name: "Example Buyer", }, successUrl: "https://merchant.example/billing/success", cancelUrl: "https://merchant.example/billing/canceled", clientIp: null, idempotencyKey: "saas-initial-42", });
// In the authenticated provider webhook handler:const settled = yield * processPaymentNotification({ provider: "midtrans", payload: rawProviderPayload, });if (settled.status !== "settled" || settled.subscriptionId === null) { return;}
const granted = yield * checkAccess({ externalCustomerId: "customer-42", entitlement: "app.use", });
const renewal = yield * createCheckout({ type: "subscription_renewal", subscriptionId: settled.subscriptionId, provider: "midtrans", customer: { externalId: "customer-42", email: "buyer@example.com", name: "Example Buyer", }, successUrl: "https://merchant.example/billing/renewed", cancelUrl: "https://merchant.example/billing/canceled", clientIp: null, idempotencyKey: "saas-renewal-42-2026-10", });Redirect to checkout.url and later renewal.url; neither result means paid.
The renewal derives frozen terms and beneficiary identity from authoritative
subscription history. Access is end-exclusive and becomes active only after an
authenticated settlement notification.