Multi-Gateway Payment Architecture & Ghanaian Commerce
This document details the multi-gateway payment subsystem, Mobile Money integrations, Paystack card processing, public invoice lookup, and transactional reconciliation guarantees implemented across the Eniceberny platform.
1. Architectural Strategy & Gateway Abstraction
Payment gateways in Eniceberny are designed using the Driver / Manager pattern, ensuring that adding new payment aggregators (e.g. Hubtel, Slydepay, ExpressPay) requires zero changes to core checkout or POS workflows:
sequenceDiagram
autonumber
actor Patron as Patron / Cashier
participant Store as Checkout / POS Controller
participant Manager as PaymentManager
participant Driver as PaymentGatewayDriver
participant Gateway as Paystack / MoMo Network
participant DB as Relational Database
Patron->>Store: Submit Order & Choose Payment Method
Store->>Manager: driver($method)
Manager->>Driver: initiatePayment($order, $payload)
alt Paystack / MoMo Redirect
Driver->>Gateway: Initialize Transaction API
Gateway-->>Driver: Return Auth URL / USSD Prompt
Driver-->>Store: Return Redirect / Instructions
Store-->>Patron: Forward to Payment Screen
Gateway-->>Driver: Webhook Event (charge.success)
Driver->>DB: Verify Signature & Mark Order 'paid'
else Physical Cash / Counter MoMo
Driver->>DB: Log Transaction 'pending' (or 'success' for POS)
DB-->>Store: Order Confirmed
Store-->>Patron: Display Receipt & QR Code
end
Gateway Contract:
All payment providers adhere strictly to App\Services\Payments\PaymentGatewayInterface:
namespace App\Services\Payments;
use App\Models\Order;
interface PaymentGatewayInterface
{
/**
* Initiate payment processing with the gateway.
*/
public function initiatePayment(Order $order, array $payload = []): array;
/**
* Query gateway to verify settlement status.
*/
public function verifyPayment(string $reference): array;
/**
* Process full or partial refund.
*/
public function refund(Order $order, ?float $amount = null, ?string $reason = null): bool;
}
2. Supported Payment Methods in Ghana
2.1 Cash on Delivery & Counter Cash (cash)
- Online Checkout: Customer chooses "Cash on Delivery" or "Pay upon In-Store Collection".
- Order is placed with
payment_status: 'pending'. - Upon delivery by the dispatch rider or patron pickup, staff marks the order paid via the Admin Order console.
- Order is placed with
- In-Store POS: Cashier tenders physical notes, calculates change, and completes the sale atomically with
payment_status: 'paid'.
2.2 Mobile Money Direct & USSD Push (momo)
Mobile Money is the primary consumer payment rail in Ghana:
- Networks Supported: MTN Mobile Money, Telecel Cash, AT Money.
- Storefront Checkout:
- The patron enters their 10-digit Ghanaian mobile number.
- The system records a unique payment reference (
EB-MOMO-XXXXXX) and instructs the patron to authorize the USSD prompt on their handset. - Verification occurs either automatically via network callback or manually by entering the telecom transaction ID.
2.3 Paystack Cards & Digital Wallets (paystack)
- Payment Instruments: Visa, Mastercard, Verve, and instant Paystack Mobile Money.
- Server-Side Security: Orders are never marked paid simply because a user's browser redirected to a success URL. All settlements require cryptographic verification.
3. Webhook Architecture & Cryptographic Signature Verification
Paystack dispatches automated HTTP POST notifications when transactions are completed.
Webhook Verification Workflow:
- Paystack sends a POST request to
/api/v1/payments/webhookwith the headerX-Paystack-Signature. - The middleware computes the HMAC SHA512 hash of the raw incoming request body using the configured secret key:
$computedSignature = hash_hmac('sha512', $request->getContent(), config('services.paystack.secret_key')); if (!hash_equals($computedSignature, $request->header('X-Paystack-Signature', ''))) { Log::warning('Paystack webhook rejected: Invalid signature'); return response()->json(['error' => 'Invalid signature'], 401); } - Idempotency Guarantee: If the webhook references an order that is already marked
paid, the system acknowledges HTTP 200 without executing duplicate inventory or notification side-effects. - Order Status Progression:
- Order
payment_statusupdates topaid. PaymentTransactionrecord created with provider reference and fee breakdown.- An immutable audit trail entry is logged.
- Order confirmation email with attached PDF receipt is automatically dispatched.
- Order
4. Public Invoice Lookup & Balance Settlement (/invoices)
For corporate catering clients, custom cake orders, and telephone bookings, the platform provides a self-service public invoice lookup portal:
- Lookup URL:
GET /invoices - Search Parameters: Patrons can look up any order or proposal using either:
- Official Order / Invoice Number (e.g.
EB-20260927-1402) - Customer Telephone Number (e.g.
0532342126or+233532342126)
- Official Order / Invoice Number (e.g.
- Detailed Invoice View (
/invoices/{order_number}):- Displays business branding crest, hotlines, and address.
- Shows line items, itemized rates, delivery fees, and discounts.
- Real-time payment status badge (
PAID,PARTIALLY PAID,UNPAID). - Single-click "Pay Online Now" button launching instant Paystack checkout for outstanding balances.
- Bank and Mobile Money remittance instructions for direct offline bank wire transfers.
5. Automated PDF Invoicing & QR Code Engine
Receipts and invoices can be downloaded or emailed as high-resolution PDF documents:
Technical Stack:
- DomPDF (
barryvdh/laravel-dompdf): Renders HTML/Blade templates into crisp PDF documents. - Standalone QR Code Generator (
chillerlan/php-qrcode): Generates offline base64 PNG QR code data streams embedded directly into the PDF:$qrCodeData = (new QRCode)->render(route('orders.receipt', $order->order_number));
Automated Dispatch:
- An automated PDF receipt (
Receipt-EB-*.pdf) is attached to:OrderPlacedReceiptMail: Dispatched immediately upon order placement.OrderStatusUpdatedMail: Dispatched whenever an order status transitions tocompletedordelivered.
- Store managers can also dispatch receipts on demand via the "Send Email Receipt (PDF Attached)" action in
/admin/orders/{id}.
6. Financial Integrity & Decimal Precision
To eliminate floating-point rounding errors and ensure compliance with accounting standards:
- Database Types: All monetary columns (
orders.subtotal,orders.delivery_fee,orders.tax_amount,orders.discount_amount,orders.total,payment_transactions.amount) useDECIMAL(10, 2). - Display Helper: Amounts are formatted consistently across templates using
format_currency($amount)(e.g.GH₵ 125.00) withfont-sans font-bold tabular-numsto ensure scannable visual alignment. - Database Transactions: All financial modifications are enclosed within
DB::transaction()to guarantee atomicity.