FOR UPDATE SKIP LOCKED/JetStream acks).tenant_id; ABAC at API; mTLS service-to-service.cps-service — Control Plane (Tenancy, Identity, RBAC/ABAC)POST /v1/tenants (create)POST /v1/users (create; msisdn/email)POST /v1/users/{id}/credentials/pin (set/rotate; Argon2id)POST /v1/roles, POST /v1/policies, POST /v1/api-keysPOST /v1/auth/token (for merchant/service-accounts; OIDC-compatible)tenant.created, identity.user.created, policy.updatedtenant(tenant_id, name, base_currency, created_at)user(user_id, tenant_id, msisdn, email, status) with RLScredential(user_id, type, secret_hash, salt, status)role, policy(subject, resource, action, condition_jsonb)api_key(id, tenant_id, subject_id, scopes[], secret_hash, status)Idempotency-Key. Token issuance has strict replay-prevention (nonce tied to subject+aud).kyc-compliance-service — KYC & AMLPOST /v1/kyc/profiles (start; INDIVIDUAL/BUSINESS)PATCH /v1/kyc/profiles/{id} (submit docs/answers)POST /v1/kyc/profiles/{id}:verify (async checks; webhooks to self on provider callback)GET /v1/kyc/profiles/{id} (status, risk score)kyc.profile.verified|rejected|updated.kyc_profile(profile_id, tenant_id, subject_id, type, status, risk_score, current_version)kyc_profile_version(profile_id, version, form_jsonb, documents_jsonb, status)aml_screening(profile_id, provider, result_jsonb, risk_score)catalog-pricing-service — Products, Tariffs, Fees, TaxesPOST /v1/catalog/tariffs (+rules)GET /v1/quotations?product&amount¤cy&walletTier&channel (returns fee/tax/commission breakdown)pricing.tariff.updated.tariff(tariff_id, product, currency, effective_from,to)tariff_rule(tariff_id, min,max, fee_type[%|fixed], amount, pct, cap, channel, tier)commission_rule(tariff_id, agent_tier, pct, floor, cap)limits-risk-service — Limits & Velocity + Risk DecisionsPOST /v1/limits (define per tier/actor)POST /v1/risk/check (sync pre-auth decision: ALLOW/DENY/CHALLENGE)limit_policy(subject_type, scope, window, max_count, max_amount)risk_counter:key windows (sliding) replicated to PG snapshots.ledger-service — System of RecordPOST /v1/ledger/journals, POST /v1/ledger/journals/{id}:reverse,POST /v1/periods/{yyyy-mm}:close, GET /v1/accounts/{code}/balances, GET /v1/journals/{id}ledger.posted, ledger.period.closed.account, journal_entry, journal_line, account_balance, gl_period, idempotency_key, command_outbox.sp_post_journal_batch validates, posts, updates balances, maintains hash chain.Idempotency-Key; outbox semantics for downstream.wallet-service — Wallets, Holds, StatementsPOST /v1/wallets, GET /v1/wallets/{id}, POST /v1/wallets/{id}/hold, POST /v1/wallets/{id}/release,GET /v1/wallets/{id}/statement?from&to&cursor, POST /v1/wallets/{id}/lock|unlockwallet.created|locked|hold.created|released.wallet(wallet_id, owner_type, owner_id, currency, tier, status),wallet_account_map(wallet_id, ledger_account_id, purpose),wallet_hold(hold_id, wallet_id, amount_minor, reason, expires_at, status).orchestrator-service — Payment Switch / State MachinesPOST /v1/payments (init), GET /v1/payments/{id} (status), POST /v1/payments/{id}:cancel, POST /v1/payments/{id}:capturepayment.created|authorized|captured|failed|cancelled.payment(payment_id, product, channel, payer_wallet_id, payee_wallet_id, amount, fees, currency, state, idem_key, source_ref), payment_event.PENDING → AUTHORIZING (risk+pricing) → AWAITING_APPROVAL (STK) → AUTHORIZED → CAPTURING → CAPTUREDFAILED; before capture: CANCELLED.c2b-stk-service — C2B & STK PushPOST /v1/c2b/initiate (merchant → platform), GET /v1/c2b/{id}, POST /v1/stk/callbacks/operator (operator → us)stk.prompt.sent, stk.approved|declined; consumes payment.created to initiate STK.c2b_request(req_id, merchant_id, order_ref, msisdn, amount, currency, status, operator_txn_id, callback_url), stk_push(push_id, req_id, msisdn, prompt_status, approval_code, operator_payload).merchant_id+order_ref; callback replay safe (operator_txn_id).p2p-service — Person-to-PersonPOST /v1/p2p/transfers (fromWalletId, toWalletId|msisdn, amount, narration), GET /v1/p2p/transfers/{id}p2p.authorized|captured|failed.p2p_transfer(transfer_id, from_wallet_id, to_wallet_id, amount_minor, fees_minor, state).b2c-service — DisbursementsPOST /v1/b2c/payouts (batch upload+validate), GET /v1/b2c/payouts/{id}b2c.payout.created|completed; item-level b2c.item.completed|failed.b2c_batch(batch_id, merchant_wallet_id, count, totals, state), b2c_item(item_id, dest_wallet_id|msisdn, amount_minor, fees_minor, state).b2b-service — Business-to-BusinessPOST /v1/b2b/payments, GET /v1/b2b/payments/{id}b2b_payment(id, from_wallet_id, to_wallet_id, amount, currency, state).agent-ops-service — Cash-In / Cash-Out / FloatPOST /v1/agent/cashin, POST /v1/agent/cashout, GET /v1/agent/float, POST /v1/agent/commission/settleagent(agent_id, float_wallet_id, status, location), agent_txn(txn_id, agent_id, customer_wallet_id, type, amount, fees, state).settlement-service — Merchant SettlementPOST /v1/settlement/runs?merchant&from&to, GET /v1/settlement/batches/{id},POST /v1/settlement/batches/{id}:dispatch, POST /v1/settlement/batches/{id}:confirmsettlement.batch.created|dispatched|completed.settlement_batch(batch_id, merchant_id, period_from,to, gross, fees, net, reserve, state), settlement_item, settlement_instruction(bank, amount, currency, status, bank_ref).recon-service — ReconciliationPOST /v1/recon/import (source, format, S3 url), GET /v1/recon/breaks?status, POST /v1/recon/breaks/{id}:resolverecon.break.created|resolved.recon_file(file_id, source, meta, imported_at), recon_row(row_id, file_id, value_date, amount, currency, ref, match_key, state), recon_break(break_id, type, our_ref, ext_ref, amount_delta, status).disputes-service — Disputes & ChargebacksPOST /v1/disputes (open), PATCH /v1/disputes/{id} (state transitions), GET /v1/disputes/{id}dispute.opened|evidence.required|resolved|reversed.dispute(id, payment_id, opened_by, reason_code, state, deadlines), dispute_evidence(id, dispute_id, doc_url, hash, uploaded_by).notifications-service — Multichannel MessagingPOST /v1/notify, POST /v1/templates, GET /v1/logs/{id}message_template(id, channel, locale, body), message_log(id, to, channel, template_id, payload, state, provider_ref).payment.*, settlement.*, etc.; publishes notify.sent|failed.webhooks-service — Partner WebhooksPOST /v1/webhooks, POST /v1/webhooks/test, GET /v1/webhooks/{id}webhook_endpoint(id, tenant_id, url, topics[], secret, status), webhook_delivery(id, endpoint_id, topic, payload, attempt, state).sha256=...) → exponential backoff → dead-letter → operator tooling to replay.payment.created|authorized|captured|failed|cancelled (orchestrator)stk.prompt.sent|approved|declined (c2b-stk)ledger.posted|period.closed (ledger)wallet.hold.created|released (wallet)settlement.batch.created|dispatched|completed (settlement)recon.break.created|resolved (recon)dispute.opened|resolved (disputes)notify.sent|failed (notifications)tenantId, traceId, schemaVersion, idempotencyKey, sourceService.ΣD == ΣC per entry/day/tenant/currency; hash chain continuity.pg_partman where needed (ledger).ledger-service, wallet-service, cps-service, catalog-pricing-service, limits-risk-service.