[Fintech][8 min read]

Fintech App Development: Compliance and Architecture Basics

By Muhammad Ansar · CTO, SignX Solutions
Banking app dashboard showing payments and compliance panels

Building software that moves money is a different discipline from building software that moves information. Most of the difference is not regulatory, it is that a bug becomes a financial loss, a duplicate becomes a double charge, and a race condition becomes a negative balance you have to explain to a customer. These are the decisions that are cheap to make correctly at the start and expensive to reverse later. ## Store money as integers Never use a floating point type for a monetary amount. Binary floating point cannot represent most decimal fractions exactly, and the error compounds across operations. Store minor units as integers (cents, fils, paisa) along with an explicit currency code. Do arithmetic in integers and format for display at the very edge of the system. Where a database decimal type with defined precision is available, that works too. What does not work is a float. Rounding needs a documented policy, applied in one place. Decide how you round, decide who absorbs the remainder when splitting an amount, and write it down. Rounding disputes are among the hardest bugs to reconstruct after the fact. ## Build a ledger, not a balance column The instinct is a balance column you increment and decrement. It is the wrong shape. Use double-entry: every movement is recorded as at least two entries that sum to zero, and a balance is derived from the entries. This gives you a complete, append-only history, makes reconciliation possible, and means an incorrect balance can be traced to the entry that caused it. The ledger should be immutable. Corrections are new entries reversing the old ones, never edits. When a regulator, an auditor or a furious customer asks how a balance came to be what it is, the answer is a query rather than an archaeology project. > A balance you cannot derive from history is a number you cannot defend. ## Idempotency is not optional Networks retry. Users double-click. Webhooks are delivered more than once, every serious payment provider says so explicitly in their documentation. Every endpoint that moves money needs an idempotency key supplied by the caller and stored with the result. A repeat of the same key returns the original outcome rather than performing the operation again. For inbound webhooks: - Verify the signature before doing anything else

  • Deduplicate on the provider's event id
  • Process asynchronously and acknowledge quickly, so a slow database does not cause the provider to retry
  • Assume events arrive out of order, and make handlers tolerate it ## Transactions and concurrency Money mutations belong inside a database transaction. Two operations against the same account must not interleave in a way that lets both read the same balance and both write. Use the isolation level and locking your database actually provides (a SELECT... FOR UPDATE, an optimistic version column, or serialisable isolation) and test it under concurrency rather than assuming it. Race conditions on balances do not appear in single-threaded testing; they appear on your busiest day. Where a limit exists (daily transfer caps, per-transaction maxima) enforce it server-side inside the same transaction as the movement. Client-side checks are a user experience feature, not a control. ## Reduce PCI scope rather than manage it If raw card numbers touch your servers, your compliance obligations expand dramatically. The pragmatic architecture is to keep them out entirely: use the provider's hosted fields or tokenisation so the card data goes from the customer's browser to the processor without transiting your infrastructure. You store a token. This is not a shortcut. It is the recommended pattern, and it converts a large ongoing compliance burden into a much smaller one. ## KYC, AML and identity If you hold funds, exchange currency or facilitate transfers, you are likely in scope for identity verification and anti-money-laundering obligations. The specifics vary by jurisdiction and are the one area where you need qualified local advice rather than engineering judgement. Architecturally, plan for it regardless: - Identity verification as a distinct step in onboarding, with clear states, not a boolean flag
  • Screening and monitoring hooks that can be enabled per jurisdiction
  • Retention of verification evidence with an access trail
  • The ability to freeze an account and halt movements without deleting history Retrofitting an onboarding flow that assumed a single global path is one of the more painful projects in this space. ## Audit trails and data handling Log who did what, to which entity, when, and from where, with the previous and new values for anything that changed. Keep these logs separate from application logs and make them immutable. Equally important: keep sensitive data *out* of ordinary logs. Card numbers, full national identifiers, authentication tokens and personal data should never reach a log aggregator. This is a common and serious leak, and it is easy to prevent with structured logging and an explicit redaction list. Encrypt personal and financial fields at rest, and manage keys through a proper key management service rather than environment variables. ## Testing that actually catches money bugs - Property tests on the ledger (entries always sum to zero, balances never go negative where they must not
  • Concurrency tests) parallel operations against the same account, asserting no lost updates
  • Idempotency tests, the same request twice produces one movement
  • Webhook replay, duplicate and out-of-order delivery handled correctly
  • Reconciliation, your ledger against the provider's, run automatically and alerting on any drift That last one is the safety net. Anything that goes wrong anywhere shows up as a reconciliation break, which is why it should run daily and page someone when it fails. ## Working with SignX We build payment and fintech products, and this is the checklist we work through before writing a line of code. If you are planning something in this space, tell us what you're building, including the regulatory context, because it changes the architecture.

Frequently asked questions

  • As integers in minor units with an explicit currency code, or a decimal type with defined precision. Never as a floating point number, binary floating point cannot represent most decimal fractions exactly and the error compounds. Format for display only at the edge of the system, and apply one documented rounding policy in one place.

Planning something similar? Tell us about your project and we'll come back within 24 hours.