Prepaid credits
import { Billing, Credits, Offer, Purchase, consumeCredits, createCheckout, getCreditBalance, getCreditHistory, processPaymentNotification, registerOffers, releaseCredits, reserveCredits,} from "@byfungsi/sapa";
const credits = yield * Credits.pack({ unit: "ai.credit", quantity: 1_000n });const creditPurchase = yield * Purchase.parse({ key: "credits.balance", revision: 1, provides: [credits], });const creditBilling = yield * Billing.oneTime({ price: { currency: "IDR", amount: 100_000n }, });const creditPack = yield * Offer.parse({ key: "credits.pack", revision: 1, purchase: creditPurchase, billing: creditBilling, });
yield * registerOffers([creditPack]);yield * createCheckout({ type: "offer", offerKey: creditPack.key, offerRevision: creditPack.revision, quantity: null, provider: "midtrans", customer, successUrl, cancelUrl, clientIp: null, idempotencyKey: "credit-pack-42", });yield * processPaymentNotification({ provider: "midtrans", payload: rawProviderPayload, });
const query = { customerId: "customer-42", unit: "ai.credit" };const before = yield * getCreditBalance(query);const reservation = yield * reserveCredits({ ...query, quantity: 10n, idempotencyKey: "generation-job-88", });
// After the external work is known to have consumed the units:yield * consumeCredits({ reservationId: reservation.id });
// For a different reservation whose work definitely did not run:const unused = yield * reserveCredits({ ...query, quantity: 5n, idempotencyKey: "generation-job-89", });yield * releaseCredits({ reservationId: unused.id });
const after = yield * getCreditBalance(query);const movements = yield * getCreditHistory(query);Consume and release are alternative terminal outcomes for one reservation. Never release in a generic cleanup block when an external result is uncertain; reconcile first. Reservations expire after the installation’s bounded window, and all quantities are exact units—not currency.