Bonum settles off-platform: the saga records the revenue-split plan, then a downstream
operator (typically MCredit) pays sub-merchants and reports back via the settlement API.
Architecture
- Three Prisma models:
BonumTransaction,MerchantPrefixRegistry,BonumConfig - NestJS module: dedicated
bonummodule on the backend - Payment strategy:
BonumPaymentStrategy— registered inPaymentFactoryunderprovider === 'BONUM' - Saga step: the revenue-distribution step forks for Bonum orders (records splits, no live transfer)
- Webhook receiver:
POST /bonum/webhook(IntegrationApiKeyGuard) - Settlement cron: two daily audit passes (
SettlementReconciliationCron)
API surface
Configuration
Payment flow
1
Create payment intent
POST /orders/v2/create-payment-intent with provider=BONUM. The PaymentFactory
routes to BonumPaymentStrategy.createPaymentIntent(), which upserts a
BonumTransaction and returns an invoice ID prefixed with the shop’s merchant prefix.2
Customer pays
The frontend uses the returned Apple/Google Pay token via the Bonum hosted page.
3
Confirm payment
POST /bonum/payment/confirm calls the integration service → Bonum PSP. The verify
use case then confirms via /api/payment-log/read.4
Run the confirmation saga
The standard
ConfirmPaymentSaga runs; for Bonum the revenue-distribution step
records splits rather than executing live transfers.5
Receive settlement webhook
Bonum POSTs to
/bonum/webhook → HandleBonumWebhookUseCase sets settledAt.6
Sub-merchant payout
MCredit (or the equivalent operator) reads the settlement API, pays sub-merchants, then
calls
PATCH /settlement/transactions/payout to mark the batch paid.Testing
Sandbox setup
- Set
BONUM_API_BASE_URL=https://testpsp.bonum.mn - Obtain a sandbox
BONUM_MERCHANT_KEYfrom the MCredit team - Confirm
NODE_ENVis notproduction
Unit tests
Critical scenarios
Settlement API examples
Cron audit
The settlement-reconciliation cron runs two daily passes:- Pass 1 — warns on
BonumTransactionrows that are verified but unsettled after 48h - Pass 2 — warns on rows that remain unverified after 24h
E2E against sandbox
Requires a sandboxBONUM_MERCHANT_KEY and an active MerchantPrefixRegistry entry.
1
Provision the shop
Create a shop with
bonumEnabled=true and assign a merchant prefix.2
Create the intent
POST /orders/v2/create-payment-intent with provider=BONUM → confirm the returned
invoice ID starts with the shop’s prefix.3
Pay via sandbox
Use a real Apple/Google Pay token from
testpsp.bonum.mn, POST it with the invoice ID
to the integration service’s /bonum/payment/process endpoint.4
Verify
GET /bonum/payment-log/read?invoiceId=<id> should return success=true.Related
- Order lifecycle — how Bonum slots into the confirmation saga.
- Checkout stability — the broader test matrix.