PRD: Payment lifecycle over providers
| Module | Payment | PRD ID | PRD-PAY-001 |
| Status | Ready to dev | FEAT | PAY |
| Epic | — | Plane | BANA-1511 |
| Date | 2026-07-15 | Version | v1.1 |
| Packages | @nx/payment | URD | PAY |
| Surface | BackendSale · POS | ||
| Owner | Phát Nguyễn | ||
What the payment service does
When a cashier collects a payment for an order by QR code or card reader, the money does not go straight from the POS into the books. It travels through an external payment provider - VNPAY - which then reports the result back.
The Payment Service is where that result lands. It takes the provider's notification, changes the payment's status, pushes the new status to the cashier's screen at once, and notifies the Sale so the order can settle. From there finance, inventory and invoicing react on their own.
The four states of a payment
Every payment runs a short lifecycle, described in business terms:
| State | Meaning |
|---|---|
| pending | The request is created, the QR code is waiting to be scanned or the reader is waiting for a card |
| paid | The provider confirms the money has arrived |
| failed | The provider reports the transaction did not go through |
| expired | The QR code lapsed before anyone paid |
Two ways VNPAY takes money
- VNPAY QR MMS: generates a QR code the customer scans with a banking app to pay.
- PhonePOS: turns the cashier's phone into a card-acceptance device the customer taps a card against.
Both report their result to the payment service along the same path, so the rest of the system behaves the same regardless of how the customer paid.
One QR payment, from scan to books
A cashier collects 200,000d for order #123 via VNPAY QR MMS:
| What happens | Payment state | The cashier sees |
|---|---|---|
| Cashier taps collect, the QR code appears | pending | A screen waiting for the scan |
| Customer scans and pays, VNPAY reports back | paid | The screen flips to paid on its own, no manual refresh |
| Sale receives the notice, settles order #123 | paid | Order #123 closes, receipt prints |
- The cashier does not refresh or wait on a polling loop. The status pushes itself the moment VNPAY confirms.
- Right after the order settles, finance posts a receipt voucher, inventory decrements stock, invoicing issues the invoice - all on their own, never holding up the cashier.
The thing most often gotten wrong
Configuration and credentials do NOT belong to the payment service
Which provider to use, the merchant code and credentials, which wallet it binds to - all of that lives in the merchant's Finance Account, owned by the finance module. Both the shop owner and the operations team configure it there.
The payment service only reads the active configuration from the Finance Account when collecting. It has no configuration screen of its own and stores no credentials of its own.
A redelivered result is applied only once
A flaky network can make the provider send the same success notification twice. The payment service recognises the second one and skips it, so there is never a double charge or a double posting for the same payment.