Crypto payment gateway architecture design

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Crypto payment gateway architecture design
Complex
~5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Designing Crypto Payment Gateway Architecture

A payment gateway is not just "accept crypto". It's a system resilient to node disconnect loss, blockchain reorganizations, double-spend attacks, rate fluctuation risks during conversion, and external API unavailability. All this simultaneously while handling real customer money.

Designing architecture before coding saves months of rework.

Fundamental Requirements

Before drawing diagrams, answer questions that determine everything:

Custodial or non-custodial?

  • Custodial: gateway stores keys, manages customer wallets. Full control, but legal liability as financial institution, licensing requirements (in some jurisdictions — payment operator license).
  • Non-custodial: payment goes directly to merchant wallet, gateway just monitors. No third-party asset custody, but merchant manages keys.

Which networks? Each network requires separate infrastructure. Bitcoin UTXO model incompatible with EVM account-model. TON has own VM. Each adds operational burden.

Settlement model. Does merchant receive crypto as-is, or gateway converts to fiat? With conversion — rate risk appears and exchange/OTC integration is needed.

Volume and SLA. 100 txns/day vs 100,000 are different architectures. 99.9% SLA vs 99.99% require fundamentally different redundancy.

Component Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Merchant-facing API                       │
│              REST / Webhooks / SDK libraries               │
└───────────────────────┬─────────────────────────────────────┘
                        │
┌───────────────────────▼─────────────────────────────────────┐
│                    Core Services                             │
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │Invoice Service│  │Address Alloc │  │ Exchange Rate    │  │
│  │(create/query) │  │(HD wallet    │  │ Service          │  │
│  └──────────────┘  │ derivation)  │  └──────────────────┘  │
│                    └──────────────┘                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │Confirmation  │  │Settlement    │  │ Notification     │  │
│  │Tracker       │  │Service       │  │ Service          │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└───────────────┬──────────────────────────────────┬──────────┘
            │                                  │
┌───────────▼──────────┐          ┌────────────▼──────────────┐
│   Blockchain Layer   │          │       Data Layer           │
│                      │          │                            │
│  BTC Connector       │          │  PostgreSQL (orders,txns)  │
│  EVM Connector       │          │  Redis (rates, sessions)   │
│  TON Connector       │          │  Message Queue (Kafka/RMQ) │
│  TRON Connector      │          └────────────────────────────┘
└──────────────────────┘

Invoice Service

Central entity — invoice. State machine:

created → address_assigned → payment_detected → 
confirming (N/required) → confirmed → settled | expired | failed
interface Invoice {
  id: string;           // UUID
  merchant_id: string;
  external_order_id: string;
  
  // What merchant wants
  requested_currency: 'USD' | 'EUR';
  requested_amount: Decimal;
  
  // What customer pays
  payment_currency: 'BTC' | 'ETH' | 'USDT_ERC20' | 'USDT_TRC20';
  payment_network: 'bitcoin' | 'ethereum' | 'tron';
  payment_address: string;
  payment_amount: Decimal;      // in crypto
  exchange_rate: Decimal;       // locked rate
  exchange_rate_expires_at: Date;
  
  // State
  status: InvoiceStatus;
  received_amount: Decimal;
  tx_hash: string | null;
  confirmations: number;
  required_confirmations: number;
  
  // Timestamps
  created_at: Date;
  expires_at: Date;             // usually created_at + 15 min
  confirmed_at: Date | null;
  settled_at: Date | null;
}

Why exchange_rate_expires_at separate from expires_at: customer can start new payment after rate expires but before invoice expires — need to update rate.

Address Allocation

HD wallet with BIP-44 derivation. Mapping {network, account_index, address_index}address stored in DB. When creating invoice, take next free address_index.

Critical problem: address reuse. One address — one invoice. Ever. Even if invoice expired — address never reused, customer could have saved it and sent later.

CREATE TABLE address_pool (
    id BIGSERIAL PRIMARY KEY,
    network VARCHAR(20) NOT NULL,
    coin_type INTEGER NOT NULL,    -- BIP-44 coin type
    address_index BIGINT NOT NULL,
    address VARCHAR(200) NOT NULL,
    allocated_at TIMESTAMPTZ,
    invoice_id UUID REFERENCES invoices(id),
    UNIQUE(network, address_index)
);

-- Pre-generate addresses (batch)
CREATE INDEX idx_address_pool_unallocated 
    ON address_pool (network) 
    WHERE allocated_at IS NULL;

Pre-generation: generate addresses beforehand in batches of 1000. Avoids delay on invoice creation.

Blockchain Connector Interface

All connectors implement single interface:

interface BlockchainConnector {
  // Monitoring
  watchAddress(address: string, callback: (tx: IncomingTransaction) => void): () => void;
  getTransaction(txHash: string): Promise<TransactionDetail>;
  getConfirmations(txHash: string, blockNumber: number): Promise<number>;
  
  // Sending (for sweep)
  buildSweepTransaction(from: string, to: string, amount: bigint): Promise<UnsignedTx>;
  broadcastTransaction(signedTx: string): Promise<string>;
  
  // Utils
  validateAddress(address: string): boolean;
  estimateFee(): Promise<bigint>;
}

Implementations: BitcoinConnector (Electrum protocol), EVMConnector (viem/ethers.js, one instance for multiple EVM networks with different RPCs), TRONConnector (tronweb).

Resilience Patterns

Circuit breaker — if RPC node doesn't respond, switch to backup:

class ResilientEVMConnector {
  private rpcUrls: string[];
  private currentIndex = 0;
  private failures: Map<string, number> = new Map();

  private async callWithFallback<T>(fn: (client: PublicClient) => Promise<T>): Promise<T> {
    for (let attempt = 0; attempt < this.rpcUrls.length; attempt++) {
      const url = this.rpcUrls[this.currentIndex];
      try {
        const client = this.getClient(url);
        const result = await fn(client);
        this.failures.set(url, 0);  // reset on success
        return result;
      } catch (err) {
        const failures = (this.failures.get(url) || 0) + 1;
        this.failures.set(url, failures);
        if (failures >= 3) {
          this.currentIndex = (this.currentIndex + 1) % this.rpcUrls.length;
        }
        if (attempt === this.rpcUrls.length - 1) throw err;
      }
    }
    throw new Error('All RPC endpoints failed');
  }
}

Reorg Handling

Reorganization means blocks you processed became non-canonical. For gateway: transaction you thought was confirmed — disappeared.

Protection: never mark invoice settled with confirmations below safe threshold. Recommended thresholds:

Network Safe Confirmations Time
Bitcoin 3 (small) / 6 (large) 30-60 min
Ethereum 12-15 3-4 min
Polygon 128 (checkpoint) 5-7 min
Arbitrum 1 (L2) 15 sec
TRON 20 1 min

Additionally: store block_hash with tx_hash. On each confirmation check, verify block with this hash is still in canonical chain.

Settlement and Sweep

Sweep — transfer received funds from payment address to cold/treasury wallet. Automated via worker:

async function sweepConfirmedPayments() {
  const confirmed = await db.query(`
    SELECT i.payment_address, i.received_amount, i.payment_currency, i.payment_network
    FROM invoices i
    WHERE i.status = 'confirmed'
      AND i.swept_at IS NULL
      AND i.received_amount > 0
  `);

  for (const invoice of confirmed) {
    const connector = getConnector(invoice.payment_network);
    const fee = await connector.estimateFee();
    const sweepAmount = invoice.received_amount - fee;
    
    if (sweepAmount <= 0) continue;
    
    const unsignedTx = await connector.buildSweepTransaction(
      invoice.payment_address,
      TREASURY_ADDRESSES[invoice.payment_currency],
      sweepAmount
    );
    
    // Sign via HSM or KMS
    const signedTx = await signingService.sign(invoice.payment_address, unsignedTx);
    const txHash = await connector.broadcastTransaction(signedTx);
    
    await db.query(
      'UPDATE invoices SET swept_at = NOW(), sweep_tx_hash = $1 WHERE id = $2',
      [txHash, invoice.id]
    );
  }
}

Webhook System

Merchant subscribes to events via webhook URL. Reliable delivery requirements:

interface WebhookDelivery {
  id: string;
  merchant_id: string;
  invoice_id: string;
  event_type: 'payment.detected' | 'payment.confirmed' | 'payment.settled' | 'payment.expired';
  payload: object;
  status: 'pending' | 'delivered' | 'failed';
  attempts: number;
  next_retry_at: Date;
  delivered_at: Date | null;
}

Retry logic with exponential backoff: 30 sec → 5 min → 30 min → 2 hours → 24 hours. After 5 failures — failed, alert support.

HMAC webhook signature — merchant verifies request from gateway:

const signature = crypto
  .createHmac('sha256', merchantSecret)
  .update(JSON.stringify(payload))
  .digest('hex');

headers['X-Signature'] = `sha256=${signature}`;

Security

  • Keys never in app servers. HSM or KMS (AWS KMS, Google Cloud KMS, HashiCorp Vault). Signing service — isolated microservice with minimal privileges.
  • IP whitelist for merchant webhook endpoint — optional but recommended.
  • Rate limiting on API: invoice creation — max 100/min per merchant (DDoS protection).
  • Audit log of all actions: who created invoice, when status changed, which worker did sweep. Immutable log via append-only table or external SIEM.

Design Process

Day 1: requirements — networks, currencies, volumes, SLA, settlement model, jurisdiction.

Day 2: schema design — invoice lifecycle, address pool, webhook deliveries. Team review.

Day 3: blockchain connectors interface, resilience patterns, reorg strategy.

Day 4: API contract (OpenAPI spec), webhook events, merchant SDK.

Day 5: security review — key management, signing service isolation, threat model. Final architecture docs (ADRs for key decisions).

Result: Architecture Decision Records, DB schema, OpenAPI spec, component and sequence diagrams. Foundation for team development without constant architecture questions.