Package reference
Mirrors the package README (single source). Install @basaltkit/subscriptions-proxypay v2.1.5 — npm · source.
<p align="center"> <a href="https://basaltkit-docs.pages.dev"> <img src="https://basaltkit-docs.pages.dev/social-card.png" alt="Basalt" width="440"> </a> </p>
@basaltkit/subscriptions-proxypay
ProxyPay payment gateway driver for @basaltkit/subscriptions — reference-based Multicaixa / EMIS payments for Angola (AOA).
Basalt's BillingGateway models card subscriptions (Stripe/Paddle). Angolan providers work differently: the customer pays a Reference at an ATM, Multicaixa Express, or a bank app using your account's fixed Entity, and the gateway confirms by webhook. This driver implements the PaymentGateway contract for that model.
Installation
pnpm add @basaltkit/subscriptions @basaltkit/subscriptions-proxypayUsage
import { ProxyPayGateway } from '@basaltkit/subscriptions-proxypay'
const payments = new ProxyPayGateway({
apiKey: process.env.PROXYPAY_API_KEY!, // Authorization: Token <key>
entity: process.env.PROXYPAY_ENTITY!, // your Multicaixa Entity (Entidade)
sandbox: process.env.NODE_ENV !== 'production',
// webhookSecret defaults to apiKey (what ProxyPay signs with); override it if you use a custom secret.
})
// Create a payment — reserves a reference and returns what to show the customer.
const instruction = await payments.createPayment({
billableId: 'acme', // echoed back on the webhook (custom_fields.billable_id)
amount: 5000, // 5000,00 Kz
expiresAt: Date.now() + 3 * 24 * 60 * 60 * 1000,
})
// instruction.reference = { entity: '00123', reference: '900000001', amount: 5000 }
// → show "Entidade 00123 · Referência 900000001 · 5.000,00 Kz"> Bring your own reference: pass a numeric reference (e.g. an order id that > is a valid ProxyPay reference) to use it directly and skip the POST /reference_ids > reserve call. Omit it to have the driver reserve the next available id for you.
Receiving the webhook
Point your ProxyPay webhook at a route and translate it:
app.post('/webhooks/proxypay', async (request, reply) => {
const raw = request.rawBody // the exact bytes — needed for signature verification
const event = payments.verifyWebhook(raw, request.headers['x-signature'])
if (event?.type === 'payment.succeeded') {
// event.billableId, event.amount, event.reference, event.paymentId
await activatePeriod(event.billableId!)
}
reply.code(200).send()
})verifyWebhook throws WebhookInvalidError (HTTP 400) on a bad signature and returns null when the payload carries no reference_id (i.e. it isn't a payment callback). ProxyPay posts a flat payment object — top-level reference_id, amount, id, custom_fields — signed with HMAC-SHA256 in the x-signature header.
API surface used
POST /reference_ids— reserve the next reference id (skipped if you pass areference)PUT /references/{id}— activate it withamount,custom_fields,end_datetime(required — defaults toexpiryDaysfrom now, 30 days, whenexpiresAtis omitted)- ProxyPay
paymentwebhook →payment.succeeded
Recurring billing
ProxyPay has no card-on-file recurring charge. Model recurring by creating one payment (reference) per period — issue the next reference when the current period ends (or on an invoice), and activate the period when its payment.succeeded arrives.
Notes & testing
- Amounts are AOA in the major unit (
5000= 5.000,00 Kz), sent to ProxyPay as a two-decimal-rounded number. - The fetch client is injectable (
options.fetch) — the globalfetchis used by default. No hard HTTP dependency. - Webhook auth: ProxyPay signs the callback with your API key (HMAC-SHA256 of the raw body, hex, in the
x-signatureheader), sowebhookSecretdefaults toapiKeyand verification is on by default. OverridewebhookSecretif you configured a custom secret. Verification cannot be disabled: an empty or whitespace-only secret makesverifyWebhookthrowWebhookSecretMissingError(fail closed). A signed but malformed body (not JSON, non-numericamount) throwsWebhookInvalidError(400). PaymentRequest.metadatais sent incustom_fields, but it can never overridebillable_idorreference— the fields the webhook is reconciled against.- Verify the exact
/reference_idsresponse shape and webhook signature scheme against your ProxyPay sandbox — the driver handles the common shapes but every account's setup should be confirmed against real credentials.