Skip to content

Metered API

import {
Access,
Billing,
Offer,
Purchase,
checkAccess,
correctMeteredUsage,
createCheckout,
enrollPostpaidSubscription,
finalizeMeteredInvoice,
getMeteredInvoiceCollection,
processPaymentNotification,
recordMeteredUsage,
registerOffers,
} from "@byfungsi/sapa";
const apiAccess =
yield *
Access.entitlements({
entitlements: ["api.use"],
duration: "purchased_period",
});
const apiPurchase =
yield *
Purchase.parse({
key: "api.metered.access",
revision: 1,
provides: [apiAccess],
});
const apiBilling =
yield *
Billing.meteredSubscription({
family: "api",
interval: "month",
meter: { key: "api.request", revision: 1 },
rate: { currency: "IDR", amount: 25n, perUnits: 10n, rounding: "trunc" },
accessPolicy: { key: "api.postpaid_access", revision: 1 },
});
const meteredApi =
yield *
Offer.parse({
key: "api.metered",
revision: 1,
purchase: apiPurchase,
billing: apiBilling,
});
yield * registerOffers([meteredApi]);
const enrollment =
yield *
enrollPostpaidSubscription({
idempotencyKey: "api-enrollment-42",
externalCustomerId: "customer-42",
offerKey: meteredApi.key,
offerRevision: meteredApi.revision,
});
yield *
recordMeteredUsage({
idempotencyKey: "request-batch-2026-09-42",
subscriptionId: enrollment.subscriptionId,
meter: { key: "api.request", revision: 1 },
quantity: 120n,
occurredAt: usageOccurredAt,
});
yield *
correctMeteredUsage({
idempotencyKey: "request-correction-2026-09-42",
originalUsageIdempotencyKey: "request-batch-2026-09-42",
quantityDelta: -20n,
correctedAt,
});
const invoice =
yield *
finalizeMeteredInvoice({
subscriptionId: enrollment.subscriptionId,
periodEndsAt,
});
if (invoice.collectionStatus === "payment_due") {
yield *
createCheckout({
type: "invoice",
invoiceId: invoice.invoiceId,
provider: "midtrans",
customer,
successUrl,
cancelUrl,
clientIp: null,
idempotencyKey: `invoice-${invoice.invoiceId}`,
});
}
yield *
processPaymentNotification({
provider: "midtrans",
payload: rawProviderPayload,
});
const collection = yield * getMeteredInvoiceCollection(invoice.invoiceId);
const granted =
yield *
checkAccess({ externalCustomerId: "customer-42", entitlement: "api.use" });

Install meter api.request@1 and access policy api.postpaid_access@1 before registration. Enrollment is an authenticated merchant decision, not a payment. Record usage only from trusted servers. Finalize only after the exact current period ends; 100 net units rate to IDR 250 after one aggregate rounding step.