Simpaisa vs Open Banking UK: Gap Analysis & Recommendations¶
Date: 2026-04-03 Baseline: Open Banking UK Read/Write API v4.0 Subject: Simpaisa GitBook API Docs (Pakistan Pay-Ins, Pay-Outs, Remittances, Cards) Markets: Pakistan, Bangladesh, Nepal Reviewer: CDO Review Status: Updated with source code findings (supersedes spec-only assessment of 2026-04-01)
Note on this revision: The original gap analysis (2026-04-01) was written from GitBook API documentation only. This revision incorporates findings from a full source code audit (Codebase-Audit.md, 2026-04-02) covering Wallet, Cardbackend, Remittance, and Disbursement repos. In almost every dimension, the actual codebase gap is significantly worse than the spec-only assessment suggested. Severity ratings have been adjusted accordingly.
Why Open Banking UK as the Baseline¶
Open Banking UK is the most mature, well-documented payment API standard globally. It was built for regulated financial services with real money movement, not just data access. The patterns it establishes for authentication, error handling, idempotency, signing, pagination, and lifecycle management are directly applicable to Simpaisa's use case.
That said, Simpaisa operates in South Asian markets (Pakistan, Bangladesh, Nepal) where the payment rails are fundamentally different from UK banking. Mobile wallets (Easypaisa, JazzCash, bKash) are more dominant than bank accounts. OTP-based authentication is standard. Real-time settlement through RAAST or 1Link IBFT differs from Faster Payments. The comparison must be adapted, not copied blindly.
The goal: adopt Open Banking's design patterns and conventions while keeping Simpaisa's domain-specific flows (wallet OTP, tokenisation, multi-channel routing, cross-border FX).
Gap Analysis: 15 Dimensions¶
1. Authentication & Authorisation¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Protocol | OAuth 2.0 + OIDC with FAPI profile | No Spring Security in any service. No authentication filter chain, no CSRF protection, no security headers. Auth is ad-hoc: merchant ID from request body (wallet), client-id header lookup (card), or nothing at all (remittance consumer). |
CRITICAL — WORSE THAN ASSESSED | P0 |
| Token management | Bearer tokens with expires_in, refresh tokens with expiry claims |
No token management. No OAuth. No bearer tokens anywhere in code. | CRITICAL | P0 |
| Client auth | Client Credentials Grant for app-level, Auth Code for user-level | merchantId in body/path. Some endpoints completely unauthenticated — Safepay /fetch-details and /redirection have zero auth checks (C-03). IP security filter exists in Card but is disabled/commented out (C-04). Disbursement IP filter has a logic bug reading getRemoteAddr() instead of X-Forwarded-For (D-03). |
CRITICAL — WORSE THAN ASSESSED | P0 |
| Consent | Intent-based consent model (consent resources, PSU authorisation) | Not applicable (B2B merchant API, not consumer-facing) | N/A | — |
| CORS | Not applicable (server-to-server) | @CrossOrigin(origins = "*") on all controllers in Wallet and Card — wide open to browser-based abuse (CX-02) |
CRITICAL | P0 |
Assessment change: The spec audit rated authentication as CRITICAL/P1. Code reality is P0 — immediate security risk. There is literally no security framework in place. The gap is not "different auth mechanism" — it is "no auth mechanism" on multiple endpoints.
Recommendation for Simpaisa:
Full OAuth 2.0 with FAPI is overkill for a B2B merchant API. But the current state (no Spring Security anywhere, unauthenticated endpoints, disabled IP filters) requires emergency remediation before any standards alignment.
Immediate (before Open Banking alignment):
- Add spring-boot-starter-security to all services
- Implement SecurityFilterChain with API key authentication as minimum
- Authenticate all endpoints — close the unauthenticated Safepay endpoints (C-03)
- Fix the disbursement IP filter to read X-Forwarded-For (D-03)
- Enable the Card IP security filter (C-04)
- Remove @CrossOrigin(origins = "*") from all controllers
Then adopt: - OAuth 2.0 Client Credentials Grant for obtaining access tokens (merchantId + secret -> bearer token) - RSA request signing standardised across ALL products - Mutual TLS standardised across ALL products
Skip: - OIDC, FAPI profile, consent model (these are for consumer-facing open banking, not B2B) - Authorisation Code Grant (no PSU redirect needed in B2B)
Tailoring for South Asia: - Keep OTP-based payment authorisation for wallet Pay-Ins (this IS the consent mechanism for mobile wallets in Pakistan) - Document OTP as the equivalent of Strong Customer Authentication (SCA) in the South Asian context
2. Error Response Schema¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Structure | {"Errors":[{"ErrorCode":"...","Message":"...","Path":"...","Url":"..."}]} |
No @ControllerAdvice or @ExceptionHandler in any service (CX-09). Each controller has its own try-catch with inconsistent behaviour — some return 200 OK with empty body, some return 401, some return 201. No standard error schema exists in code. |
CRITICAL — WORSE THAN ASSESSED | P0 |
| Exception handling | Structured error propagation | Exceptions silently swallowed. Controller catch blocks catch Exception, call e.printStackTrace(), and return whatever partial state exists with HTTP 200 OK (W-05). Remittance financial methods use finally { return response; } which suppresses all exceptions (R-07). Callers cannot distinguish success from failure. |
CRITICAL — WORSE THAN ASSESSED | P0 |
| Error codes | ISO 20022 reason codes + OBL proprietary codes | Custom codes per product (0000-9999). Card service's validatePaymentBody() is a no-op — returns empty error set, validates nothing (C-06). Inverted validation booleans in Card — isValidEmail() returns true for invalid emails (C-02). |
CRITICAL | P0 |
| Field path | Path field pointing to the problematic request field |
Not present. No typed DTOs exist to even reference field paths. | HIGH | P2 |
| Documentation link | Url field linking to error documentation |
Not present | MEDIUM | P3 |
Assessment change: The spec audit rated error handling as CRITICAL/P1. Code reality is P0. The problem is not just "inconsistent format" — it is that errors are actively hidden from callers. A payment can fail and the caller receives HTTP 200 with an empty or partial response.
Recommendation for Simpaisa:
Before adopting Open Banking error formats, the codebase needs fundamental error handling:
Immediate:
- Add @ControllerAdvice with @ExceptionHandler in every service
- Remove all finally { return } anti-patterns in financial methods (R-07)
- Replace e.printStackTrace() with logger.error() across all services (CX-03, 40+ instances in Remittance alone)
- Fix inverted validation booleans in Card (C-02)
- Implement actual validation in Card's validatePaymentBody() (C-06)
Then adopt the Open Banking error array pattern:
{
"errors": [
{
"code": "INVALID_ACCOUNT",
"message": "Beneficiary account number is invalid",
"path": "request.beneficiaryAccountNo",
"reference": "https://simpaisa.gitbook.io/simpaisa-docs/errors/INVALID_ACCOUNT"
}
],
"traceId": "uuid-for-support-debugging"
}
Tailoring:
- Use Simpaisa's existing numeric codes (0001, 0008, etc.) as the code field, but add a human-readable constant alongside: {"code": "0008", "name": "INVALID_ACCOUNT", ...}
- Add traceId (not in Open Banking but essential for cross-border debugging across multiple payment rails)
- The path field maps directly and should be adopted as-is
3. HTTP Status Codes¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Success | 200 OK, 201 Created, 204 No Content | Confirmed: returns 200 for everything including failures. Controller catch blocks return HTTP 200 with empty body on exception (W-05). | CRITICAL — CONFIRMED | P0 |
| Client errors | 400, 401, 403, 405, 406, 415 | Confirmed: 200 with error code (or empty body). No validation framework to produce 400 errors — HashMap<String, Object> request bodies have no Bean Validation. Card's validation is a no-op (C-06). |
CRITICAL — CONFIRMED | P0 |
| Rate limiting | 429 with Retry-After header |
Confirmed: no rate limiting exists in any service (CX-06). No Bucket4j, no Spring Cloud Gateway rate limiter, no custom implementation. | CRITICAL | P1 |
| Server errors | 500, 503 (also for deprecated versions) | Exceptions caught and swallowed — 500 never returned even when server fails. | CRITICAL | P0 |
| 400 vs 404 | 400 for missing resource IDs, 404 for undefined endpoints | No differentiation. Everything is 200. | CRITICAL | P1 |
Assessment change: The spec audit suspected "200 for everything" and rated it CRITICAL/P1. Code confirms this and reveals it is even worse — exceptions produce 200 with empty/partial bodies, making it impossible for clients to detect failures.
Recommendation for Simpaisa:
Open Banking's HTTP status code usage should be adopted directly, but this requires the @ControllerAdvice and typed DTO work first. Without typed request objects, the framework cannot generate 400 responses automatically.
Adopt immediately (once @ControllerAdvice is in place):
- 201 for successful resource creation (payment initiation)
- 200 for successful queries
- 400 for validation errors
- 401 for auth failures
- 429 for rate limiting with Retry-After
- 500 for server errors
Tailoring: None needed. HTTP status codes are universal.
4. Idempotency¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Header | x-idempotency-key (max 40 chars) |
Request-Id (UUID, 36 chars) on Wallet via @IdempotentAnnonation aspect. Optional — if header is absent, request proceeds without deduplication (W-04). |
HIGH | P1 |
| Scope | POST requests only, 24-hour window | Wallet verify call only. No idempotency in Remittance Kafka consumer (R-09) — with at-least-once delivery, duplicates possible. No idempotency in Disbursement. | CRITICAL | P0 |
| Failure mode | Deterministic (idempotency always enforced) | Fails open. If Redis is down, catch block (line 83-86) falls through to pjp.proceed() — idempotency silently disabled, duplicate payments can be created (W-04). |
CRITICAL — WORSE THAN ASSESSED | P0 |
| Body verification | MAY verify body unchanged via signature | Not implemented | MEDIUM | P2 |
| Timing | TPP must wait >= 1 second before retry | Not documented or enforced | LOW | P3 |
Assessment change: The spec audit rated idempotency as "not documented" / CRITICAL/P1. Code reveals it partially exists but fails open — a Redis outage silently disables duplicate payment protection. This is a financial integrity risk.
Recommendation for Simpaisa:
Immediate: - Make idempotency mandatory on all payment-initiating endpoints (fail closed, not open) - Fix the Redis failure path — if idempotency store is unavailable, reject the request (don't process it) - Add idempotency check to Remittance Kafka consumer before processing (R-09) - Add row-level locking on Disbursement scheduler fetch to prevent duplicate processing (D-09)
Then adopt:
- Rename Request-Id to x-idempotency-key for industry alignment
- Keep UUID format (Simpaisa's 36-char UUID is within Open Banking's 40-char limit)
- Extend to all POST endpoints across all products
- 24-hour deduplication window
5. Request/Response Signing¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Algorithm | PS256 (RSA-PSS with SHA-256) via detached JWS | RSA 2048-bit in some services. But Remittance uses RSA/ECB/PKCS1Padding which is vulnerable to Bleichenbacher padding oracle attacks (R-11, D-08). |
HIGH — WORSE THAN ASSESSED | P1 |
| Format | Detached JWS in x-jws-signature header |
signature field in request/response body. Signatures logged to stdout — System.out.println("request signature = " + signature) in Wallet (W-07), leaking cryptographic material in production. |
CRITICAL | P0 |
| Non-repudiation | Full JOSE header with issuer, timestamp, trust anchor | Basic RSA signature with no metadata | MEDIUM | P2 |
| Pay-Ins | Signed (per spec) | NO signing at all on Wallet endpoints | HIGH | P1 |
| Key management | Trust anchor chain, certificate rotation | Private keys committed to source control — PEM files in src/main/resources/ (W-02). Hardcoded RSA public key in Remittance (R-02). Card fetches private key from AWS Secrets Manager per request with no caching (C-07). |
CRITICAL | P0 |
Assessment change: The spec audit rated signing as MEDIUM/P2. Code reveals critical key management failures — private keys in Git, signatures in stdout, vulnerable padding modes.
Recommendation for Simpaisa:
Immediate (before standards alignment): - Remove PEM private keys from source control and Git history (W-02) — use BFG Repo-Cleaner - Stop logging signatures to stdout (W-07) - Migrate from PKCS1 v1.5 padding to OAEP (R-11, D-08) - Cache private keys from AWS Secrets Manager instead of fetching per request (C-07)
Then adopt:
- Move signature from request body to x-jws-signature header (cleaner separation of data and auth)
- Add signing to Pay-Ins (currently unsigned, which is a security gap)
- Manage all keys via HashiCorp Vault (already in some services)
Defer: - Full JOSE header with trust anchor chain (overkill for B2B, useful when Simpaisa opens to third-party TPPs) - PS256 algorithm migration (Simpaisa's SHA-256 RSA is acceptable once padding is fixed)
6. Pagination¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Mechanism | HATEOAS links: Links.Self, Links.Next, Links.Prev |
Unknown from code audit. No evidence of pagination in the controllers reviewed. HashMap<String, Object> responses make structured pagination unlikely. |
HIGH | P2 |
| Page size | Min 25, max 1000 per page | Not implemented | HIGH | P2 |
| Metadata | Meta.TotalPages |
Not present | MEDIUM | P3 |
Assessment change: Unchanged — pagination was undocumented in specs and unconfirmed in code.
Recommendation for Simpaisa:
Adopt the HATEOAS link pattern for pagination. It's self-documenting and doesn't require merchants to construct URLs.
{
"data": [...],
"links": {
"self": "/merchants/123/disbursements?page=2",
"next": "/merchants/123/disbursements?page=3",
"prev": "/merchants/123/disbursements?page=1"
},
"meta": {
"totalPages": 47,
"totalCount": 1150
}
}
Tailoring: For high-volume transaction lists (disbursements, remittances), use cursor-based pagination instead of page numbers. This performs better on the MySQL databases Simpaisa uses. The Disbursement scheduler currently fetches up to 1,000 records with a simple SELECT ... WHERE state = 'published' LIMIT 1000 with no locking (D-09) — any pagination implementation must also address row-level locking.
7. Versioning¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Format | /v[major].[minor]/ in URL path |
Confirmed: no versioning anywhere (CX-05). Wallet uses /wallets/transaction/*, Card uses /payments, /capture, Remittance uses /kafka-send. No URL versioning, no header versioning, no content negotiation. |
CRITICAL — CONFIRMED | P1 |
| Deprecation | 503 for deprecated versions | Not possible (no versions exist) | HIGH | P2 |
| Migration | payload-version header for schema migration |
Not present | MEDIUM | P3 |
Assessment change: The spec audit noted inconsistent versioning (/v2/ for Pay-Ins, version: 3.0 header elsewhere). Code confirms no versioning exists at all — those version indicators were in the GitBook docs only, not implemented in code.
Recommendation for Simpaisa:
Adopt URL path versioning: /v1/, /v2/. Drop the version header approach (harder to route, harder to cache). This must be implemented alongside the URI restructuring (see Dimension 10).
Tailoring: Use major versions only (/v1/, /v2/). Open Banking's v4.0 minor versioning adds complexity Simpaisa doesn't need yet.
8. Correlation & Tracing¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Correlation ID | x-fapi-interaction-id (UUID, echoed in response) |
Confirmed: not present (CX-10). No MDC-based request tracing, no X-Request-Id propagation into log context, no Spring Cloud Sleuth or Micrometer Tracing. |
CRITICAL — CONFIRMED | P1 |
| Request tracing | Interaction ID links request -> response -> webhook | Not present. Logging uses e.printStackTrace() to stdout (CX-03), bypassing Log4j2 appenders entirely. In containerised environments, these stack traces are lost. |
CRITICAL — WORSE THAN ASSESSED | P0 |
Assessment change: The spec audit rated this HIGH/P1. Code confirms it and reveals the logging infrastructure is fundamentally broken — e.printStackTrace() in every catch block across all services means distributed tracing cannot work even if correlation IDs were added.
Recommendation for Simpaisa:
Immediate (before correlation IDs):
- Replace all e.printStackTrace() with logger.error("Context message", e) across all services (40+ instances in Remittance alone)
- Stop logging signatures, PII, and full request/response bodies at INFO level (R-12, W-07)
Then adopt:
- x-request-id header (simpler naming than FAPI)
- Echo it in every response and every webhook payload
- Propagate into MDC for structured logging
- Store in OpenSearch for tracing
9. Payment Lifecycle States¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Standard | ISO 20022 PaymentStatusCode (13 states) | Custom states per product (6 for Pay-Outs, 9 for Remittances). Disbursement has an enum bug — MONTHLY_LIMIT_EXCEEDED message says "Daily-limit-exceeded" (D-13). |
MEDIUM | P2 |
| Naming | RCVD, PDNG, ACTC, ACSP, ACSC, RJCT, etc. | published, in_review, on_hold, stuck, disbursed, rejected, etc. | MEDIUM | P2 |
| Transitions | Documented in spec | Listed but no transition diagram. Code reveals unrecoverable partial states — Remittance has no @Transactional on financial operations (R-05), so balance deductions can succeed while state updates fail, creating orphaned states that require manual intervention. |
CRITICAL — WORSE THAN ASSESSED | P0 |
Assessment change: The spec audit rated lifecycle states as MEDIUM/P2. Code reveals a P0 issue — the absence of database transactions means the state machine can reach states that are not defined in the model and cannot be recovered automatically.
Recommendation for Simpaisa:
Immediate:
- Add @Transactional to all financial operations (R-05) — this is a prerequisite for any reliable state machine
- Fix the enum naming bug (D-13)
Then adopt:
- State transition diagrams (Open Banking documents which transitions are valid)
- StatusUpdateDateTime field on every resource (when did the state last change?)
- Webhook notifications for ALL state changes (Simpaisa's Remittances only sends postbacks for 4 of 9 states)
- Don't rename states to ISO 20022 codes — Simpaisa's human-readable names are better for developer experience
Tailoring: Map Simpaisa states to ISO 20022 equivalents in documentation for banks/regulators who need it, but keep the human-readable names as primary.
10. Resource URI Structure¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Pattern | /open-banking/v4.0/pisp/domestic-payments/{id} |
Confirmed: 3+ different patterns. Wallet: /wallets/transaction/*, Card: /payments, /capture, Remittance: /kafka-send. No consistent hierarchy, no versioning prefix. |
HIGH | P2 |
| Hierarchy | resource-group / resource / resource-id / sub-resource | Flat (no sub-resources). Remittance endpoint is literally /kafka-send — implementation detail exposed as API contract. |
HIGH | P2 |
Recommendation for Simpaisa:
Adopt a consistent pattern across all products:
/api/v1/payin/{merchantId}/transactions/{action}
/api/v1/payout/{merchantId}/disbursements/{action}
/api/v1/remittance/{merchantId}/transfers/{action}
/api/v1/cards/{merchantId}/payments/{action}
Tailoring: Use product names that match Simpaisa's domain (payin, payout, remittance) not Open Banking's (pisp, aisp, cbpii).
11. Webhook / Event Notification¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Format | JWS-signed event notifications | Unsigned JSON POST — confirmed in code. No HMAC, no JWS, no signature of any kind on webhook payloads (W-06). | CRITICAL | P0 |
| Retry | Documented policy | No retry. MerchantPostbackService sends a single POST request. If the merchant's server is down, the postback is lost. RabbitMQ queue constants exist (POSTBACK_QUEUE, POSTBACK_EXCHANGE) suggesting a queue-based retry was planned but never implemented (W-06). |
CRITICAL — CONFIRMED | P0 |
| Verification | Signature verification by recipient | Not possible (unsigned) | CRITICAL | P0 |
| Event types | Typed event resources | Inferred from payload content | HIGH | P2 |
| Dead letter | Failed events tracked and retryable | Kafka dead letter topic handler is empty — when all retries fail, remittance messages are silently dropped (R-08). | CRITICAL | P0 |
Assessment change: The spec audit rated webhooks as CRITICAL/P1. Code confirms and reveals additional severity — not only are webhooks unsigned and unretried, but the Kafka error handler silently drops failed messages.
Recommendation for Simpaisa:
Immediate: - Implement Kafka Dead Letter Topic — failed messages must not be silently dropped (R-08) - Add webhook retry with exponential backoff (3 retries at 1min, 5min, 30min)
Then adopt:
- HMAC-SHA256 signature on webhook payloads
- X-Simpaisa-Signature header with HMAC of raw body using shared secret
- Event type field: "event": "payment.completed", "event": "disbursement.rejected"
Tailoring: HMAC-SHA256 instead of JWS (simpler for merchants to implement, sufficient for B2B webhooks).
12. Date/Time Formats¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Format | ISO-8601 with timezone required | Mixed formats (2023-06-08 16:09:56.0 in callbacks). Remittance uses DecimalFormat("#.##") to truncate amounts (R-06) and double for money — date/time handling likely equally inconsistent. |
MEDIUM | P2 |
| Timezone | Mandatory in all payloads | Not specified | MEDIUM | P2 |
Recommendation: Adopt ISO-8601 with timezone: 2023-06-08T16:09:56+05:00 (PKT). The current format without timezone and with .0 suffix is non-standard.
13. Supplementary / Extensibility¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Extension model | SupplementaryData empty object, ASPSP-defined |
No extension model. However, HashMap<String, Object> everywhere (CX-04) means the entire request/response is effectively untyped and extensible — but in the worst possible way (no schema, no validation, no documentation). |
MEDIUM | P3 |
Recommendation: The HashMap<String, Object> pattern must be replaced with typed DTOs first (CX-04). Once typed models exist, add a metadata object for market-specific fields. For example, Pakistan requires CNIC for certain transactions, Bangladesh might require different KYC fields. A metadata object keeps the core schema clean while allowing regional extensions.
14. Security (TLS/mTLS/Encryption)¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| TLS version | TLS 1.2 mutual authentication | SSL certificate validation disabled in Remittance — 7 separate methods create TrustManager accepting all certificates with NoopHostnameVerifier (R-01). This enables man-in-the-middle attacks on partner bank connections (Bank of Asia, Faysal Bank, Trust Bank, 1Link). |
CRITICAL — CATASTROPHIC | P0 |
| Certificate | X.509 with RSA >= 2048-bit, SHA-2 signed | Private keys committed to Git (W-02, D-01). Hardcoded credentials in source (W-01, R-02, D-01). Disbursement SSL keystore password is changeit (D-01). |
CRITICAL — WORSE THAN ASSESSED | P0 |
| mTLS | Mandatory | Only Disbursement gateway (and even that runs on EOL Spring Boot 2.2.6 / Java 8 with Netflix Zuul). Not present in Wallet, Card, or Remittance. | CRITICAL | P0 |
| Encryption | AES-256 or equivalent | AES-ECB mode across Wallet, Card, and Disbursement (W-03, C-01, D-07). ECB is deterministic — identical plaintext blocks produce identical ciphertext. For card payment PAN data, this is a PCI-DSS blocker. Disbursement has a separate EncryptionUtils.java that correctly uses AES/CBC, but the insecure utility coexists and may still be called. |
CRITICAL — PCI BLOCKER | P0 |
| Hashing | SHA-256+ | Remittance uses MD5 for security tokens (R-03): token = MD5(date + time + SECRET_KEY + stan). MD5 is cryptographically broken. |
CRITICAL | P0 |
| PAN handling | Tokenised references, minimal PCI scope | Card service handles raw PAN through application layer — String cardNumber and String cvv as plain parameters (C-05). Card data stored in Redis with 15-minute TTL without confirmed TLS or ACL. Significantly increases PCI-DSS scope. |
CRITICAL — PCI BLOCKER | P0 |
Assessment change: The spec audit rated security as MEDIUM-HIGH. Code reveals multiple P0 security vulnerabilities: disabled SSL, AES-ECB on card data, MD5 for auth tokens, private keys in Git, raw PAN handling, and hardcoded credentials. This dimension alone has more P0 findings than the entire original assessment.
Immediate actions: 1. Fix SSL certificate validation — remove all trust-all patterns in Remittance (R-01) 2. Replace AES-ECB with AES-GCM across all services (W-03, C-01, D-07) 3. Replace MD5 token generation with HMAC-SHA256 (R-03) 4. Rotate and remove all hardcoded credentials from source and Git history (W-01, R-02, D-01) 5. Remove PEM private keys from repos (W-02) 6. Review PAN handling for PCI-DSS compliance (C-05)
Recommendation: Standardise mTLS across all products. Migrate Disbursement gateway to Spring Boot 3.x / Spring Cloud Gateway (D-05).
15. Settlement & Reconciliation¶
| Dimension | Open Banking UK | Simpaisa Current (Code Reality) | Gap | Priority |
|---|---|---|---|---|
| Expected settlement | ExpectedSettlementDateTime in response |
Not present. Additionally, settlement operations lack database transactions (R-05) — a settlement record can be inserted while the balance update fails. | CRITICAL — WORSE THAN ASSESSED | P0 |
| Expected execution | ExpectedExecutionDateTime in response |
Not present | HIGH | P1 |
| Charges | Charges array in payment response |
Not present | MEDIUM | P2 |
| Refund reference | Refund object with account details |
Refund API incomplete | HIGH | P1 |
| Money precision | Decimal precision in all financial fields | double used for money throughout Remittance (R-06). DecimalFormat("#.##") truncates to 2 decimal places, silently losing precision. Settlement discrepancies are inevitable. |
CRITICAL | P0 |
| Concurrent processing | Safe concurrent access to settlement data | No row-level locking on Disbursement fetch (D-09) — multiple scheduler instances process the same disbursements simultaneously. Retry scheduler only supports Bank of Asia, defaulting all other partners to wrong bank's API (R-10). | CRITICAL | P0 |
Assessment change: The spec audit rated settlement as HIGH/P1. Code reveals P0 financial integrity risks — floating-point money, no database transactions on settlement operations, no row-level locking, and a retry scheduler that routes failed transactions to the wrong partner bank.
Immediate:
- Migrate all monetary fields to BigDecimal with explicit rounding modes (R-06)
- Add @Transactional to settlement operations (R-05)
- Add SELECT ... FOR UPDATE SKIP LOCKED to disbursement scheduler (D-09)
- Fix retry scheduler partner routing (R-10)
Then adopt:
- expectedExecutionDateTime — when the payment will be submitted to the rail
- expectedSettlementDateTime — when the beneficiary will receive funds
- charges array — breakdown of fees applied
Tailoring for South Asia: - Settlement timing varies wildly: Easypaisa is near-instant, 1Link IBFT is real-time, bank transfers may be T+1 - Document per-channel settlement expectations - For cross-border remittances: include both sending and receiving settlement times
Summary: What to Adopt, Adapt, and Skip¶
Adopt Directly (universal best practices)¶
- Standard HTTP status codes (200, 201, 400, 401, 429, 500)
x-idempotency-keyheader on all POST endpoints (24-hour window, fail closed)- Error array format with code, message, path, reference URL
x-request-idcorrelation header (echoed in responses and webhooks)- ISO-8601 with timezone for all date/time fields
- HATEOAS pagination links with metadata
- URL path versioning (
/v1/,/v2/) Retry-Afterheader on 429 responses- Webhook signature verification (HMAC-SHA256)
expectedSettlementDateTimein payment responses
Adopt — But Requires Foundational Remediation First (code reality blockers)¶
These Open Banking patterns cannot be implemented until the underlying codebase issues are resolved:
| Open Banking Pattern | Blocker in Code | Remediation Required First |
|---|---|---|
| OAuth 2.0 Client Credentials | No Spring Security in any service (CX-01) | Add spring-boot-starter-security, implement SecurityFilterChain |
| Standard error schema | Exceptions silently swallowed, 200 OK on failure (W-05, R-07, CX-09) | Add @ControllerAdvice, remove finally { return }, fix inverted validators |
| Request/response signing | Private keys in Git, signatures in stdout (W-02, W-07) | Purge keys from history, stop logging signatures, fix RSA padding |
| Typed API contracts (OpenAPI) | HashMap<String, Object> everywhere (CX-04) |
Introduce typed DTOs with Bean Validation |
| Idempotency enforcement | Fails open on Redis failure (W-04) | Make mandatory, fail closed |
| Settlement timing | double for money, no @Transactional (R-05, R-06) |
Migrate to BigDecimal, add transaction management |
| mTLS everywhere | SSL validation disabled in Remittance (R-01), gateway on EOL stack (D-05) | Fix trust-all patterns, upgrade gateway |
Adapt for South Asian Markets¶
- Auth: OAuth 2.0 Client Credentials (not full FAPI/OIDC, B2B not consumer)
- Signing: RSA or JWS (not PS256 mandated, keep RSA-SHA256 compatibility for regional banks — but fix PKCS1 padding first)
- Payment states: Keep human-readable names, map to ISO 20022 in docs for regulators
- Consent: OTP-based wallet authorisation IS the SCA equivalent, document it as such
- URI structure: Use Simpaisa product names (payin/payout/remittance), not OB group names
- Metadata extension: Add
metadataobject for market-specific fields (CNIC for Pakistan, NID for Bangladesh) - Settlement timing: Document per-channel (Easypaisa vs 1Link vs bKash) since it varies by rail
- Webhook events: Include channel-specific events (OTP sent, wallet approval pending, AML review)
Skip (not applicable to B2B merchant API)¶
- OIDC / Authorisation Code Grant / Hybrid flow (consumer-facing, not B2B)
- Consent resource model (PSU authorisation via redirect, not needed)
- CIBA (backchannel auth for consumer banking)
- FAPI profile (regulatory requirement for UK open banking, not South Asian markets)
- ISO 20022 status codes as primary identifiers (keep as secondary mapping)
- JWE encryption (mTLS + RSA signing is sufficient for B2B)
- Trust anchor chain in JWS headers (needed for multi-party open banking, not single-provider)
- 503 for deprecated versions (Simpaisa should use proper deprecation headers instead)
Implementation Roadmap (Revised — Aligned with Code Reality)¶
Timeline adjustment: The original roadmap (2026-04-01) assumed a starting point of "inconsistent but functional APIs." The code audit reveals the starting point is fundamentally insecure and unreliable. A new Phase 0 (emergency security remediation) is required before any Open Banking alignment work can begin. Overall timeline extends from 16 weeks to 28–32 weeks.
Phase 0: Emergency Security Remediation (Weeks 1–2)¶
Goal: Close active security vulnerabilities. No Open Banking alignment — just stop the bleeding.
This phase maps directly to the Codebase Audit Phase 0 remediation items.
| # | Action | Source Finding | Services |
|---|---|---|---|
| 1 | Rotate all hardcoded credentials (Twilio, partner APIs, MySQL, SMTP, JazzCash, Easypaisa, 1Link) | W-01, R-02, D-01 | All |
| 2 | Remove private keys from Git history (BFG Repo-Cleaner) | W-02 | Wallet |
| 3 | Fix SSL certificate validation — remove all trust-all patterns | R-01 | Remittance |
| 4 | Fix inverted validation booleans | C-02 | Card |
| 5 | Authenticate Safepay endpoints | C-03 | Card |
| 6 | Enable IP security filter / fix IP filter X-Forwarded-For bug | C-04, D-03 | Card, Disbursement |
| 7 | Remove chromedriver binaries and Selenium code | D-02 | Disbursement |
| 8 | Purge all credentials from Git history | All credential findings | All |
Phase 1: Security Hardening (Weeks 3–8)¶
Goal: Establish baseline security that Open Banking patterns can be built upon.
| # | Action | OB Dimension | Source Finding | Services |
|---|---|---|---|---|
| 9 | Replace AES-ECB with AES-GCM | §14 Security | W-03, C-01, D-07 | Wallet, Card, Disbursement |
| 10 | Add Spring Security with API key auth | §1 Auth | CX-01 | All |
| 11 | Remove @CrossOrigin(origins="*") |
§1 Auth | CX-02 | Wallet, Card |
| 12 | Replace MD5 token generation with HMAC-SHA256 | §14 Security | R-03 | Remittance |
| 13 | Add @Transactional to all financial operations |
§15 Settlement | R-05 | Remittance |
| 14 | Migrate monetary fields to BigDecimal |
§15 Settlement | R-06 | Remittance |
| 15 | Upgrade RSA padding to OAEP | §5 Signing | R-11, D-08 | Remittance, Disbursement |
| 16 | Fix no-op payment validation | §2 Errors | C-06 | Card |
| 17 | Stop logging signatures to stdout | §5 Signing | W-07 | Wallet |
| 18 | Add row-level locking on disbursement fetch | §15 Settlement | D-09 | Disbursement |
| 19 | Fix retry scheduler partner routing | §15 Settlement | R-10 | Remittance |
| 20 | Upgrade disbursement gateway to Spring Boot 3.x | §14 Security | D-05 | Disbursement |
Phase 2: Error Handling & Reliability (Weeks 9–14)¶
Goal: Establish the error handling and reliability contracts that Open Banking requires.
| # | Action | OB Dimension | Source Finding | Services |
|---|---|---|---|---|
| 21 | Add @ControllerAdvice global exception handler |
§2 Errors, §3 HTTP Status | CX-09 | All |
| 22 | Replace e.printStackTrace() with structured logging |
§8 Tracing | CX-03 | All |
| 23 | Implement standard error response schema (OB error array) | §2 Errors | — | All |
| 24 | Implement proper HTTP status codes | §3 HTTP Status | — | All |
| 25 | Make idempotency mandatory, fail closed | §4 Idempotency | W-04, R-09 | Wallet, Remittance |
| 26 | Add Kafka Dead Letter Topic | §11 Webhooks | R-08 | Remittance |
| 27 | Add webhook retry with exponential backoff | §11 Webhooks | W-06 | Wallet |
| 28 | Add webhook HMAC-SHA256 signing | §11 Webhooks | — | All |
| 29 | Add correlation IDs / distributed tracing | §8 Tracing | CX-10 | All |
| 30 | Remove finally { return } anti-pattern |
§2 Errors | R-07 | Remittance |
| 31 | Mask PII in logs | §8 Tracing | R-12 | Remittance, Disbursement |
Phase 3: API Contract & Developer Experience (Weeks 15–22)¶
Goal: Establish the typed, versioned, documented API contracts that Open Banking mandates.
| # | Action | OB Dimension | Source Finding | Services |
|---|---|---|---|---|
| 32 | Introduce typed DTOs replacing HashMap<String, Object> |
§2 Errors, §13 Extensibility | CX-04 | All |
| 33 | Add Bean Validation on typed DTOs | §2 Errors | CX-04 | All |
| 34 | Add API versioning /api/v1/ |
§7 Versioning | CX-05 | All |
| 35 | Restructure URIs to consistent pattern | §10 Resource URIs | — | All |
| 36 | Add springdoc-openapi for auto-generated documentation |
§13 Extensibility | CX-07 | All |
| 37 | Add HATEOAS pagination links | §6 Pagination | — | All |
| 38 | Add rate limiting with Retry-After |
§3 HTTP Status | CX-06 | All |
| 39 | Standardise date/time to ISO-8601 with timezone | §12 Date/Time | — | All |
| 40 | Add settlement timing fields to payment responses | §15 Settlement | — | All |
| 41 | Add state transition diagrams and webhook mapping | §9 Lifecycle | — | All |
| 42 | Add Flyway database migrations | — | CX-08 | All |
Phase 4: OAuth & Signing Standards (Weeks 23–28)¶
Goal: Implement the authentication and signing patterns aligned with Open Banking.
| # | Action | OB Dimension | Services |
|---|---|---|---|
| 43 | Implement OAuth 2.0 Client Credentials Grant | §1 Auth | All |
| 44 | Standardise RSA request signing across all products | §5 Signing | All |
| 45 | Move signature from body to x-jws-signature header |
§5 Signing | All |
| 46 | Standardise mTLS across all products | §14 Security | All |
| 47 | Add metadata object for market-specific extensions |
§13 Extensibility | All |
| 48 | Add unit and integration test suites | — | All |
| 49 | Consolidate scheduler forks into single repo | — | Disbursement |
| 50 | Implement SurrealDB migration plan | — | All |
Phase 5: Regional Expansion & Sandbox (Weeks 29–32)¶
Goal: Ensure the patterns work across all markets.
| # | Action | OB Dimension | Services |
|---|---|---|---|
| 51 | Bangladesh/Nepal standards audit | — | All |
| 52 | Per-market settlement documentation | §15 Settlement | All |
| 53 | Sandbox test scenarios per channel | — | All |
| 54 | Reconciliation API (transaction search, daily reports) | §15 Settlement | All |
| 55 | Per-market metadata fields (CNIC, NID, etc.) | §13 Extensibility | All |
Appendix: Open Banking Pattern Cheat Sheet for Simpaisa Engineers¶
| Pattern | Open Banking Way | Simpaisa Adaptation | Code Reality Blocker |
|---|---|---|---|
| Auth | OAuth 2.0 + FAPI + mTLS | OAuth 2.0 Client Credentials + RSA signing + mTLS | No Spring Security anywhere (CX-01) |
| Error format | {"Errors":[{"ErrorCode","Message","Path","Url"}]} |
{"errors":[{"code","name","message","path","reference"}],"traceId"} |
Exceptions swallowed, 200 OK on failure (W-05, R-07) |
| Idempotency | x-idempotency-key header, 40 chars, 24h |
Same, UUID format, fail closed | Fails open on Redis failure (W-04) |
| Correlation | x-fapi-interaction-id UUID |
x-request-id UUID |
No tracing, e.printStackTrace() everywhere (CX-10, CX-03) |
| Signing | Detached JWS with PS256 | RSA-SHA256 in x-signature header (upgrade path to JWS) |
Keys in Git, signatures in stdout, PKCS1 padding (W-02, W-07, R-11) |
| Pagination | HATEOAS Links.Self/Next/Prev, Meta.TotalPages |
Same pattern, cursor-based for large sets | HashMap responses, no typed models (CX-04) |
| Versioning | /v[major].[minor]/ in path |
/v[major]/ in path (simpler) |
No versioning exists at all (CX-05) |
| Dates | ISO-8601 with timezone | Same | Mixed formats, no standardisation |
| Webhooks | JWS-signed event notifications | HMAC-SHA256 signed, typed events | No signing, no retry, Kafka DLT empty (W-06, R-08) |
| States | ISO 20022 codes (RCVD, ACSP, RJCT) | Human-readable (published, disbursed, rejected) with ISO mapping | No @Transactional — orphan states possible (R-05) |
| Settlement | ExpectedSettlementDateTime in response |
Same, per-channel | double for money, no row locking (R-06, D-09) |
| Extension | SupplementaryData empty object |
metadata object for regional fields |
Everything is HashMap already — need types first (CX-04) |
| TLS/Encryption | TLS 1.2+ mandatory, AES-256 | Same | AES-ECB, SSL disabled, MD5 tokens (W-03, R-01, R-03) |