Top-up
Top-up is the only way external money enters Moria. The owner creates a payment order with reference_type: account_top_up; the order moves through pending → waiting_payment → processing → terminal (completed · failed · cancelled). The main balance is credited only when the order reaches completed on a signed async callback from the gateway.
Lifecycle
Section titled “Lifecycle”A top-up payment order is a short-lived state machine. The pending state is transient — by the time the API responds, the gateway has already accepted the order and returned the payment instructions (VA number, QR code, or e-wallet link), and the order is sitting in waiting_payment. From there, the order either advances to processing once the user initiates payment, or sweeps to cancelled if it expires.
The main balance is not credited synchronously when the order is created. Credit happens later, on the gateway’s signed callback — and only then does the order land in completed.
stateDiagram-v2
direction LR
[*] --> pending: POST /v1/payment-gateway/orders<br/>(transient)
pending --> waiting_payment: gateway accepts order<br/>VA / QR / e-wallet<br/>returned to client
waiting_payment --> processing: user initiates<br/>payment
processing --> completed: signature callback ok<br/>+ main balance credited
processing --> failed: gateway reports<br/>payment failed
waiting_payment --> cancelled: user cancels<br/>or sweeper expires
completed --> [*]
failed --> [*]
cancelled --> [*]
note right of waiting_payment
Client polls or listens
for the credit. Main balance
is NOT credited until
the callback runs.
end note
note right of completed
Callback is signed and
idempotent on trx_id.
Duplicate callbacks are
safe to receive.
end note
Sequence
Section titled “Sequence”The main balance is credited only on the callback — not synchronously when the order is created. Build your client to treat the order response as an “instruction to pay” and poll or listen for the credit that will follow. Callbacks are idempotent on trx_id, so duplicate webhooks are safe.
---
config:
sequence:
actorMargin: 380
width: 240
messageMargin: 38
boxMargin: 14
noteMargin: 12
---
sequenceDiagram
autonumber
actor U as Account owner
participant API as Moria API
participant GW as Payment Gateway
Note over U,GW: Top-up · happy path<br/>• owner triggers from the client app<br/>• gateway returns payment instructions<br/>• balance is credited only on the callback (async)
U->>API: POST /v1/payment-gateway/orders<br/>{ amount, payment_method,<br/>reference_type: "account_top_up",<br/>reference_id: account_id }
API->>GW: create transaction<br/>(server-to-server)
GW-->>API: { trx_id, va_number / qr_code,<br/>status: waiting_payment }
API-->>U: 201 Created<br/>{ order_id, payment_instructions,<br/>status: "waiting_payment" }
U->>GW: pay VA / scan QR<br/>(out of band)
Note over GW,API: Callback (async, signed)
GW->>API: POST /v1/payment-gateway/callback<br/>{ trx_id, status: success, signature }
API->>API: verify signature · idempotent on trx_id<br/>set order status: "completed"<br/>credit main balance · emit event
API-->>GW: 200 OK
U->>API: GET /v1/accounts/me/balance
API-->>U: 200 OK<br/>{ balance: <updated> }