Fintech & Payments Architecture

Fintech Payment Gateway Integration Guide: Stripe, Razorpay, Webhooks, and Reconciliation in Node.js

Handling customer money demands zero-tolerance for bugs. A single unhandled webhook retry or race condition can double-charge clients or credit digital goods twice. Building production-grade fintech systems requires 3D Secure 2.0 client tokenization, cryptographic HMAC webhook ingestion, immutable dual-entry ledgers, and automated nightly settlement reconciliation.

Fintech Payment Gateway Integration Guide Stripe Razorpay Webhooks Dual Entry Ledgers
Production Summary

Never rely on client-side frontend callbacks (e.g. browser redirects) to mark invoices as paid. Always treat incoming asynchronous webhooks as the source of truth, verified with raw-body HMAC signatures, deduplicated via distributed Redis locks, and committed to an immutable double-entry ledger.

GatewaysStripe & Razorpay API v2026SecurityPCI-DSS SAQ-A Compliant TokenizationIntegrityImmutable Double-Entry Ledger (Zero Drift)

The Payment Intent & 3D Secure Lifecycle

Client OrdersBackend Creates IntentStripe Elements / Razorpay CheckoutBank 3DS OTP AuthenticationAsync Webhook ConfirmationDouble-Entry Ledger Settlement

By using Stripe Elements or Razorpay Standard Checkout, raw credit card numbers never touch your application servers, dramatically reducing your compliance scope to the simplest PCI-DSS Self-Assessment Questionnaire (SAQ-A).

Production Webhook Handler & Ledger Transaction

Stripe and Razorpay will retry webhooks for up to 72 hours if your endpoint experiences network timeouts. The code below guarantees idempotency and updates our financial ledgers within an atomic database transaction:

// stripe-webhook.ts - Production Stripe Webhook Handler with Dual-Entry Ledger
import { buffer } from "micro";
import Stripe from "stripe";
import { pool } from "../../db";
import Redis from "ioredis";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2024-06-20" as any,
});
const redis = new Redis(process.env.REDIS_URL!);

export async function handleStripeWebhook(req: any, res: any) {
  const buf = await buffer(req);
  const sig = req.headers["stripe-signature"];

  let event: Stripe.Event;

  // 1. Cryptographic HMAC Signature Verification
  try {
    event = stripe.webhooks.constructEvent(buf, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch (err: any) {
    console.error(`Webhook signature verification failed.`, err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // 2. Atomic Idempotency Check: Prevent duplicate payment settlement
  const idempotencyKey = `stripe_event:${event.id}`;
  const isNewEvent = await redis.set(idempotencyKey, "PROCESSED", "EX", 86400 * 30, "NX");
  if (!isNewEvent) {
    console.warn(`Duplicate Stripe event received: ${event.id}`);
    return res.status(200).json({ received: true, deduplicated: true });
  }

  // 3. Process Payment Intent Succeeded with Dual-Entry Accounting
  if (event.type === "payment_intent.succeeded") {
    const paymentIntent = event.data.object as Stripe.PaymentIntent;
    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      // Update Order Status
      await client.query(
        `UPDATE orders SET payment_status = 'PAID', updated_at = NOW() WHERE payment_intent_id = $1`,
        [paymentIntent.id]
      );

      // Record Double-Entry Ledger Transactions
      const amountCents = paymentIntent.amount_received;
      const currency = paymentIntent.currency;
      const orderId = paymentIntent.metadata.orderId;

      // Debit Cash/Receivables Account
      await client.query(
        `INSERT INTO ledger_entries (account_id, entry_type, amount_cents, currency, reference_id)
         VALUES ('ACCOUNT_STRIPE_CLEARING', 'DEBIT', $1, $2, $3)`,
        [amountCents, currency, orderId]
      );

      // Credit Revenue Account
      await client.query(
        `INSERT INTO ledger_entries (account_id, entry_type, amount_cents, currency, reference_id)
         VALUES ('ACCOUNT_SALES_REVENUE', 'CREDIT', $1, $2, $3)`,
        [amountCents, currency, orderId]
      );

      await client.query("COMMIT");
    } catch (dbError) {
      await client.query("ROLLBACK");
      // Release idempotency key so Stripe retry can succeed
      await redis.del(idempotencyKey);
      throw dbError;
    } finally {
      client.release();
    }
  }

  res.status(200).json({ received: true });
}

Double-Entry Accounting Architecture

In financial software, a single balance column (like users.balance = 500) is an anti-pattern. Every financial movement must consist of matching Debit and Credit entries such that the sum of all debits always equals the sum of all credits ($\sum Debits = \sum Credits$).

EventAccountEntry TypeAmount
Customer purchases $100 planStripe Clearing (Asset)DEBIT+$100.00
Customer purchases $100 planSubscription Sales (Revenue)CREDIT+$100.00
Stripe charges 2.9% fee ($2.90)Payment Processing Fees (Expense)DEBIT+$2.90
Stripe charges 2.9% fee ($2.90)Stripe Clearing (Asset)CREDIT-$2.90

Automating Refunds, Chargebacks, and Disputes

When a customer initiates a chargeback with their credit card issuer, payment gateways emitcharge.dispute.created events. Your application should automatically lock sensitive SaaS features, send a notification email with dispute documentation upload links, and post a provisional chargeback loss entry to your accounting ledger.

Nightly Settlement Reconciliation

Every 24 hours, download the gateway payout balance report and compare it against your internalledger_entries table. Automated cron jobs flag discrepancies (> $0.01) immediately, catching currency conversion rounding errors, gateway fees, and unrecorded refunds.

Fintech Integration Checklist

✓ Raw body buffer used for HMAC signature validation

✓ Redis idempotency key locks prevent double-billing on retries

✓ Double-entry ledger architecture records debit/credit pairs

✓ 3D Secure 2.0 (3DS) enabled for SCA compliance in Europe & India

✓ All monetary amounts stored as integers in minor currency units (cents/paise)

✓ Automatic dispute webhooks alert support and lock delinquent accounts

✓ Daily automated reconciliation script validates gateway payouts

✓ Secret keys guarded in KMS/Vault with restricted developer access

Build Secure Fintech & Payment Solutions with Endurance Softwares

We engineer PCI-compliant payment gateways, subscription billing engines, multi-currency wallets, and automated reconciliation systems for enterprise platforms.

Consult With Our Fintech Engineers
Shares

Request Free Consultation

Frequently Asked Questions

Why store currency amounts as integers instead of floating point numbers?

Floating-point numbers in computer memory (IEEE 754) suffer from binary rounding errors (e.g., 0.1 + 0.2 = 0.30000000000000004). Always store monetary values in the smallest currency unit (e.g., cents, pence, paise) as integer BIGINT columns.

What is the difference between Stripe and Razorpay?

Stripe is the global standard for international cards, SEPA, ACH, and Apple Pay. Razorpay is the leading gateway in India with specialized support for UPI AutoPay, NetBanking across 50+ Indian banks, and RBI recurring mandates.

How do we handle multi-currency payments?

Lock the foreign exchange rate at the time the payment intent is created. Record the transaction amount in both the presentment currency (what the customer paid) and your base settlement currency in the ledger.

Get Quote
Let's build something powerful

Have a project idea? Let’s turn it into a scalable product.

Book Free Consultation

© 2026 Endurance Softwares. All rights reserved.