Platform Feature Report

ECG Payment & Recharge
Orchestrator

A microservices-based electricity prepaid and postpaid payment platform built for the Electricity Company of Ghana. Vendor requests flow through a public REST gateway into durable Wolverine saga workflows coordinating six backend services.

EcgPaymentsCore · ECG Ghana
May 2026
16 features
6 services

Platform Overview

The platform exposes a vendor-facing REST API and orchestrates multi-step payment workflows via Wolverine sagas backed by Marten/PostgreSQL and RabbitMQ. Sagas provide durable, compensatable workflows — if any step fails, the saga can roll back or retry safely without duplicating side-effects.

External Integrations
Apache Fineract
Core financial ledger — float accounts, fund transfers, GL entries
SUBS Enterprise API
Meter lookups — prepaid search and postpaid bill retrieval
8 Meter Vendor APIs
Electricity token generation per meter category (MMS, NURI, HOLLEY…)
Azure Blob Storage
Persistent storage for generated PDF invoices and receipts
Arkesel
SMS delivery to end customers after vend completion
Brevo (SMTP)
Transactional email to vendors for invoices and receipts

Services

The platform is decomposed into six microservices. Api.Gateway is the only publicly accessible service; all others communicate internally via durable RabbitMQ messaging.

ServicePortRole
Api.Gateway 32780 / 32781 Public REST API — sole external entry point. Runs the pre-transaction guard chain and dispatches saga commands via RabbitMQ.
Services.Orchestrator 32775 / 32776 Long-running Wolverine saga coordinator. Manages all multi-step payment workflows with durable inbox/outbox guarantees against Marten/PostgreSQL.
Services.Tokens Meter recharge and electricity token generation. Routes to the correct vendor API per MeterCategoryCode; tracks per-category failure counts.
Services.Billing Invoice lifecycle management, top-up and settlement invoice creation, and scheduled daily settlement via an hourly TickerQ cron job.
Services.Browser Headless browser rendering of HTML templates into PDF documents. Uploads output to Azure Blob Storage and returns the resulting URL.
Services.Notifications Consumes seven RabbitMQ queues to deliver SMS (Arkesel), email (Brevo), and HTTP webhook callbacks to customers and vendors.

System Architecture

The gateway is the only publicly reachable service. All mutation operations are dispatched to the orchestrator via RabbitMQ commands, which then fans out to the appropriate leaf services. Read-heavy endpoints are served directly from the gateway using a two-tier Redis cache.

Service topology
ECG Payment & Recharge Orchestrator service topology Hierarchy: Vendor Application at the top; Api.Gateway below; Services.Orchestrator in the middle; four leaf services (Tokens, Billing, Browser, Notifications) at the bottom. Vendor Application HTTP REST Api.Gateway Port 32780 · Auth · Guard chain · Redis cache RabbitMQ Services.Orchestrator Port 32775 · Wolverine sagas · Marten/PostgreSQL Services.Tokens Token generation Services.Billing Invoices & settlements Services.Browser PDF generation Services.Notifications SMS · Email · Webhooks External / Vendor Platform service Internal (RabbitMQ)

Features

1 Prepaid Meter Vending

Accepts a vend request from a vendor, validates it against the pre-transaction guard chain, then dispatches a VendPrepaidSaga. The saga transfers funds from the vendor's Fineract float account to the meter wallet, calls the correct meter vendor API to generate an electricity token, and delivers an SMS to the customer plus a webhook callback to the vendor.

HTTP Entry Point
POST/api/v1/meters/vend
Involved Services
Api.Gateway Services.Orchestrator Services.Tokens Services.Notifications
VendPrepaidSaga — Workflow
8-step saga · VendPrepaidSaga
VendPrepaidSaga 8-step workflow Sequential saga: Record Payment, Transfer Funds, Authorize Payment, Record GL Entry, Complete Payment, Generate Token, Send SMS, Send Webhook. 1 Record Payment Persist payment record with amount, channel, and metadata 2 Transfer Funds Debit vendor float · Credit meter wallet via Fineract API 3 Authorize Payment Lock funds and mark payment as authorized in Fineract 4 Record GL Entry Write double-entry general ledger journal to Fineract 5 Complete Payment Mark payment as fully settled and close the financial step 6 Generate Token Call meter vendor API · Store generated electricity token 7 Send SMS to Customer Deliver token and confirmation message via Arkesel 8 Send Webhook to Vendor POST vend result payload to vendor-configured callback URL
Payment method support: An optional PaymentData block accepts channel (cash or mobile-money), provider, accountNumber, accountName, and referenceId. All fields are persisted in the payment record's JSONB Extra column.

2 Postpaid Meter Vending

Extends the vend flow for postpaid meters. Before any funds are moved, the VendPostpaidSaga first queries the SUBS Postpaid API to validate the meter and retrieve the outstanding bill. After payment is complete, a postpaid bill credit is recorded and postpaid-specific notifications are sent, followed by an additional GL entry for the notification cost.

HTTP Entry Point
POST/api/v1/meters/vend

Same endpoint as prepaid — routing is determined by the meter's category code.

Involved Services
Api.Gateway Services.Orchestrator Services.Notifications
VendPostpaidSaga — Workflow
11-step saga · VendPostpaidSaga
VendPostpaidSaga 11-step workflow Sequential saga starting with a SUBS meter search, then payment, postpaid bill recording, and notifications. 1 Search Meter (SUBS Postpaid API) Validate meter and retrieve outstanding postpaid bill 2 Record Payment Persist payment record with outstanding bill amount 3 Transfer Funds Debit vendor float · Credit meter wallet via Fineract 4 Authorize Payment Lock funds and mark payment as authorized 5 Record GL Entry Write general ledger journal entry to Fineract 6 Complete Payment Mark payment as fully settled 7 Record Postpaid Bill Persist the postpaid bill credit record against the meter 8 Send Postpaid Notification Deliver postpaid-specific confirmation notice to vendor 9 Send SMS to Customer Deliver token and confirmation message via Arkesel 10 Send Webhook to Vendor POST result payload to vendor callback URL 11 Record Notification GL Entry Write GL entry to account for notification delivery cost

3 Vend Status Lookup

Allows a vendor to query the outcome of a previously submitted vend using either the platform's internal reference or the vendor's own external reference. Returns the generated electricity token if vending succeeded. The endpoint response is output-cached per reference in Redis.

HTTP Entry Point
GET/api/v1/meters/vends/{reference}
Involved Services
Api.Gateway

4 Multi-Vendor Meter Recharge Routing

Routes token generation requests to the correct meter vendor API based on the meter's MeterCategoryCode. Each vendor has dedicated credentials, endpoint logic, and a failure count that is tracked and surfaced to detect service degradation.

Involved Services
Services.Tokens
Supported Meter Vendors
Category CodeVendor
MMSECG MMS Meter Layer API
NURINURI Meters
HOLLEYHOLLEY Meters
MBHMBH Meters
CLOUCLOU Meters
ECASH4ECASH4 Meters
IMESIMES Meters
ALPHAALPHA Meters

On repeated failures, the MeterCategoryFailureTrackingHandler increments the category's failure count, which is exposed through the meter category status endpoint for vendor visibility.

5 Meter Category Status Query

Returns the meter categories assigned to the authenticated vendor, along with the active or inactive status of each. This allows vendors to know in real time which meter types are available for vending. Responses are output-cached per vendor with a 2-minute TTL backed by Redis.

HTTP Entry Point
GET/api/v1/meters/categories/statuses
Involved Services
Api.Gateway

6 Transaction Reversal Ticketing

Vendors can raise a formal reversal ticket against a completed vend transaction. The ticket carries a typed reason, a description, and a status lifecycle managed by the platform. When an admin approves the ticket, a MeterVendReversalSaga executes a Fineract fund refund and GL correction. Vendors can also query existing tickets by transaction reference.

HTTP Entry Points
POST/api/v1/meters/reversals
GET/api/v1/meters/reversals/{reference}
GET/api/v1/meters/reversals/types
Involved Services
Api.Gateway Services.Orchestrator
Ticket Lifecycle
Submitted Under Review Approved or Rejected or Canceled
Reversal Reason Types
IDDescription
1Token not received
2Meter not recharged
3Payment processed but no delivery
4Duplicate charge
5Incorrect amount charged
6Other
Idempotency: The reversal submission endpoint accepts an x-idempotency-key header with a 48-hour replay window, preventing duplicate reversal tickets from being created for the same transaction.

7 Vendor Balance Enquiry

Returns the authenticated vendor's current available float balance, total Fineract account balance, account currency, and account status. Because balance data is fetched live from Fineract during the VendorChecker preprocessor and placed into the per-request state bag, no additional database round-trip is needed at the endpoint itself.

HTTP Entry Point
GET/api/v1/vendors/balance
Involved Services
Api.Gateway

8 Vendor Top-Up

Processes a vendor float account top-up initiated by an external payment (mobile money or bank transfer). The TopupSaga tracks both the base Amount and the commission-inclusive TotalAmount separately — the total is deposited into Fineract while the commission split is recorded in the GL entry. A top-up invoice PDF is generated and delivered by email and webhook.

Involved Services
Services.Orchestrator Services.Billing Services.Browser Services.Notifications
TopupSaga — Workflow
1
Initiate Top-Up Payment
2
Fund Fineract Account (TotalAmount deposited)
3
Record GL Entry
4
Mark Payment Complete
5
Generate Top-Up Invoice
6
Generate Invoice PDF via Browser service
7
Send Invoice Email to vendor
8
Send Webhook to vendor

9 Vendor Settlement

Moves funds from a vendor's Fineract float account to an external bank or mobile money account on a vendor-configured daily schedule. The BillingCronJobs runs an hourly TickerQ cron job (0 0 * * * *) that finds all vendors whose configured SettlementTime falls within the current ±5-minute window and triggers a settlement saga for each.

Involved Services
Services.Orchestrator Services.Billing Services.Browser Services.Notifications
SettlementSaga — Workflow
1
Initiate Settlement Payment
2
Withdraw from Fineract Account
3
Record GL Entry
4
Mark Payment Complete
5
Generate Settlement Invoice
6
Generate Invoice PDF via Browser service
7
Send Invoice Email to vendor
8
Send Webhook to vendor

10 Invoice Lifecycle Management

Manages the complete lifecycle of vendor invoices from creation through to verified payment or rejection. Every state change produces an audit entry. Two mark-as-paid modes are supported: a basic confirmation and an enhanced version (V2) that accepts proof-of-payment file attachments. On verification, a receipt PDF is generated and delivered; on rejection, a rejection notification is sent.

Involved Services
Services.Billing Services.Orchestrator Services.Browser Services.Notifications
HTTP Entry Points (Services.Billing)
MethodPathAction
POST/invoicesCreate a new invoice
POST/invoices/{id}/pdfAttach a generated PDF URL
POST/invoices/{id}/receiptAttach a receipt PDF URL
POST/invoices/{id}/mark-paidMark invoice as paid (basic)
POST/invoices/{id}/statusUpdate invoice verification status
POST/invoices/{id}/verifyVerify or reject a payment

Invoice types: settlement and topup. Email templates: invoice.html, payment-received.html, payment-rejected.html.

11 Document Generation (PDF & Screenshots)

Renders HTML templates or arbitrary HTML strings into PDF documents or screenshots using a headless browser. Output files are uploaded to Azure Blob Storage and the resulting URL is returned to the caller for storage or delivery.

HTTP Entry Points (Services.Browser)
MethodPathAction
POST/generate/pdfRender raw HTML to PDF
POST/generate/pdf/templateRender a named template to PDF
POST/generate/screenshot/templateRender a named template to screenshot
Available Templates
TemplateUsed for
topup_invoice.htmlVendor top-up invoice
settlement_invoice.htmlVendor settlement invoice
invoice-template.htmlGeneral vendor invoice
receipt-template.htmlPayment receipt
invoice_receipt.htmlCombined invoice + receipt

12 Multi-Channel Notifications

Delivers notifications via SMS, email (direct and template-based), and HTTP webhook callbacks. Supports both individual and bulk delivery modes. All channels are driven by dedicated RabbitMQ queues with Wolverine durable inbox/outbox guarantees, ensuring at-least-once delivery even across service restarts.

Involved Services
Services.Notifications
Queue Routing
QueueModeProvider
notifications-sms-queueSingle SMSArkesel (sms.arkesel.com)
notifications-bulk-sms-queueBulk SMSArkesel
notifications-email-queueSingle emailBrevo SMTP (smtp-relay.brevo.com:587)
notifications-email-template-queueTemplate-based emailBrevo SMTP
notifications-bulk-email-queueBulk emailBrevo SMTP
notifications-webhook-queueSingle webhookVendor callback URLs
notifications-bulk-webhook-queueBulk webhookVendor callback URLs

13 Vendor Authentication & Authorization

Dual-mode authentication at the gateway. API key authentication validates the key against an internal Auth API and issues a JWT bearing a VendorId claim. OAuth2/OIDC via Keycloak is also supported for JWT introspection.

Every protected endpoint runs the VendorChecker preprocessor, which hydrates the vendor's Fineract balance, status, business rules, and meter categories into a per-request state bag using a two-tier hybrid cache: a 10-second in-memory layer backed by a 20-second Redis layer. This eliminates per-request database round-trips while keeping data fresh.

Involved Services
Api.Gateway

14 Pre-Transaction Guard Chain

A sequenced pipeline of preprocessors executes before any vend or mutation endpoint. If any guard fails, a standardised error response is returned immediately — the handler is never reached. Guards run in order and each receives the full hydrated vendor state bag from the VendorChecker.

Involved Services
Api.Gateway
7-guard pre-transaction pipeline
Pre-transaction guard chain Sequential pipeline of 7 preprocessors that validate every vend request before the handler executes. 1 VendorChecker Vendor exists, is active, and Fineract data is hydrated into state bag 2 VendorIpAddressChecker Request IP must be in the vendor's configured IP whitelist 3 VendorFloatAvailabilityCheck Vendor float balance must be sufficient for the requested amount 4 VendorFinancialLimitChecker Transaction amount must not exceed the vendor's configured max limit 5 VendorInvoiceChecker No outstanding pending invoices exist (bypassed for Type A vendors) 6 VendorMeterCategoryChecker Meter category must be assigned to and active for this vendor 7 ValidationErrorProcessor Maps FluentValidation errors to a standardized ApiResponse shape

15 Idempotency & Output Caching

Prevents duplicate mutations and avoids redundant computation on reads. Mutation endpoints that accept an x-idempotency-key header will replay the original response from Redis for any duplicate request within the configured window — the handler is never re-executed. Read endpoints use FastEndpoints output caching with per-vendor or per-reference cache keys backed by Redis.

Involved Services
Api.Gateway
Idempotency Windows
EndpointWindowKey
POST /api/v1/meters/vend Configured per deployment x-idempotency-key header
POST /api/v1/meters/reversals 48 hours x-idempotency-key header

16 Meter Lookup (Prepaid & Postpaid)

Resolves meter details before or during a vend, using different paths depending on meter type:

  • Prepaid — Performed at the gateway via the SUBS Enterprise API using HMAC-SHA256 signed requests, before dispatching the vend command to the orchestrator.
  • Postpaid — Performed inside the orchestrator saga as Step 1 of VendPostpaidSaga, via the SUBS Postpaid API, to validate the meter and retrieve the outstanding bill before any funds are moved.
Involved Services
Api.Gateway Services.Orchestrator

Service Responsibility Matrix

Which services are involved in each feature.

Feature Api.Gateway Orchestrator Tokens Billing Browser Notifications
Prepaid vend
Postpaid vend
Vend status lookup
Multi-vendor routing
Meter category status
Reversal ticketing
Vendor balance
Vendor top-up
Vendor settlement
Invoice lifecycle
Document generation
Multi-channel notifications
Vendor auth & authorization
Pre-transaction guards
Idempotency & caching
Meter lookup