Skip to content

Simpaisa GitBook API Documentation Audit

Date: 2026-04-03 Source: https://simpaisa.gitbook.io/simpaisa-docs Scope: Pakistan Pay-Ins, Pay-Outs, Remittances, Cards Reviewer: CDO Review Status: Updated with source code findings (Codebase Audit 2026-04-02)


Executive Summary

The GitBook docs are a real improvement over the internal SA docs. They're structured by country/product, have actual endpoint details, request/response examples, and even an idempotency page. The Cards section has proper encryption specs. Pay-Outs has RSA signing documentation with OpenSSL commands.

But there are still significant gaps. The docs are inconsistent across the three products (Pay-Ins is the most complete, Pay-Outs is middling, Remittances is thin on error handling). Authentication differs per product but this isn't called out. Several endpoints lack response schemas. And the payment-operations contracts that merchants actually care about (reconciliation, settlement timing, reversals) are barely there.

28 original findings. 5 Critical, 10 High, 9 Medium, 4 Low.

Since the original audit, a full source code review has been completed. The code reality is significantly worse than the documentation suggests. In many cases the docs promise capabilities the code does not deliver, and in other cases the code has serious vulnerabilities the docs fail to disclose. Each finding below now includes a Code vs Docs assessment where the source code contradicts or qualifies what the documentation claims.

What's improved since the SA docs: idempotency exists (Pay-Ins only), status code mappings exist, webhook callback samples exist, transaction states are documented, sandbox URLs are listed, RSA auth is documented for Pay-Outs and Remittances.

What's still broken: no standard error schema, inconsistent auth across products, no HTTP status codes, missing response schemas, no webhook retry/signature spec, no reconciliation docs.

What the code audit revealed is far worse: no Spring Security in any service, hardcoded production credentials in Git, disabled SSL on partner bank connections, AES-ECB encryption for card data, financial operations without database transactions, and Redis used everywhere despite documentation referencing SurrealDB.


Severity Definitions

Severity Meaning
CRITICAL Will cause production incidents, duplicate payments, security issues, or major integration failures
HIGH Causes significant merchant friction, increases support load, or violates API best practices
MEDIUM Inconsistency or missing detail that slows integration but has workarounds
LOW Style/convention issues worth fixing but not blocking

WHAT'S GOOD (credit where due)

Before the problems, what the GitBook docs get right:

  1. Idempotency exists for Pay-Ins. Request-Id UUID header on verify calls. This is ahead of most regional payment platforms.
  2. Status code mappings exist. Pay-Ins has 34 codes, Pay-Outs has 17+, Remittances has its own set. Merchants can parse errors.
  3. Transaction state machines exist. Disbursement has 6 states, Remittance has 9 states with enum values.
  4. RSA signing is documented. Pay-Outs and Remittances have proper RSA 2048-bit with PKCS8, SHA-256, including OpenSSL commands.
  5. Sandbox URLs are listed. Each product has sandbox vs production base URLs.
  6. Webhook callback samples. Pay-Ins has wallet, refund, and e-billing callback examples.
  7. Card encryption spec. AES encryption format, mutual TLS, PCI DSS requirement.
  8. Country-based organisation. Pakistan, Bangladesh, Nepal, Iraq, each with product sub-sections.

Finding 1: Authentication Differs Per Product, Not Documented as a Unified Strategy

Severity: CRITICAL Affects: All APIs

Three completely different auth mechanisms depending on which product:

Product Auth Mechanism Details
Pay-Ins Custom headers (mode, region, operatorId, version) + JSESSIONID cookie No signing, no encryption
Pay-Outs RSA 2048-bit signature + Mutual TLS (2-way SSL) Proper crypto, well-documented
Remittances RSA signature + headers (mode: remitance, region, version) Similar to Pay-Outs
Cards RSA signature + Mutual TLS + AES card encryption Most secure

A merchant integrating Pay-Ins AND Pay-Outs builds two completely different auth stacks. The Pay-Ins auth (custom headers + session cookie) is significantly weaker than Pay-Outs/Remittances/Cards.

What's missing: - No overview page explaining "here's how auth works across Simpaisa products" - No explanation of WHY Pay-Ins uses a different (weaker) mechanism - The JSESSIONID cookie in Pay-Ins curl examples is session-based auth, which is wrong for a stateless API - No API key provisioning documentation (how do merchants get credentials?)

Code vs Docs: The documentation overstates the security posture considerably. The code audit (CX-01) found no Spring Security in any service — not wallet, not card, not remittance. Authentication is handled ad-hoc: merchant ID lookup from the request body (wallet), a client-id header lookup (card), or nothing at all (remittance consumer). The RSA signing the docs describe for Pay-Outs and Remittances exists in code as signature validation utility classes, but there is no authentication filter chain, no CSRF protection, no security headers, and no authorisation framework. The Safepay card endpoints (C-03) have zero authentication — no client-id check, no signature, no IP whitelist. The IP security filter in the card service (C-04) is present but completely disabled — the validation logic is commented out. In short, the docs describe a security architecture that the code does not enforce.

Recommendation: - Create a unified "Authentication" page at the top level explaining all mechanisms - Consider upgrading Pay-Ins to RSA signing (consistency + security) - Remove JSESSIONID references from curl examples - Document credential provisioning flow (how to get merchantId, API keys, RSA keys) - NEW: Add Spring Security with a proper SecurityFilterChain before documenting any further auth claims


Finding 2: Idempotency Only Exists for Pay-Ins Verify, Not for Payment Initiation

Severity: CRITICAL Affects: Pay-Outs, Remittances, Pay-Ins (initiate)

The idempotency page is only for Pay-Ins and only on the verify call, not the initiate call. Pay-Outs and Remittances have no idempotency at all.

The reference field in Pay-Outs and Remittances could serve as an idempotency key (it's described as "unique reference"), but it's not documented as providing idempotent behaviour. Does a duplicate reference return the original response or create a 0055 "Reference-Already-Exists" error?

What goes wrong: A merchant calls POST /merchants/{id}/disbursements/initiate, gets a timeout, retries. If the system treated reference as an idempotency key, it would return the original response. If it returns error 0055, the merchant doesn't know if the first request succeeded or not.

Code vs Docs: The code reality is worse than the documentation gap suggests. The wallet's @IdempotentAnnonation aspect (W-04) fails open — if Redis is unavailable, the catch block (lines 83-86) falls through to pjp.proceed(), silently disabling idempotency and allowing duplicate payments. Furthermore, the Request-Id header is optional — if a merchant omits it, the request proceeds without any deduplication. The Kafka consumer for remittances (R-09) has no deduplication check at all; with Kafka's at-least-once delivery semantics, the same remittance can be processed twice. The docs present idempotency as a feature; the code treats it as a best-effort convenience that degrades silently.

Recommendation: - Extend idempotency to ALL payment-initiating endpoints across all 3 products - Document whether the reference field in Pay-Outs/Remittances provides idempotent behaviour - Add Request-Id header to Pay-Outs and Remittances initiate calls - Document behaviour clearly: same reference + same params = return original response (200), same reference + different params = error - NEW: Make Request-Id mandatory (not optional). Fix the fail-open pattern so Redis failure returns an error rather than silently proceeding.


Finding 3: No Standard Error Response Schema

Severity: CRITICAL Affects: All APIs

Each product returns errors differently:

Pay-Ins: {"status":"0006","message":"Channel-Rejected-Transaction",...} Pay-Outs: {"response":{"status":"0054","message":"Invalid-Signature","reference":"..."}} Remittances: {"response":{"status":"0000","message":"Success","reference":"..."}}

Pay-Outs and Remittances wrap in a response object. Pay-Ins returns flat. The status codes are different number ranges across products (Pay-Ins uses 0001-0095, Pay-Outs uses 0044-0119). None include a traceId for debugging.

Code vs Docs: The code makes this significantly worse. There is no @ControllerAdvice or @ExceptionHandler in any service (CX-09). Each controller has its own try-catch with wildly inconsistent behaviour — some return HTTP 200 OK with an empty body, some return 401, some return 201. The wallet controller (W-05) catches Exception, calls e.printStackTrace(), and returns whatever is in the response variable (potentially empty or partial) with HTTP 200. Callers literally cannot distinguish a successful payment from a server crash. The documented error codes exist in the code, but exceptions that occur before the error code is set result in silent 200 OK responses with no meaningful body.

Recommendation: - Define ONE error response schema across all products (see prior audit report for proposed format) - Include traceId in every response for support debugging - Publish a single consolidated error code reference - At minimum, document the structural differences so merchants integrating multiple products know what to expect - NEW: Add a @ControllerAdvice global exception handler to every service so that unhandled exceptions produce a proper error response, not a silent 200 OK.


Finding 4: No HTTP Status Codes Documented

Severity: CRITICAL Affects: All APIs

The Pay-Outs disbursement initiate endpoint shows 200 OK in the example. No other endpoint documents HTTP status codes. The status code mappings pages document Simpaisa's internal codes (0000, 0001, etc.) but not HTTP status codes.

Merchants don't know: does a validation error return HTTP 400 or 200 with a non-zero status? Does rate limiting return 429? Does auth failure return 401 or 200 with an error code?

Based on the examples, it appears Simpaisa returns HTTP 200 for everything (including errors) with the error indicated by the internal status code. This is a common pattern in older APIs but violates REST conventions and breaks standard HTTP error handling in client libraries.

Code vs Docs: Confirmed by source code. The wallet controller wraps everything in try-catch and returns 200 regardless of outcome (W-05). The card service similarly returns 200 on exceptions. There is no @ResponseStatus annotation usage, no ResponseEntity with proper HTTP codes. The code always returns 200 — including on authentication failures, validation errors, and unhandled exceptions. This is not merely undocumented; it is a design flaw that the documentation should either acknowledge honestly or the code should fix.

Recommendation: - Document HTTP status codes for every endpoint - If the current implementation returns 200 for all responses, document that explicitly so merchants don't waste time looking for 4xx/5xx - Consider migrating to proper HTTP status codes in a v2 (422 for validation errors, 401 for auth failures, 429 for rate limits)


Finding 5: Webhook Spec Missing Retry Policy and Signature Verification

Severity: CRITICAL Affects: All APIs

The webhook pages show sample callback payloads and state that HTTP 200 = success. But they don't document:

  1. Retry policy: What happens if the merchant endpoint returns 500? How many retries? What intervals?
  2. Signature verification: How does the merchant verify the callback is genuinely from Simpaisa (not spoofed)?
  3. Timeout: How long does Simpaisa wait for a 200 before marking delivery as failed? (Cards page mentions 40 minutes, but wallet webhooks don't specify)
  4. Event ordering: Can webhooks arrive out of order?
  5. Deduplication: Can the same webhook be delivered twice?

Pay-Outs mentions postbacks only fire for 4 of 9 remittance states. Which 4? (On Hold, Remitted, Rejected, Reversed). Merchants don't get notified for Published, In-Review, Stuck, AML Review, or In-Process. That's a gap.

Code vs Docs: The code confirms these gaps are real. The wallet's MerchantPostbackService (W-06) sends a single POST request with no retry. If the merchant's server is down, the postback is permanently lost. No exponential backoff, no dead letter queue, no webhook signature for authenticity. RabbitMQ queue constants exist (POSTBACK_QUEUE, POSTBACK_EXCHANGE) suggesting a queue-based retry approach was planned but never implemented. Similarly, the remittance Kafka consumer (R-08) has an empty exhaustion callback — when all retries fail, the message is silently dropped with no Dead Letter Topic configured. The docs imply a functioning webhook system; the code shows fire-and-forget with no reliability guarantees.

Recommendation: - Document retry policy (e.g., 3 retries at 1min, 5min, 30min) - Add HMAC signature to webhook payloads with verification docs - Document timeout per product - Document which states trigger webhooks vs which require polling - Provide a webhook testing tool (let merchants trigger test callbacks) - NEW: Implement the planned RabbitMQ-based retry mechanism. Add a Dead Letter Topic for Kafka-based postbacks.


Finding 6: Pay-Ins Response Schema Incomplete

Severity: HIGH Affects: Pay-Ins

The Easypaisa tokenisation initiate endpoint documents 6 response fields (status, message, msisdn, operatorId, merchantId, transactionId). The verify endpoint adds sourceId. But:

  • No documentation of what the response looks like on failure (just different status code? different fields?)
  • No documentation of the amount field in responses (is it echoed back?)
  • The callback sample includes createdTimestamp, updatedTimestamp, userKey, transactionType but the API response docs don't mention these

Code vs Docs: The wallet controller uses HashMap<String, Object> for all request and response bodies (CX-04). There are no typed DTOs anywhere, which means the response schema is determined at runtime by whatever the controller happens to put into the map. On exceptions, the response variable may be empty or partially populated. The documented 6-field response is a best-case scenario; failure responses are undefined because the code has no standard error structure.

Recommendation: - Document the full response schema for both success and error cases per endpoint - Document which fields are always present vs conditional - Align API response docs with callback payload docs (same fields should appear in both)


Finding 7: Pay-Outs Missing Response Schemas for Most Endpoints

Severity: HIGH Affects: Pay-Outs

Pay-Outs has 14 endpoint pages in the nav. The disbursement initiate page shows a response. But endpoints like "Fetching the customer details", "Fetching Bank List", "Fetching the balance data", "Getting the list of available disbursements" don't have response examples visible from the overview.

Recommendation: - Add full response schemas with examples for every Pay-Outs endpoint - Include pagination details for list endpoints (banks, disbursements)


Finding 8: Inconsistent URL Patterns Across Products

Severity: HIGH Affects: All APIs

Product URL Pattern Example
Pay-Ins /v2/wallets/transaction/{action} /v2/wallets/transaction/initiate
Pay-Outs /merchants/{merchantId}/disbursements/{action} /merchants/123/disbursements/initiate
Remittances /remittance/{merchantId}/{action} /remittance/123/remit-initiate
Cards Not fully specified in overview Appears to follow hosted page pattern

Three different patterns. merchantId in path for Pay-Outs and Remittances, in the request body for Pay-Ins. Version prefix (/v2/) only for Pay-Ins.

Code vs Docs: The code audit (CX-05) confirms there is no API versioning strategy at all. The wallet uses /wallets/transaction/* in the controller, the card service uses /payments and /capture, and the remittance consumer uses /kafka-send. The /v2/ prefix documented for Pay-Ins does not appear in the wallet controller code — it may be applied at a gateway level but this is not verifiable from the source. No content negotiation or header-based versioning exists.

Recommendation: - Document the differences explicitly on a "Getting Started" page - For new versions, standardise on one pattern - Consider: /v{version}/{product}/{merchantId}/{resource}/{action}


Finding 9: Different Base URLs Per Product

Severity: HIGH Affects: All APIs

Production base URLs: - Pay-Ins Wallets: wallets.simpaisa.com - Pay-Ins Cards: payment.simpaisa.com - Pay-Ins IBFT: ibft.simpaisa.com - Pay-Ins Hosted: widgetapi.simpaisa.com - Pay-Ins Refund: refund.simpaisa.com - Pay-Outs: disb.simpaisa.com - Remittances: remit.commerceplex.com (different domain entirely!)

7 different base URLs across products, and Remittances uses a completely different domain (commerceplex.com vs simpaisa.com).

What goes wrong: Merchants need to configure 7 different hostnames. Firewall whitelisting becomes complex. The commerceplex.com domain for remittances doesn't look like it belongs to Simpaisa, which erodes trust.

Code vs Docs: The disbursement gateway (D-05) runs on Spring Boot 2.2.6 with Java 8 (both end-of-life) using Netflix Zuul (maintenance mode). This gateway routes to the various backend services. The HBL Pay-Out path (D-06) is a pass-through proxy that forwards requests directly to https://paymentapi.hbl.com/OpenAPIRest with no authentication, transformation, rate limiting, or logging. The remittance code contains a hardcoded IP address (http://13.215.165.235:8080 in R-04) for SOAP API calls, bypassing DNS entirely. The infrastructure reality behind these documented URLs is fragile.

Recommendation: - Document all base URLs clearly in one table (partially done, but Remittances is separate) - Long-term: consolidate to api.simpaisa.com with path-based routing - At minimum, move Remittances to a simpaisa.com subdomain


Finding 10: Header Typo: mode: remitance (Missing 't')

Severity: HIGH Affects: Remittances

The Remittances header specifies mode: remitance (should be remittance). If this is the actual required value in the API, it's a permanent typo baked into the protocol. If it's a doc error, it will cause integration failures.

Recommendation: - Verify if the API actually requires remitance (one 't') or remittance (two 't's) - If the API requires the misspelled version, document it with a note: "Note: the value is remitance (intentional spelling)" - If it's a doc error, fix it


Finding 11: No Pagination Documentation for List Endpoints

Severity: HIGH Affects: Pay-Outs, Remittances

Pay-Outs has "Getting the list of available disbursements" and "Fetching Bank List". Remittances has "Fetching the Banks List". None document pagination parameters or limits.

Code vs Docs: The disbursement scheduler (D-09) fetches up to 1,000 published disbursements with a simple SELECT ... WHERE state = 'published' LIMIT 1000 and no SELECT ... FOR UPDATE SKIP LOCKED. The absence of pagination in the docs reflects the absence of pagination in the code — list endpoints likely return unbounded result sets or arbitrary limits that are not externally configurable.

Recommendation: - Add pagination params (page, limit) to all list endpoints - Document default and max page sizes - Include pagination metadata in responses (totalCount, totalPages, hasNext)


Finding 12: No Rate Limiting Documentation

Severity: HIGH Affects: All APIs

No mention of rate limits anywhere in the GitBook docs. Merchants don't know their request limits.

Code vs Docs: Confirmed — there is no rate limiting anywhere in the codebase (CX-06). No Bucket4j, no Spring Cloud Gateway rate limiter, no custom implementation. Combined with the lack of authentication (CX-01), these APIs are fully open to abuse. The docs cannot document rate limits because none exist in the code.

Recommendation: - Document rate limits per endpoint or per product - Return rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) - Document what happens when exceeded (HTTP 429 or Simpaisa status code?) - NEW: Implement rate limiting before documenting it — otherwise the docs would be aspirational fiction.


Finding 13: Cards Section References APIs Without Documenting Them

Severity: HIGH Affects: Cards

The Cards overview references 7 APIs: Payments, Inquiry, Capture, Void, Finalise, Refunds, Postbacks, plus Response Codes. But the overview page only describes the flow conceptually. The actual endpoint details (URLs, request/response schemas) are in sub-pages that may or may not exist.

Code vs Docs: The card service has additional undocumented problems. Payment body validation is a no-op (C-06) — the validatePaymentBody() method returns an empty error set and validates nothing. The isValidEmail() and isValidCountry() methods (C-02) have inverted boolean logic — they return true for invalid values and false for valid ones, meaning valid emails are rejected and invalid emails accepted. Merchants integrating the Cards API will encounter bizarre validation behaviour that contradicts any future field validation documentation.

Recommendation: - Ensure every referenced API has a dedicated endpoint page with full request/response schemas - Add curl examples for each Cards API endpoint - NEW: Fix the inverted validation booleans and no-op validation before publishing detailed field specifications.


Finding 14: Transaction Identity and Reconciliation Not Documented

Severity: HIGH Affects: All APIs

This is the gap Codex flagged and it's real. Merchants need to answer "where is my money?" and the docs don't help them:

  • Transaction ID mapping: Pay-Ins generates transactionId. Pay-Outs uses reference. Remittances uses reference. How do these map to each other if a merchant uses multiple products?
  • Reconciliation: No settlement reports, no daily reconciliation API, no export format
  • Settlement timing: When does money actually move? Same day? T+1? T+2?
  • Dispute handling: What happens when a beneficiary claims they didn't receive funds?

Code vs Docs: The code makes reconciliation even harder. Remittance financial operations (R-05) have no @Transactional annotations — balance deductions, state updates, and settlement insertions are not wrapped in database transactions. A balance deduction could succeed while the remittance state update fails, or a settlement record could be inserted while the balance update fails. This creates partial states that are unrecoverable without manual intervention. The remittance service also uses double for money (R-06), causing floating-point rounding errors that will create settlement discrepancies. DecimalFormat("#.##") is used to truncate amounts, silently losing precision. Any reconciliation documentation would need to acknowledge that the underlying financial data may be inconsistent.

Recommendation: - Add a "Reconciliation" section documenting how merchants match transactions across systems - Document settlement timing per channel (Easypaisa: real-time, 1Link: T+1, etc.) - Document the transaction ID lifecycle (what ID is generated when, how to look it up) - Add a transaction inquiry/search API if one doesn't exist - NEW: Add @Transactional to all financial operations and migrate monetary fields from double to BigDecimal before publishing reconciliation guarantees.


Finding 15: Inconsistent Request Body Wrapping

Severity: MEDIUM Affects: Pay-Outs, Remittances

Pay-Outs and Remittances wrap request bodies in a "request" object:

{"request": {"reference": "...", "amount": "..."}}

And responses in a "response" object:

{"response": {"status": "0000", "message": "Success"}}

Pay-Ins sends/receives flat JSON:

{"merchantId": "...", "amount": "..."}

Code vs Docs: All three services accept HashMap<String, Object> as request bodies (CX-04). There are no typed DTOs anywhere, no Bean Validation (@Valid, @NotNull, @Size), and field names are scattered as string constants. The wrapping convention the docs describe is implemented at runtime through manual map manipulation, not enforced by any schema.

Recommendation: - Document the wrapping convention per product clearly - For new versions, pick one approach (flat JSON is simpler and more standard)


Finding 16: OTP Expiry Not Documented in GitBook

Severity: MEDIUM Affects: Pay-Ins

The internal SA docs mentioned 30-minute OTP expiry. The GitBook docs don't mention OTP expiry at all. Merchants don't know how long the user has to enter the OTP.

Recommendation: - Document OTP expiry per wallet (may differ: Easypaisa vs JazzCash) - Document max OTP retry attempts - Reduce to 5 minutes if currently 30 (security best practice for payments)


Finding 17: Non-OTP Flow Lacks API Details

Severity: MEDIUM Affects: Pay-Ins

The Non-OTP Flow page is mostly conceptual. It mentions approval windows (Easypaisa: 60s, JazzCash: 360s) and references Verify API and Inquiry API sub-pages but the main page has no endpoint details.

Recommendation: - Add endpoint details directly on the Non-OTP Flow page or ensure sub-pages are complete - Document the async polling pattern: how often should merchants poll? What's the terminal state?


Finding 18: FX Rate Quote Validity Not Documented

Severity: MEDIUM Affects: Remittances

The FX Rate API returns rate, converted amount, fees, and a qDetails.iD for processing. But it doesn't document: - How long the quoted rate is valid - What happens if you use an expired quote ID - Whether the rate is locked on getFxRate or on confirmQuotation

Recommendation: - Document quote validity window (e.g., 30 seconds, 5 minutes) - Document error code for expired quotes - Document the full FX flow: getFxRate -> confirmQuotation -> initiate


Finding 19: Disbursement States Missing Transition Diagram

Severity: MEDIUM Affects: Pay-Outs

Six states are listed with descriptions but no transition diagram. Merchants can't tell: can a transaction go from "stuck" to "disbursed"? Can "on-hold" go back to "published"?

Code vs Docs: The disbursement scheduler has an enum bug (D-13): Responses.MONTHLY_LIMIT_EXCEEDED("0098", "Daily-limit-exceeded") — the enum name says "Monthly" but the message says "Daily". State transitions in the code may produce confusing error messages that don't match the documented state descriptions.

Recommendation: - Add an ASCII or visual state transition diagram:

published -> in_review -> on_hold -> published (after top-up)
                      \-> disbursed (success)
                      \-> rejected (failure)
          -> stuck -> rejected (after max retries)
- Document which transitions trigger webhooks


Finding 20: Remittance States Postback Coverage Incomplete

Severity: MEDIUM Affects: Remittances

Only 4 of 9 remittance states trigger postbacks (On Hold, Remitted, Rejected, Reversed). States like "Stuck", "AML Review", and "In Review" are silent. A merchant has no way to know their transaction is stuck unless they actively poll.

Code vs Docs: The remittance retry scheduler (R-10) only supports Bank of Asia. All other partners (Faysal Bank, Trust Bank, 1Link) default to the Bank of Asia channel, meaning retry attempts for those partners will send requests to the wrong bank's API. Stuck transactions for non-Bank-of-Asia partners may never recover correctly, making the silent "Stuck" state even more dangerous than the docs suggest.

Recommendation: - Document this clearly: "You will NOT receive webhooks for these states: in_process, published, in_review, stuck, aml_review" - Recommend polling interval for silent states - Consider adding postbacks for "stuck" and "aml_review" (merchants need to know)


Finding 21: Field Validation Rules Incomplete

Severity: MEDIUM Affects: All APIs

Some fields have lengths documented (merchantId: 07, msisdn: 10, reference: 45). But: - No regex patterns (what characters are allowed in reference?) - No min/max for amount (is 0.01 valid? Is 99999999 valid?) - currency is listed as "03 String" but no enum of allowed values - purpose is listed as "Int 4" but purpose codes are documented on a separate page, not linked

Code vs Docs: The code has essentially no input validation. The card service's validatePaymentBody() is a literal no-op (C-06) — it returns an empty error set. All services accept HashMap<String, Object> with no Bean Validation annotations (CX-04). The validation rules documented in GitBook may not actually be enforced by the code. Merchants could send invalid data and receive unpredictable behaviour rather than the documented error codes.

Recommendation: - Add regex patterns for string fields - Add min/max for numeric fields - Link to enum value pages from the field descriptions - Document which fields are echoed back in responses - NEW: Implement the documented validation rules in code before publishing them. Documenting validations the code does not enforce is misleading.


Finding 22: Sandbox Has No Test Scenarios

Severity: MEDIUM Affects: All APIs

Sandbox URLs exist but there's no documentation of: - Test merchant credentials - Test account numbers that simulate specific outcomes (success, failure, timeout) - Whether sandbox actually hits real wallet providers or uses stubs

Recommendation: - Provide test credentials in the docs - Create test accounts: "Use msisdn 3000000001 for success, 3000000002 for insufficient balance, 3000000003 for timeout" - Document sandbox limitations (e.g., "sandbox does not simulate real OTP delivery")


Finding 23: No API Versioning Strategy

Severity: MEDIUM Affects: All APIs

Pay-Ins has /v2/ in the URL and a version: 3.0 header. Pay-Outs and Remittances have version: 3.0 header only. No documentation of what changed between versions or how versioning works.

Code vs Docs: Confirmed — no URL versioning, no header versioning, no content negotiation exists in any service codebase (CX-05). Any breaking change affects all consumers simultaneously. The /v2/ prefix and version: 3.0 header documented in GitBook may be handled at a gateway level, but the underlying services have no awareness of API versions.

Recommendation: - Document the versioning strategy (URL path, header, or both?) - Publish a changelog of version differences - Document deprecation policy


Finding 24: Pay-Outs "States of Disbursment" Typo

Severity: LOW Affects: Pay-Outs

The page title is "States of Disbursment" (missing 'e'). Also "their low balance" should be "there's low balance" in the On-Hold description.

Code vs Docs: The typo pattern extends to the codebase — the disbursement scheduler repo is named disbusrment-scheduler (transposed 'r' and 'e'). The code and docs share the same misspelling culture.

Recommendation: Fix typos. Small thing, but it signals quality to integrating merchants.


Finding 25: No Content Negotiation Documentation

Severity: LOW Affects: All APIs

Headers specify Accept: text/plain, application/json, application/*+json. It's unclear if the API actually supports text/plain responses or if this is just a default. No documentation of what happens if you send Accept: application/xml.

Recommendation: - Simplify headers to Accept: application/json if that's the only supported format - Document that JSON is the only supported content type


Finding 26: Bangladesh, Nepal, Iraq Docs Not Assessed

Severity: LOW Affects: Other regions

This audit focused on Pakistan. Bangladesh, Nepal, and Iraq sections exist in the nav but were not reviewed.

Recommendation: - Apply the same audit criteria to all regions - Ensure consistency across countries (same error schemas, same auth, same patterns)


Finding 27: Refund API Incomplete Documentation

Severity: HIGH Affects: Pay-Ins

The Refund page lists status codes and base URLs but doesn't document: - The actual endpoint path (what URL do you POST to?) - Request body fields (transactionId? amount? partial refund fields?) - Whether partial refunds are supported via API (mentioned on the overview but not in the API spec) - Refund response schema - How long after the original transaction a refund can be requested

Recommendation: - Add full endpoint documentation: URL, HTTP method, request/response schemas, curl example - Document refund time window - Document partial vs full refund parameters


Finding 28: 1 Bill Service and Hosted Page Not Assessed in Detail

Severity: LOW Affects: Pay-Ins

These sections exist in the nav but were not deeply reviewed. They may have the same patterns of gaps found in the wallet and card flows.

Recommendation: - Apply the same audit criteria to 1 Bill Service and Hosted Page sections


Documentation Gaps Revealed by Code Audit

The following issues were discovered in the source code audit and are not mentioned anywhere in the GitBook documentation. These represent risks that merchants and partners should be aware of, or capabilities that require urgent remediation before they could be safely documented.

Gap 1: Hardcoded Production Credentials in Source Control

Severity: P0 — Should NOT be documented publicly, must be remediated

The codebase contains live production credentials committed to Git across multiple repositories:

  • Wallet: Twilio account SID, auth token, and phone number in Utility.java
  • Wallet: PEM private keys (hbl_konnect_prv_pcks8.pem, private_key_pkcs8.pem) in src/main/resources/
  • Remittance: Partner API username (ComPlex_API), password, and system ID in RSAEncryptor.java
  • Disbursement: MySQL credentials, SMTP password, JazzCash/Easypaisa/1Link client credentials, and SSL keystore password (changeit) in application-prd.properties
  • Disbursement: Hardcoded Easypaisa MSISDN and PIN in commented-out Selenium code

Why this matters for docs: The docs describe credential management (RSA keys, merchant provisioning) without disclosing that Simpaisa's own credential hygiene is severely compromised. All affected credentials must be rotated and purged from Git history before any documentation about security practices is credible.

Gap 2: SSL Certificate Validation Disabled on Partner Connections

Severity: P0 — Must be remediated, then documented as a security practice

The remittance service's HttpClientService contains seven separate methods that create a TrustManager accepting all certificates and use NoopHostnameVerifier. This disables TLS verification on connections to partner banks (Bank of Asia, Faysal Bank, Trust Bank, 1Link).

A network attacker between Simpaisa and any partner bank can intercept and modify remittance instructions (amounts, beneficiary accounts) in transit.

What the docs should say (after remediation): "All partner bank connections use mutual TLS with proper certificate validation. Partner certificates are managed via a dedicated truststore."

Gap 3: AES-ECB Encryption for Card Data

Severity: P0 — Docs claim AES encryption; code uses insecure ECB mode

The GitBook docs reference "AES encryption" for card data — which is technically true but misleading. The code in wallet, card, and disbursement services all use Cipher.getInstance("AES") which defaults to AES/ECB/PKCS5Padding. ECB mode is deterministic — identical plaintext blocks produce identical ciphertext blocks, leaking data patterns. For a card payment service handling PAN data, this is a PCI-DSS compliance blocker.

What the docs should say (after remediation): "Card data is encrypted using AES-256-GCM with a unique 12-byte IV per operation."

Gap 4: Raw PAN Data Through Application Layer

Severity: P1 — Not documented, significantly increases PCI-DSS scope

The card service's AlfalahMasterCardThreeDService accepts raw card numbers and CVVs as plain String parameters. The application handles raw PAN data directly rather than using tokenised references from the acquirer gateway. Card data is also stored in Redis (MASTERCARD_CARDDATA cache, 15-minute TTL) without confirmed TLS or ACL configuration.

What the docs should say (after remediation): Document the tokenisation strategy clearly — whether Simpaisa handles raw PAN or uses acquirer-side tokenisation — so merchants can assess their own PCI-DSS scope.

Gap 5: Redis Used Everywhere, Not SurrealDB

Severity: P3 — Documentation references SurrealDB; code uses Redis

The organisation's documentation and stated technical direction reference SurrealDB (in-memory) for caching. The code audit found Redis everywhere:

  • Wallet: 3 separate Redis instances using 3 different client libraries (Jedis, Lettuce, Redisson)
  • Card: 2 Redis instances (merchant cache with infinite TTL, card data cache with 15-minute TTL)
  • Remittance: 1 Redis instance

No SurrealDB dependency exists in any service.

What the docs should say: Be honest about the current caching layer. If SurrealDB migration is planned, document it as a roadmap item, not a current capability.

Gap 6: CORS Unrestricted on Payment APIs

Severity: P0 — Not documented

Both wallet and card controllers have @CrossOrigin(origins = "*"), allowing any website to make cross-origin requests to the payment APIs. For server-to-server payment APIs, CORS should be disabled entirely or restricted to known merchant domains. This is not mentioned in the documentation.

Gap 7: PII Logged at INFO Level

Severity: P2 — Not documented, data protection concern

The remittance service logs full request/response bodies at INFO level, including account numbers, beneficiary names, addresses, IBAN numbers, and phone numbers. SOAP XML requests contain complete customer PII. This is not disclosed in any data handling or privacy documentation.

Gap 8: No Test Suite Exists

Severity: P2 — Relevant for merchant confidence

Zero unit tests and zero integration tests exist across all three core codebases. No src/test directories contain any test files. This is relevant to merchants evaluating platform reliability — the documentation implies a mature payment platform, but the code has no automated quality assurance.

Gap 9: Disbursement Gateway on End-of-Life Runtime

Severity: P1 — Not documented

The disbursement-gateway runs Spring Boot 2.2.6 with Java 8 — both end-of-life. The gateway uses Netflix Zuul (also in maintenance mode). The schedulers run Spring Boot 3.x / Java 17, creating an inconsistent and potentially insecure runtime environment. This is not documented and affects the security and reliability claims made in the Pay-Outs documentation.

Gap 10: No OpenAPI/Swagger Specification

Severity: P2 — Affects developer experience

No springdoc-openapi or springfox dependency exists in any service. No static OpenAPI YAML/JSON specs exist. The GitBook docs are the only API contract, and they are hand-written and incomplete. A machine-readable spec would enable SDK generation, automated testing, and contract validation.


Summary Table

# Finding Severity Product Code vs Docs Conflict
1 Auth differs per product, no unified strategy CRITICAL All Docs overstate security; no Spring Security in code
2 Idempotency only on Pay-Ins verify, not initiate CRITICAL All Idempotency fails open; Request-Id is optional
3 No standard error response schema CRITICAL All Exceptions swallowed; 200 OK returned on failures
4 No HTTP status codes documented CRITICAL All Confirmed: code always returns 200
5 Webhook missing retry policy and signature CRITICAL All Fire-and-forget; no retry; no DLT
6 Pay-Ins response schema incomplete HIGH Pay-Ins HashMap responses; no typed DTOs
7 Pay-Outs missing response schemas HIGH Pay-Outs
8 Inconsistent URL patterns HIGH All No versioning in code at all
9 7 different base URLs, different domain for remittances HIGH All Gateway on EOL stack; hardcoded IPs
10 Header typo: remitance HIGH Remittances
11 No pagination for list endpoints HIGH Pay-Outs, Remittances No row locking; arbitrary limits
12 No rate limiting documentation HIGH All No rate limiting in code either
13 Cards references 7 APIs without full docs HIGH Cards Validation is a no-op; inverted booleans
14 No reconciliation or settlement docs HIGH All No @Transactional; double for money
15 Inconsistent request body wrapping MEDIUM Pay-Outs, Remittances HashMap everywhere; no typed DTOs
16 OTP expiry not documented MEDIUM Pay-Ins
17 Non-OTP flow lacks API details MEDIUM Pay-Ins
18 FX rate quote validity not documented MEDIUM Remittances
19 Disbursement states missing transition diagram MEDIUM Pay-Outs Enum name/message mismatch
20 Remittance postbacks only for 4 of 9 states MEDIUM Remittances Retry scheduler only supports 1 partner
21 Field validation rules incomplete MEDIUM All Validation is no-op in code
22 Sandbox has no test scenarios MEDIUM All
23 No API versioning strategy MEDIUM All No versioning in codebase
24 Typos in page titles and descriptions LOW Pay-Outs Same typos in repo names
25 Content negotiation headers unclear LOW All
26 Bangladesh/Nepal/Iraq not assessed LOW Other
27 Refund API incomplete documentation HIGH Pay-Ins
28 1 Bill and Hosted Page not assessed LOW Pay-Ins

Documentation Gaps (from Code Audit)

# Gap Severity Impact
G1 Hardcoded credentials in Git P0 Security credibility
G2 SSL validation disabled on partner connections P0 Man-in-the-middle risk
G3 AES-ECB instead of AES-GCM P0 PCI-DSS blocker
G4 Raw PAN through application layer P1 PCI-DSS scope
G5 Redis everywhere, not SurrealDB P3 Docs misrepresent stack
G6 CORS unrestricted on payment APIs P0 Cross-origin abuse
G7 PII logged at INFO level P2 Data protection violation
G8 No test suite exists P2 Platform reliability
G9 Disbursement gateway on EOL runtime P1 Security and reliability
G10 No OpenAPI/Swagger specification P2 Developer experience

Comparison: GitBook vs Internal SA Docs

Area Internal SA Docs GitBook Docs Verdict
Idempotency Not mentioned Exists (Pay-Ins verify only) Improved but incomplete
Status codes Not documented 34+ codes mapped Much better
Transaction states Not documented 6 states (Pay-Outs) + 9 states (Remittances) Good
Auth documentation Vague ("API keys or OAuth") RSA signing with OpenSSL commands Much better
Sandbox URLs QA URL leaked in example Proper sandbox/production table Better
Webhook payloads Not documented Sample payloads for 3 event types Improved
Endpoint schemas Not documented Partial (some endpoints have full schemas) Partially improved
Error format Not documented Different per product, no standard Still missing
HTTP status codes Not documented Not documented Still missing
Reconciliation Not documented Not documented Still missing
Rate limiting Not documented Not documented Still missing
Internal details leaked Database tables, scheduler timing Mostly removed Much better

GitBook Docs vs Actual Source Code

Area GitBook Claims Code Reality Verdict
Authentication RSA signing for Pay-Outs/Remittances/Cards No Spring Security; some endpoints completely unauthenticated Docs overstate
Idempotency Request-Id header for deduplication Optional header; fails open on Redis failure Docs overstate
Error codes 34+ mapped status codes Exceptions swallowed; 200 OK returned on failure Docs overstate
Card encryption AES encryption with mutual TLS AES-ECB (insecure); raw PAN through app layer Docs misleading
Caching layer SurrealDB (in-memory) Redis everywhere (6 instances across services) Docs incorrect
Webhooks Sample payloads; HTTP 200 = success Fire-and-forget; no retry; no signature Docs incomplete
Input validation Field lengths and types documented No-op validation; inverted booleans Docs aspirational
Rate limiting Not documented Does not exist Consistent (both absent)
Reconciliation Not documented No @Transactional; double for money Consistent (both absent, code worse)
SSL/TLS Mutual TLS documented for Pay-Outs SSL validation disabled on 7 partner methods Docs overstate

Priority Action Plan

Phase 0: Code Remediation Before Any Doc Updates (Week 1-2)

The code audit revealed that several documented features do not work as described. Updating documentation without fixing the code would make the docs less trustworthy, not more. These code fixes must happen first:

  1. Rotate all hardcoded credentials and purge from Git history (G1)
  2. Fix SSL certificate validation on partner connections (G2)
  3. Replace AES-ECB with AES-GCM across all services (G3)
  4. Add Spring Security with authentication filter chain (CX-01)
  5. Fix inverted validation booleans in card service (C-02)
  6. Add @Transactional to all financial operations (R-05)
  7. Make idempotency mandatory and fix fail-open (W-04)
  8. Remove @CrossOrigin(origins="*") from payment controllers (G6)

Phase 1: Critical Doc Fixes (Week 3-5)

  1. Unified auth documentation page with all mechanisms explained — updated to reflect actual code behaviour (Finding 1)
  2. Extend idempotency to Pay-Outs and Remittances initiate calls (Finding 2)
  3. Standard error response schema across all products (Finding 3)
  4. Document HTTP status codes or document that all responses return 200 (Finding 4)
  5. Webhook retry policy and signature verification (Finding 5)
  6. Complete Refund API documentation (Finding 27)
  7. Correct SurrealDB references to reflect actual Redis usage (G5)

Phase 2: Schema Completeness (Week 6-7)

  1. Complete Pay-Ins response schemas for all endpoints (Finding 6)
  2. Complete Pay-Outs response schemas for all 14 endpoints (Finding 7)
  3. Complete Cards endpoint documentation for all 7 referenced APIs (Finding 13)
  4. Add field validation rules (regex, min/max, enums) — aligned with actual code validation (Finding 21)
  5. Document Non-OTP flow endpoints (Finding 17)

Phase 3: Merchant Operations (Week 8-9)

  1. Reconciliation and settlement documentation (Finding 14)
  2. Pagination for list endpoints (Finding 11)
  3. Rate limiting documentation — after implementing rate limiting (Finding 12)
  4. Sandbox test scenarios with test credentials (Finding 22)
  5. FX rate quote validity (Finding 18)
  6. State transition diagrams for Pay-Outs and Remittances (Findings 19, 20)

Phase 4: Consistency and Polish (Week 10-12)

  1. Standardise URL patterns documentation (Finding 8)
  2. Consolidate base URLs documentation (Finding 9)
  3. Fix typos (Findings 10, 24)
  4. Document versioning strategy (Finding 23)
  5. OTP expiry documentation (Finding 16)
  6. Audit Bangladesh, Nepal, Iraq docs (Finding 26)
  7. Content negotiation cleanup (Finding 25)
  8. 1 Bill and Hosted Page audit (Finding 28)
  9. Generate OpenAPI specs from code once typed DTOs are in place (G10)