Skip to content

Ebook download

This flow keeps payment, download ownership, and optional email delivery as separate durable facts.

import {
Billing,
DownloadEmails,
Downloads,
Offer,
Purchase,
authorizeDownload,
createCheckout,
processPaymentNotification,
registerOffers,
} from "@byfungsi/sapa";
import { Effect } from "effect";
const ebookDownload =
yield *
Downloads.download({
asset: { key: "ebook.effect.pdf", revision: 1 },
duration: "perpetual",
});
const ebookEmail =
yield *
DownloadEmails.customer({
asset: { key: "ebook.effect.pdf", revision: 1 },
});
const ebookPurchase =
yield *
Purchase.parse({
key: "ebook.effect.benefits",
revision: 1,
provides: [ebookDownload, ebookEmail],
});
const ebookBilling =
yield *
Billing.oneTime({
price: { currency: "IDR", amount: 99_000n },
});
const ebook =
yield *
Offer.parse({
key: "ebook.effect",
revision: 1,
purchase: ebookPurchase,
billing: ebookBilling,
});
yield * registerOffers([ebook]);
const checkout =
yield *
createCheckout({
type: "offer",
offerKey: ebook.key,
offerRevision: ebook.revision,
quantity: null,
provider: "midtrans",
customer: {
externalId: "customer-42",
email: "buyer@example.com",
name: "Example Buyer",
},
successUrl: "https://merchant.example/orders/success",
cancelUrl: "https://merchant.example/orders/canceled",
clientIp: null,
idempotencyKey: "ebook-order-42",
});
// Redirect the browser to checkout.url. Later, in the webhook handler:
const settlement =
yield *
processPaymentNotification({
provider: "midtrans",
payload: rawProviderPayload,
});
if (settlement.status === "settled") {
// Run the deployment's fulfillment/email worker independently.
}
// In a later authenticated customer request:
const download =
yield *
authorizeDownload({
externalCustomerId: "customer-42",
asset: { key: "ebook.effect.pdf", revision: 1 },
});

Install asset revision ebook.effect.pdf@1 before registration. Return download.url only after your host authenticates that the caller owns customer-42. The URL is bounded by both the configured link lifetime and the historical eligibility end. A refund makes subsequent authorization fail even if email delivery is still retrying.

See Downloads for asset adapter and signed-link requirements.