Skip to content

The wallet model

The wallet is the single idea that makes Trile different from Stripe. Understand it and the rest of the API follows.

Recurring billing elsewhere works by storing a card and pulling from it each month. Nepal has no equivalent rail — banks don’t support Variable Recurring Payments, and eSewa/Khalti/Fonepay can’t schedule auto-charges. So Trile can’t pull on renewal.

Instead, the customer pre-funds a wallet, and Trile deducts from that balance each cycle. The authorization happens once (at top-up/subscribe); renewals are internal ledger movements, not new payment-provider transactions.

  • Balance — available paisa, as a string (balance_paisa).
  • Ledger — append-only entries (led_…) for every credit (top-up) and debit (billing, withdrawal). The balance is the sum of the ledger; the two are reconciled daily.
  • Pending — top-ups initiated but not yet settled by the provider (pending_paisa).
  • KYC tier & cap — a per-customer maximum balance/top-up set by NRB rules. See KYC & wallet limits.

Read it all from GET /v1/wallet/balance (end-user session):

{
"balance_paisa": "1250000",
"currency": "NPR",
"kyc_tier": "tier_1",
"cap_paisa": "10000000",
"pending_paisa": "0",
"kyc_status": "verified"
}

Customers top up via eSewa, Khalti, IME Pay, or bank transfer. A top-up:

  1. POST /v1/wallet/topup (or the checkout top-up step) creates a pending top-up (pnd_…) and returns a provider handoff URL.
  2. The customer pays at the provider.
  3. The provider redirects back; Trile verifies with the provider’s status API and settles — crediting the wallet ledger and clearing the pending amount.

Settlement is verified server-side against the provider, never trusted from the redirect alone.

When a subscription cycle is due, the billing worker:

  1. Generates an invoice for the cycle amount.
  2. Attempts to debit the wallet for that amount.
  3. Success → invoice paid, ledger debited, invoice.paid event/webhook fired.
  4. Insufficient balance → invoice stays open, subscription goes past_due, and invoice.payment_failed fires. The customer is prompted to top up; Trile retries.

The very first cycle is charged synchronously at subscription creation — which is why POST /v1/subscriptions returns insufficient_funds if the wallet can’t cover it.

SituationWhat you’ll see
Wallet can’t cover first cycle400 insufficient_funds on subscription create
Renewal fails (empty wallet)Subscription status: past_due, invoice.payment_failed webhook
Customer tops up after going past_dueTrile retries; on success subscription returns to active, invoice.paid fires
Top-up exceeds KYC cap400 wallet_cap_exceeded