Indonesian channels are deferred payments: checkout creates an order and returns provider instructions while the order remains pending_payment. A verified provider callback—or an authorized kode-unik operator—settles the payment later.
Pick a provider by channel and operation
| Provider | Supported checkout channels | Settlement signal | Refund boundary |
|---|---|---|---|
| Midtrans Core API v2 | QRIS, BCA/BNI/BRI VA, Mandiri bill-key, Permata VA, GoPay, Indomaret, Alfamart | SHA-512 notification plus a fresh transaction-status query | Automatic for QRIS and GoPay; other channels require a recorded manual transfer |
Xendit Payment Requests 2024-11-11 |
QRIS, BCA/BNI/BRI/Mandiri VA, OVO, DANA-compatible redirects | Constant-time x-callback-token verification |
Confirm the current provider/channel contract before enabling refunds |
| Kode unik | IDR bank transfer with a visible 001–999 adjustment |
Authorized operator confirmation | Manual bank transfer with an operator-recorded reference |
Expose each channel as a separate payment method. This keeps eligibility, labels, instructions, and operational reporting explicit.
Configure Midtrans
pnpm add @adocommercekit/midtrans
node ace configure @adocommercekit/midtransSet MIDTRANS_SERVER_KEY, MIDTRANS_CLIENT_KEY, and MIDTRANS_ENVIRONMENT, then register each channel:
import { midtrans } from "@adocommercekit/midtrans"
payments: {
default: "midtrans_qris",
methods: {
midtrans_qris: midtrans({
serverKey: env.get("MIDTRANS_SERVER_KEY"),
clientKey: env.get("MIDTRANS_CLIENT_KEY"),
environment: env.get("MIDTRANS_ENVIRONMENT"),
channel: "qris",
}),
midtrans_bca_va: midtrans({
serverKey: env.get("MIDTRANS_SERVER_KEY"),
clientKey: env.get("MIDTRANS_CLIENT_KEY"),
environment: env.get("MIDTRANS_ENVIRONMENT"),
channel: "bca_va",
}),
},
}Point Midtrans notifications at <APP_URL>/commerce/webhooks/midtrans. Do not reproduce the signature check in a controller: the driver verifies the notification and re-queries Midtrans before producing a normalized event.
Configure Xendit
pnpm add @adocommercekit/xendit
node ace configure @adocommercekit/xenditSet XENDIT_SECRET_KEY and XENDIT_WEBHOOK_TOKEN:
import { xendit } from "@adocommercekit/xendit"
payments: {
default: "xendit_qris",
methods: {
xendit_qris: xendit({
secretKey: env.get("XENDIT_SECRET_KEY"),
webhookToken: env.get("XENDIT_WEBHOOK_TOKEN"),
channel: "qris",
}),
},
}Point callbacks at <APP_URL>/commerce/webhooks/xendit. Xendit uses Basic secret-key authentication and the callback token; Midtrans signature rules do not apply.
Configure kode unik
Kode unik adds a small positive adjustment so incoming bank transfers can be distinguished on one receiving account.
import { kodeUnik } from "@adocommercekit/id"
payments: {
methods: {
kode_unik: kodeUnik({
receivingAccount: {
id: "bca-main",
bankName: "BCA",
accountNumber: env.get("COMMERCE_BANK_ACCOUNT_NUMBER"),
accountName: "PT Example",
},
expiresAfterMinutes: 60,
}),
},
}The driver requires IDR, allocates one active code per receiving account, adds that code to the amount due, and returns bank-transfer instructions. Settlement is never inferred from browser input. An operator must pass host authorization to KodeUnikAdminService.confirm() and record the bank statement reference. Rejection and manual refunds also require auditable operator actions.
Checkout and operations contract
- Return every instruction and expiry supplied by the payment intent. QR payloads, VA numbers, redirect URLs, and exact transfer amounts are not interchangeable.
- Make pending, paid, expired, rejected, and review-required states visible to the customer.
- Dispatch expiry and asynchronous work through a durable queue in production.
- Treat callback bodies, payment instructions, account numbers, tokens, and provider payloads as sensitive. Keep them out of general logs and issue trackers.
- Make callback handling idempotent. Retries and out-of-order delivery are normal provider behavior.
- Test one controlled production purchase per enabled channel and every claimed refund path before launch.
Continue
- Add Indonesian shipping and regions.
- Review IDR and PPN evidence.
- Configure the full engine in the configuration reference.
- Diagnose provider boot and capability errors with troubleshooting.