← Back to work

Case study Fintech · Payments

MJPay payment gateway

A mobile-money payment gateway for Bangladesh. It turns a bKash or Nagad "send money" into a real checkout: a hosted payment page, automatic confirmation from the operator's receipt SMS, a signed webhook to the merchant's system, and payouts sent back out through the same API.

Role
Architect & full-stack engineer, end to end
Stack
React · Hono + tRPC · PostgreSQL · Android
Status
Live since September 2026
mrjorg.site
MJPay home page

Visit mrjorg.site ↗

The problem

Most small businesses in Bangladesh are paid by mobile money: the customer opens bKash or Nagad and sends the amount to the shop's number. There is no checkout, no confirmation and no webhook. Someone reads the receipt SMS, compares Transaction IDs by hand and then releases the order, so orders wait, mistakes happen and a made-up TrxID can slip through.

Card gateways solve some of this, but they need paperwork, take a fee and hold the money in a settlement cycle. The goal was to automate the mobile-money flow businesses already use, with the money paid straight into their own wallet numbers.

My role

How a payment works

The flow is designed to take a payment from "Pay" to "Paid" in about 8 seconds:

  1. 0.0 s — The customer taps Pay and the hosted checkout opens on their phone.
  2. 0.4 s — One of the merchant's wallet numbers is allocated to this payment alone.
  3. ~6 s — The customer sends the money in their own bKash or Nagad app.
  4. ~7 s — The operator's receipt SMS reaches the bridge, which forwards it signed; it is stored raw before parsing.
  5. ~8 s — Amount, number and TrxID match exactly one open payment, which is marked paid.
  6. ~8 s — A signed payment.success event reaches the merchant's server.

Engineering decisions

The receipt SMS is the truth.

A payment is marked paid only when the operator's own SMS matches the amount, number and TrxID of exactly one open payment. A TrxID typed by the customer is a claim, never a credit. Every raw SMS is kept, so any decision can be replayed and checked.

Money is exact.

Amounts are fixed-point decimals stored and sent as strings, with integer arithmetic in code. Nothing that touches money uses floating point.

Payouts without a bank API.

A payout request makes an agent phone dial USSD itself. Large amounts, new payees, bursts and repeat numbers are risk-scored, and high-risk payouts wait for a person. Live payouts have completed in 12 and 15 seconds.

Nothing worth stealing.

There are no card rails, so no card numbers ever enter the system. API keys are revocable and locked to IP addresses, sign-in uses two factors, staff actions go to an append-only log, and public endpoints return only the fields they name.

Invite-only, checked by a person.

Every merchant is approved by a person. They test the whole integration in a sandbox first, payouts included, then swap one key to go live.

Key features delivered

Stack

Outcome

MJPay has been live at mrjorg.site since September 2026. Merchants connect with one API key, a webhook secret and an endpoint URL, using a published integration guide. Automatic payouts have completed in 12 and 15 seconds on live runs, and the same engine powers the payment gateway solution on this site.

Need payments in your product? Let's talk →