Payment state machine
How transaction and provider statuses transition, expire, and map to balances.
Canonical behavior reference for payment lifecycle logic in lomi. For implementation details (database functions, jobs), see For contributors.
Money-in lifecycle
stateDiagram-v2
[*] --> pending: live payment initiated
[*] --> completed: test payment (sandbox)
pending --> completed: provider confirms success
pending --> failed: provider decline or hard failure
pending --> expired: timeout or session expiry
completed --> refunded: refund processed
failed --> [*]
expired --> [*]
refunded --> [*]In test, new payments often start as completed immediately. In live, they typically start pending until the customer approves mobile money or the card issuer confirms.
Transaction statuses
| Status | Meaning |
|---|---|
pending | Payment initiated; final outcome not yet confirmed (typical in live). |
completed | Payment succeeded; merchant balance and fees settle per platform rules. |
failed | Payment did not succeed (including some expiration paths). |
expired | Pending payment timed out. |
refunded | Refund processed against the original payment. |
Provider-facing status mapping
Platform transaction_status is reflected in provider_payment_status on linked provider records:
| Transaction status | Provider payment status |
|---|---|
completed | succeeded |
pending | processing |
failed | cancelled |
expired | expired |
refunded | refunded |
See Transactions for field names in API responses.
Status updates: idempotency and metadata
When status moves to completed:
- Duplicate completion does not double-apply balance: metadata is merged, but balance is credited once.
- Metadata is merged, not replaced, so webhook retries can append fields safely.
Pending expiration
Pending transactions can be expired by scheduled jobs:
- By age (e.g. older than N hours).
- Optionally by provider session expiry (e.g. Wave session
when_expires).
Expired rows carry expiration_info in metadata describing why they closed.
What happens on completed
- Coupon usage may be incremented.
- Balances update via an idempotent path (
metadata.balance_updatedprevents double crediting). - Subscriptions linked to the transaction may move from
pendingtoactive.
For balance timing, see Balance and settlement.