UoM GDG · Hands-on Backend Workshop

Engineering a Fintech Payment API

How Payment Systems Differ from Normal Web APIs

Host
GDG on Campus, University of Manchester
Date
February 25, 2025

This workshop connects the portfolio page and the live fintech-api demo project. Students see how the same API fundamentals apply in both fintech and non-fintech systems, but fintech adds strict controls around money movement, identity, and legal accountability.

We frame the session around one idea: writing a normal API is mostly product engineering; writing a fintech API is product engineering plus security engineering plus regulatory engineering.

Session Focus

Who Should Attend: CS Year 1-2 students, REST/HTTP learners, and fintech-curious builders.

Workshop Outcomes

After this workshop you will be able to:
  • Explain how fintech APIs differ from regular APIs.
  • Understand idempotency and why it prevents double charges.
  • Understand basic compliance and fraud concepts.
  • Test and run a local payment API project.
  • Read and follow a backend request flow.

Prerequisites: API basics, GET/POST, JSON, and basic Node.js.

Concepts + Architecture

At a high level, the API interface can look similar. The difference is the risk envelope: financial data, customer identity, legal obligations, and irreversible money impact.

Security and Compliance

  • Strong encryption, secure key management, and tokenization of sensitive fields.
  • KYC/AML checks before funds movement.
  • Regulatory reporting and traceability requirements by jurisdiction.

Reliability and Data Handling

  • Atomic and idempotent transaction behavior.
  • Exactly-once intent for payment side effects.
  • Strict storage and logging discipline for sensitive identity/payment data.

Audit and Release Controls

  • Tamper-evident audit trails and fraud monitoring in real time.
  • Sandbox and certification-style environments for partner/testing flows.
  • Controlled production releases with security and penetration testing.

Architecture flow

System architecture

flowchart TD A[HTTP Request\nPOST /v1/payments] B[Router\nsrc/routes/router.js] C[Controller\nsrc/routes/payments.js] D[Validation\nsrc/utils/validation.js] E[Compliance/Fraud\nsrc/core/*.js] F[Model Store\nsrc/core/store.js] G[Ledger + Audit] H[API Response\n201/202/422] A --> B --> C C --> D C --> E C --> F C --> G G --> H

Audit chain

sequenceDiagram participant API participant AuditLog API->>AuditLog: Write entry (prevHash, hash) AuditLog-->>API: Store entry API->>AuditLog: GET /v1/audit-logs/verify AuditLog-->>API: Verify chain integrity

Each entry links to previous by hash.

Demo

The workshop uses GitHub Repo, a dependency-light Node.js project focused on behavior and architecture clarity.

  • Idempotency: Idempotency-Key support on POST /v1/payments to avoid double charging.
  • Compliance: KYC/AML gates before moving money.
  • Fraud: Velocity and pattern checks.
  • Ledger: Double-entry posting for every successful payment.
  • Audit: Tamper-evident log chain using prevHash and hash.
  • Data redaction: Sensitive fields are masked in operational logs.

Endpoint comparison

General API endpoint

POST /orders

  • validate payload
  • create order record
  • return confirmation

Fintech API endpoint

POST /v1/payments

  • auth and idempotency gate
  • KYC/AML and fraud checks
  • ledger post when approved
  • audit chain write
  • deterministic response

Workshop

Hands-on steps used during the session.

Step 1: Clone and enter the project

git clone https://github.com/kamrankhalid786/fintech-payment-api-architecture.git
cd fintech-payment-api-architecture

Step 2: Prepare .env config

cp .env.example .env

Default demo token: demo-fintech-token

Step 3: Run tests and start server

npm test
npm start

Default local URL: http://localhost:3000

Step 4: Validate endpoints

curl -s http://localhost:3000/health

curl -s http://localhost:3000/v1/accounts \
  -H "Authorization: Bearer demo-fintech-token"

Code Steps and Diagrams: Route -> Controller -> Model -> Validation

Route

File: src/routes/router.js

if (req.method === "POST" && path === "/v1/payments") {
  createPayment(req, res);
  return true;
}

Route mapping

flowchart LR A[Request] B[routeRequest] C{POST /v1/payments?} D[createPayment] E[return true] F[next route checks] A --> B --> C C -- Yes --> D --> E C -- No --> F

Controller

File: src/routes/payments.js

export function createPayment(req, res) {
  const payload = req.body ?? {};
  validatePaymentPayload(payload);

  const idempotencyKey = req.headers["idempotency-key"];
  const replay = getIdempotencyResult("POST:/v1/payments", idempotencyKey, payload);
  if (replay) return sendJson(res, replay.statusCode, replay.body, { "Idempotency-Replayed": "true" });

  // compliance + fraud + ledger + audit
}

Controller orchestration

sequenceDiagram participant C as Controller participant V as Validate participant I as Idempotency participant R as Risk participant L as Ledger/Audit C->>V: Validate payload V-->>C: OK C->>I: Check key I-->>C: Replay or proceed C->>R: Compliance + fraud R-->>C: Decision C->>L: Ledger post + audit L-->>C: Done C-->>C: Send response

Model (In-memory store)

File: src/core/store.js

export const store = {
  customers: seedCustomers(),
  accounts: seedAccounts(),
  payments: new Map(),
  ledgerEntries: [],
  auditLogs: [],
  idempotencyKeys: new Map(),
  recentPaymentAttempts: []
};

Model and state

flowchart LR A[store] B[customers + accounts] C[payments + idempotency] D[ledger + audit] E[recentAttempts] A --> B A --> C A --> D A --> E

Validation

File: src/utils/validation.js

export function validatePaymentPayload(payload) {
  if (!isNonEmptyString(payload.sourceAccountId)) throw new ApiError(400, "VALIDATION_ERROR");
  if (!isNonEmptyString(payload.destinationAccountId)) throw new ApiError(400, "VALIDATION_ERROR");
  if (payload.sourceAccountId === payload.destinationAccountId) throw new ApiError(400, "VALIDATION_ERROR");
  if (!Number.isInteger(payload.amountCents) || payload.amountCents <= 0) throw new ApiError(400, "VALIDATION_ERROR");
}

Validation rules

flowchart LR A[Validate payload] B{Fields valid?} C{Amount > 0?} D[Accept] X[ApiError 400] A --> B B -- No --> X B -- Yes --> C C -- No --> X C -- Yes --> D

Run & Execution

$ cd fintech-payment-api-architecture
$ cp .env.example .env
$ npm test
$ npm start
$ curl -s http://localhost:3000/health
$ curl -s http://localhost:3000/v1/accounts -H "Authorization: Bearer demo-fintech-token"
$ curl -s -X POST http://localhost:3000/v1/payments ...
Exercise
Repeat the same payment request with the same Idempotency-Key and observe how the API avoids duplicate financial side effects.