External fulfillment
import { Access, Billing, Fulfillment, Offer, Purchase, createCheckout, processPaymentNotification, registerOffers,} from "@byfungsi/sapa";
const courseAccess = yield * Access.entitlements({ entitlements: ["course.effect.use"], duration: "perpetual", });const courseDelivery = yield * Fulfillment.external({ binding: { key: "course.effect.delivery", revision: 1 }, entitlement: "course.effect.use", });const coursePurchase = yield * Purchase.parse({ key: "course.effect.enrollment", revision: 1, provides: [courseAccess, courseDelivery], });const courseBilling = yield * Billing.oneTime({ price: { currency: "IDR", amount: 499_000n }, });const course = yield * Offer.parse({ key: "course.effect", revision: 1, purchase: coursePurchase, billing: courseBilling, });
yield * registerOffers([course]);yield * createCheckout({ type: "offer", offerKey: course.key, offerRevision: course.revision, quantity: null, provider: "midtrans", customer, successUrl, cancelUrl, clientIp: null, idempotencyKey: "course-enrollment-42", });
// The authenticated webhook atomically records payment and the obligation.const settlement = yield * processPaymentNotification({ provider: "midtrans", payload: rawProviderPayload, });Before registration, install binding course.effect.delivery@1 in the runtime’s
capability manifest and connect it to your fulfillment worker. The worker should
call the learning platform with SAPA’s stable delivery identity as the remote
idempotency key:
yield * learningPlatform.enroll({ courseId: "effect-foundations", beneficiary: obligation.beneficiary, idempotencyKey: obligation.deliveryKey, });The worker API is deployment-owned and is therefore not exported by
@byfungsi/sapa. It runs outside the settlement transaction, retries retained
obligations, and marks success only after the remote outcome is known. History
replay must never call the learning platform. A refund can enqueue a separately
fenced reversal; it must not delete the original delivery fact.