Skip to content

romalsh/payment-service

Repository files navigation

Payment Service

A production-style service for secure and idempotent payment webhook processing.

The project demonstrates how to protect payment integrations against invalid signatures, replay attacks, duplicate delivery and concurrent processing.

  • HMAC-SHA256 webhook signature verification
  • Timestamp validation and stale request rejection
  • Replay attack protection using unique nonces
  • Idempotent processing of duplicate webhook deliveries
  • Controlled payment state transitions
  • Concurrency-safe account crediting
  • Exactly-once business effect for successful payments
  • Unit and end-to-end test coverage
  • Isolated application and infrastructure layers

Architecture

flowchart LR
    MerchantClient[Merchant client] --> InvoiceAPI[Invoice API]
    Provider[Payment provider] --> WebhookAPI[Webhook API]

    InvoiceAPI --> InvoiceService[Invoice service]
    WebhookAPI --> Verification[HMAC and timestamp verification]
    Verification --> ReplayProtection[Nonce replay protection]
    ReplayProtection --> WebhookService[Webhook service]

    InvoiceService --> MerchantRepository[(Merchant repository)]
    InvoiceService --> InvoiceRepository[(Invoice repository)]

    WebhookService --> MerchantRepository
    WebhookService --> InvoiceRepository
    WebhookService --> CreditService[Credit service]
    ReplayProtection --> Redis[(Redis)]

    MerchantRepository --> MongoDB[(MongoDB)]
    InvoiceRepository --> MongoDB
Loading

Stack

  • TypeScript
  • Node.js
  • Express
  • MongoDB and Mongoose
  • Redis and ioredis
  • Jest
  • Supertest
  • MongoDB Memory Server
  • Prettier

Project structure

src/
├── application/       # Use cases and application ports
├── domain/            # Invoice rules, money logic, and domain errors
├── infrastructure/    # MongoDB, Redis, and external-service adapters
├── libs/              # Cryptography, HTTP, and logging utilities
├── presentation/      # Express application, routers, and error handling
├── config.ts          # Environment configuration
└── server.ts          # Composition root and application entry point

tests/
├── helpers.ts
└── webhook.e2e.test.ts

Testing

Run all tests:

npm test

Run unit tests:

npm run test:unit

Run end-to-end tests:

npm run test:e2e

The test suite covers:

  • valid signatures
  • modified payloads
  • incorrect secrets
  • malformed and missing signatures
  • stale timestamps
  • missing headers
  • reused nonces
  • duplicate deliveries
  • terminal-state protection
  • unknown invoices
  • invalid statuses
  • simultaneous webhook processing
  • recovery after partial processing failure

Signed webhook example

import crypto from "node:crypto";

const secret = "demo-webhook-secret";
const body = JSON.stringify({
  invoiceId: "replace-with-an-invoice-id",
  status: "paid"
});

const signature = crypto
  .createHmac("sha256", secret)
  .update(body)
  .digest("hex");

const response = await fetch("http://localhost:3000/webhook", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "x-signature": signature,
    "x-timestamp": String(Math.floor(Date.now() / 1000)),
    "x-nonce": crypto.randomUUID()
  },
  body
});

console.log(response.status, await response.json());

About

Secure payment webhook processing with HMAC verification, replay protection, idempotency and concurrency-safe crediting

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors