Biashara ERP Enterprise Suite
← All guides

POS payment methods architecture

How acceptors, branch scope, and manual vs STK rails are modelled in settings and checkout.

POS payment methods — architecture

Problem

Kenyan retail rarely uses one M-Pesa setup. Typical patterns:

Tenant profile What they use
Kiosk One bank paybill (e.g. Equity 247247) + account ref → SMS on owner phone
Shop Buy Goods till or own paybill
Supermarket Per-branch paybill/till; STK at some branches only
Petrol station Many outlets × many Buy Goods tills (one per pump lane)

Payments must be configured independently, saved per block, and scoped to branches where needed. Checkout should only show what the tenant activated.

Layers

┌─────────────────────────────────────────────────────────────┐
│  Admin: Integrations → POS payment settings                 │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐            │
│  │ Manual rules│ │ POS channels│ │ Daraja/KCB/ │  each with │
│  │  [Save]     │ │  [Save]     │ │ Equity Save │  own save  │
│  └─────────────┘ └─────────────┘ └─────────────┘            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  pos_payment_settings (org-wide)                            │
│  • manual defaults (txn code, verify-on-phone)              │
│  • mpesa_channel_options[] — payment acceptors (JSON)       │
│  • daraja / kcb / equity credentials (encrypted)            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  POS checkout (branch-aware)                                │
│  Filter channels: branch_id null OR branch_id = current     │
│  Show only enabled options; cashier picks one sub-chip      │
└─────────────────────────────────────────────────────────────┘

Payment acceptor (channel option row)

Each row in M-Pesa options at POS is a payment acceptor:

Field Purpose
id Stable key at checkout (supports multiple Equity/Co-op paybills)
type Flow template (bank_paybill, manual_buy_goods, stk_paybill, …)
label Cashier-facing name
enabled Show at checkout when true
branch_id null = all branches; set = that branch only
bank_name e.g. Equity Bank, SACCO name
paybill_number Lipa na M-Pesa paybill (247247, 400200, …)
account_number 6-digit (or shorter) account ref mapped to tenant bank account
till_number Buy Goods till
txn_code_required Override org manual rule (optional)
allow_verify_without_code Override org manual rule (optional)

Dominant flow: bank paybill (manual)

  1. Owner adds Bank paybill (Lipa na M-Pesa) → Equity 247247 + account ref.
  2. Saves POS channels section only.
  3. Sets Active; disables unused STK/Daraja options.
  4. Cashier at checkout sees chip Equity Paybill 247247.
  5. Customer pays on phone → owner SMS → cashier enters M-Pesa code (or marks verified-on-phone if allowed).

No STK API, no Daraja credentials required for this flow.

Section saves

Section Route Persists
Manual M-Pesa PATCH …/sections/manual Global manual rules
POS channels PATCH …/sections/channels mpesa_channel_options
Daraja PATCH …/sections/daraja Daraja env + credentials
KCB Buni PATCH …/sections/kcb KCB env + credentials
Equity Jenga PATCH …/sections/equity Equity env + credentials

Tenants configure only the sections they need.

Branch scenarios

Scenario Configuration
3 branches, different paybills 3 bank_paybill rows, each with branch_id
Org-wide Equity paybill 1 row, branch_id empty
Petrol: 20 outlets × 4 tills 80 manual_buy_goods rows with outlet branch_id + till_number
HQ STK + branch manual only Daraja section saved once; branch rows manual-only

Checkout resolves CurrentBranch::id() and filters acceptors.

Activation at POS

  1. Per acceptor: enabled checkbox in channel builder.
  2. Payment method catalog: pos_payment_methods.is_active (Settings → payment methods) for cash/card/bank.
  3. Both must pass for M-Pesa family codes.

Future (out of scope — not implemented)

These were explicitly deferred in the branch-aware payment methods plan. They are not small polish items; each is a separate feature if requested later.

Item Why deferred
Bulk till import (CSV) Petrol chains with 80+ tills need a dedicated import UX and validation pipeline
pos_payment_acceptors table JSON array handles ~200 acceptors; relational model only if orgs exceed that scale
Per-branch Daraja shortcodes Enterprise: different Safaricom shortcodes per outlet (credentials today are org-wide)
Auto-match SMS/C2B to checkout Match incoming payment notifications to open POS carts by till/paybill/account ref

Small polish (shipped with section saves)

  • Per-section PATCH only — monolithic PUT /pos/payments/settings removed
  • Hash scroll to saved section (#ppay-channels, etc.) after redirect
  • Section isolation tests for all five PATCH endpoints

Ready to run your business on one platform?

14-day trial on entry tier · CRM & mass SMS · Industry-specific modules · Your own workspace subdomain