Create a payment
POST /v1/partners/paymentsCreates the thing a customer pays with: a virtual account number, a QRIS payload, or an e-wallet checkout link. It does not take any money — that happens when the customer pays.
| Permission | collect-payment |
| Idempotency | Required — X-Idempotency-Key, 8–128 characters |
| Answers | 200 |
Request
Section titled “Request”POST /v1/partners/paymentsAuthorization: Bearer sk_live_xxxxxxxxxxxxxxxxx-partner-signature: <hmac-sha256 of the raw body, hex>X-Idempotency-Key: order-2026-09-03-00417Content-Type: application/json{ "method": "virtual_account", "amount": "100000", "channel": "BRI", "external_ref": "INV-2026-0912", "customer_name": "Siti Rahmawati", "expires_in_minutes": 1440}| Field | Required | Notes |
|---|---|---|
method | yes | virtual_account, qris, or ewallet |
amount | yes | Decimal string. "100000" or "100000.00". The provider-facing gross for this payment method must be a whole IDR amount; under the default DEDUCTED mode, use a whole-IDR amount |
channel | depends | Issuing bank for a virtual account (BRI); wallet vendor for e-wallet (EWALLET_DANA). Omit for QRIS — one QR is scannable by every wallet |
external_ref | no | Your own reference, echoed on every response and searchable in the console. Strongly recommended |
organization_id | no | Which co-operative under you this belongs to, so you can reconcile per organisation. Must be one Moria has linked to your account — an unlinked id is refused with RESOURCE_NOT_LINKED rather than quietly attributed |
customer_name | no | |
customer_email | no | |
customer_phone | no | |
expires_in_minutes | no | How long the instrument stays payable. Defaults to 24 hours |
Response
Section titled “Response”Answers 200, not 201 — the payment exists, but nothing has been paid.
{ "data": { "id": "0f3c…", "reference": "INV-2026-0912", "status": "pending", "is_final": false, "method": "virtual_account", "channel": "BRI", "amount": "100000.0000", "net_amount": "96000.0000", "fee": "4000.0000", "currency": "IDR", "payment_code": "888801000157508", "status_url": "/v1/partners/payments/0f3c…", "expires_at": "2026-09-04T07:00:00.000Z", "created_at": "2026-09-03T07:00:00.000Z", "finalized_at": null }, "request_id": "req_…"}payment_code is what the customer pays with, and its meaning follows method:
| Method | payment_code holds |
|---|---|
virtual_account | The VA number to transfer to |
qris | The QR payload to render |
ewallet | The checkout URL to send them to |
reference echoes your external_ref when you sent one, and falls back to Moria’s own reference when you did not — which is one reason to always send one.
Fees are reported as one partner-facing figure: amount is the gross, fee is the combined charge, and net_amount is yours. Moria keeps the provider/margin split internally for accounting and reconciliation.
Errors worth handling
Section titled “Errors worth handling”| Code | Means |
|---|---|
REQUEST_IDEMPOTENCY_KEY_REUSED | The key is already attached to a different request. Usually a bug where one key is reused across orders |
REQUEST_INVALID_PAYLOAD | The provider-facing gross is fractional; money-in instruments require a whole IDR amount |
RESOURCE_NOT_LINKED | The organization_id is not linked to your account, or its link was revoked |
AUTH_PARTNER_INACTIVE | Your account is not active yet — KYB has not passed |
PROVIDER_INDETERMINATE | We asked the provider and cannot tell whether it worked. Poll; do not re-send |
The full list is on Errors.
- Quote the fees first if you want to show a total before committing
- Read the payment back while you wait for a terminal state
- Webhooks — the intended way to learn that it was paid