Skip to content

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.