Skip to content

Payment Flow Example

This comprehensive example demonstrates a complete payment flow from order creation to payment confirmation using the Crypto Payment Gateway.

Overview

This example shows how to implement a typical e-commerce payment flow:

  1. Customer places an order
  2. System generates payment address
  3. Customer sends cryptocurrency
  4. System detects and confirms payment
  5. Order is fulfilled

Complete Implementation

1. Order Creation and Payment Address Generation

javascript
// services/PaymentService.js
const { Gateway } = require('@gateway/crypto-payment-sdk');

class PaymentService {
  constructor() {
    this.gateway = new Gateway({
      apiKey: process.env.GATEWAY_API_KEY,
      environment: process.env.NODE_ENV === 'production' ? 'production' : 'test'
    });
  }

  async createPaymentForOrder(order) {
    try {
      // Create unique payment address for this order
      const paymentAddress = await this.gateway.addresses.create({
        network: order.paymentMethod.network || 'ethereum',
        coin: order.paymentMethod.coin || 'usdt',
        type: 'user',
        metadata: {
          customer_id: order.customerId,
          order_id: order.id,
          amount: order.total.toString(),
          currency: order.currency,
          created_at: new Date().toISOString(),
          expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() // 24 hours
        }
      });

      // Update order with payment information
      await this.updateOrderPaymentInfo(order.id, {
        paymentAddressId: paymentAddress.data.id,
        paymentAddress: paymentAddress.data.address,
        network: paymentAddress.data.network,
        coin: paymentAddress.data.coin,
        status: 'awaiting_payment'
      });

      return {
        success: true,
        paymentAddress: paymentAddress.data.address,
        network: paymentAddress.data.network,
        coin: paymentAddress.data.coin,
        amount: order.total,
        qrCode: await this.generateQRCode(paymentAddress.data.address, order.total),
        expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
      };

    } catch (error) {
      console.error('Failed to create payment for order:', error);
      throw new Error('Payment creation failed');
    }
  }

  async generateQRCode(address, amount) {
    // Generate QR code for payment
    const paymentUri = `ethereum:${address}?value=${amount}`;
    return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(paymentUri)}`;
  }

  async updateOrderPaymentInfo(orderId, paymentInfo) {
    // Update order in database
    // Implementation depends on your database
    console.log(`Updating order ${orderId} with payment info:`, paymentInfo);
  }
}

module.exports = PaymentService;

2. Frontend Payment Interface

html
<!-- payment.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Complete Payment</title>
    <style>
        .payment-container {
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            font-family: Arial, sans-serif;
        }
        .payment-info {
            background: #f5f5f5;
            padding: 20px;
            border-radius: 8px;
            margin: 20px 0;
        }
        .address-display {
            background: white;
            padding: 15px;
            border: 2px solid #ddd;
            border-radius: 4px;
            font-family: monospace;
            word-break: break-all;
            margin: 10px 0;
        }
        .qr-code {
            text-align: center;
            margin: 20px 0;
        }
        .status-indicator {
            padding: 10px;
            border-radius: 4px;
            margin: 10px 0;
            font-weight: bold;
        }
        .status-pending { background: #fff3cd; color: #856404; }
        .status-confirmed { background: #d4edda; color: #155724; }
        .status-failed { background: #f8d7da; color: #721c24; }
        .countdown {
            background: #e7f3ff;
            padding: 10px;
            border-radius: 4px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="payment-container">
        <h1>Complete Your Payment</h1>
        
        <div class="payment-info">
            <h3>Order Summary</h3>
            <p><strong>Order ID:</strong> <span id="orderId"></span></p>
            <p><strong>Amount:</strong> <span id="amount"></span> <span id="coin"></span></p>
            <p><strong>Network:</strong> <span id="network"></span></p>
        </div>

        <div class="payment-info">
            <h3>Payment Instructions</h3>
            <p>Send exactly <strong><span id="paymentAmount"></span> <span id="paymentCoin"></span></strong> to the address below:</p>
            
            <div class="address-display" id="paymentAddress"></div>
            
            <div class="qr-code">
                <img id="qrCode" alt="Payment QR Code" style="max-width: 200px;">
            </div>
            
            <div class="countdown">
                <p>Payment expires in: <span id="countdown"></span></p>
            </div>
        </div>

        <div id="paymentStatus" class="status-indicator status-pending">
            ⏳ Waiting for payment...
        </div>

        <div id="transactionInfo" style="display: none;">
            <h3>Transaction Details</h3>
            <p><strong>Transaction Hash:</strong> <span id="txHash"></span></p>
            <p><strong>Confirmations:</strong> <span id="confirmations"></span></p>
            <p><strong>Status:</strong> <span id="txStatus"></span></p>
        </div>
    </div>

    <script>
        class PaymentMonitor {
            constructor(orderId) {
                this.orderId = orderId;
                this.pollInterval = null;
                this.countdownInterval = null;
                this.init();
            }

            async init() {
                await this.loadPaymentInfo();
                this.startPolling();
                this.startCountdown();
            }

            async loadPaymentInfo() {
                try {
                    const response = await fetch(`/api/orders/${this.orderId}/payment`);
                    const payment = await response.json();

                    document.getElementById('orderId').textContent = this.orderId;
                    document.getElementById('amount').textContent = payment.amount;
                    document.getElementById('coin').textContent = payment.coin.toUpperCase();
                    document.getElementById('network').textContent = payment.network;
                    document.getElementById('paymentAmount').textContent = payment.amount;
                    document.getElementById('paymentCoin').textContent = payment.coin.toUpperCase();
                    document.getElementById('paymentAddress').textContent = payment.address;
                    document.getElementById('qrCode').src = payment.qrCode;

                    this.expiresAt = new Date(payment.expiresAt);
                } catch (error) {
                    console.error('Failed to load payment info:', error);
                }
            }

            startPolling() {
                this.pollInterval = setInterval(async () => {
                    await this.checkPaymentStatus();
                }, 10000); // Check every 10 seconds
            }

            async checkPaymentStatus() {
                try {
                    const response = await fetch(`/api/orders/${this.orderId}/status`);
                    const status = await response.json();

                    this.updatePaymentStatus(status);

                    if (status.status === 'confirmed' || status.status === 'failed') {
                        clearInterval(this.pollInterval);
                        clearInterval(this.countdownInterval);
                    }
                } catch (error) {
                    console.error('Failed to check payment status:', error);
                }
            }

            updatePaymentStatus(status) {
                const statusElement = document.getElementById('paymentStatus');
                const transactionInfo = document.getElementById('transactionInfo');

                switch (status.status) {
                    case 'awaiting_payment':
                        statusElement.className = 'status-indicator status-pending';
                        statusElement.innerHTML = '⏳ Waiting for payment...';
                        break;
                    case 'payment_detected':
                        statusElement.className = 'status-indicator status-pending';
                        statusElement.innerHTML = '🔍 Payment detected, waiting for confirmations...';
                        this.showTransactionInfo(status.transaction);
                        break;
                    case 'confirmed':
                        statusElement.className = 'status-indicator status-confirmed';
                        statusElement.innerHTML = '✅ Payment confirmed! Order is being processed.';
                        this.showTransactionInfo(status.transaction);
                        setTimeout(() => {
                            window.location.href = `/orders/${this.orderId}/success`;
                        }, 3000);
                        break;
                    case 'failed':
                        statusElement.className = 'status-indicator status-failed';
                        statusElement.innerHTML = '❌ Payment failed or expired.';
                        break;
                }
            }

            showTransactionInfo(transaction) {
                if (transaction) {
                    document.getElementById('txHash').textContent = transaction.hash;
                    document.getElementById('confirmations').textContent = transaction.confirmations;
                    document.getElementById('txStatus').textContent = transaction.status;
                    document.getElementById('transactionInfo').style.display = 'block';
                }
            }

            startCountdown() {
                this.countdownInterval = setInterval(() => {
                    const now = new Date();
                    const timeLeft = this.expiresAt - now;

                    if (timeLeft <= 0) {
                        document.getElementById('countdown').textContent = 'EXPIRED';
                        clearInterval(this.countdownInterval);
                        return;
                    }

                    const hours = Math.floor(timeLeft / (1000 * 60 * 60));
                    const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
                    const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);

                    document.getElementById('countdown').textContent = 
                        `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
                }, 1000);
            }
        }

        // Initialize payment monitor
        const urlParams = new URLSearchParams(window.location.search);
        const orderId = urlParams.get('orderId') || 'ORDER_123';
        new PaymentMonitor(orderId);
    </script>
</body>
</html>

3. Backend API Endpoints

javascript
// routes/payment.js
const express = require('express');
const PaymentService = require('../services/PaymentService');
const OrderService = require('../services/OrderService');

const router = express.Router();
const paymentService = new PaymentService();
const orderService = new OrderService();

// Create payment for order
router.post('/orders/:orderId/payment', async (req, res) => {
  try {
    const order = await orderService.getOrder(req.params.orderId);
    
    if (!order) {
      return res.status(404).json({ error: 'Order not found' });
    }

    if (order.status !== 'pending') {
      return res.status(400).json({ error: 'Order is not pending payment' });
    }

    const payment = await paymentService.createPaymentForOrder(order);
    
    res.json(payment);
  } catch (error) {
    console.error('Payment creation error:', error);
    res.status(500).json({ error: 'Failed to create payment' });
  }
});

// Get payment info for order
router.get('/orders/:orderId/payment', async (req, res) => {
  try {
    const order = await orderService.getOrder(req.params.orderId);
    
    if (!order || !order.paymentAddress) {
      return res.status(404).json({ error: 'Payment not found' });
    }

    res.json({
      address: order.paymentAddress,
      network: order.network,
      coin: order.coin,
      amount: order.total,
      qrCode: await paymentService.generateQRCode(order.paymentAddress, order.total),
      expiresAt: order.paymentExpiresAt
    });
  } catch (error) {
    console.error('Get payment info error:', error);
    res.status(500).json({ error: 'Failed to get payment info' });
  }
});

// Get order status
router.get('/orders/:orderId/status', async (req, res) => {
  try {
    const order = await orderService.getOrderWithTransaction(req.params.orderId);
    
    if (!order) {
      return res.status(404).json({ error: 'Order not found' });
    }

    res.json({
      status: order.status,
      transaction: order.transaction ? {
        hash: order.transaction.hash,
        confirmations: order.transaction.confirmations,
        status: order.transaction.status
      } : null
    });
  } catch (error) {
    console.error('Get order status error:', error);
    res.status(500).json({ error: 'Failed to get order status' });
  }
});

module.exports = router;

4. Webhook Handler for Payment Processing

javascript
// webhooks/paymentWebhook.js
const express = require('express');
const crypto = require('crypto');
const OrderService = require('../services/OrderService');
const NotificationService = require('../services/NotificationService');

const router = express.Router();
const orderService = new OrderService();
const notificationService = new NotificationService();

// Webhook signature verification middleware
function verifyWebhookSignature(req, res, next) {
  const signature = req.headers['x-gateway-signature'];
  const payload = JSON.stringify(req.body);
  
  const expectedSignature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  if (signature !== `sha256=${expectedSignature}`) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  next();
}

// Main webhook handler
router.post('/gateway', verifyWebhookSignature, async (req, res) => {
  try {
    const { event, data } = req.body;
    
    console.log(`Received webhook: ${event}`, data);
    
    switch (event) {
      case 'transaction.detected':
        await handleTransactionDetected(data);
        break;
      case 'transaction.confirmed':
        await handleTransactionConfirmed(data);
        break;
      case 'transaction.failed':
        await handleTransactionFailed(data);
        break;
      default:
        console.log(`Unhandled webhook event: ${event}`);
    }
    
    res.status(200).json({ success: true });
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).json({ error: 'Webhook processing failed' });
  }
});

async function handleTransactionDetected(transaction) {
  const { metadata } = transaction;
  
  if (!metadata || !metadata.order_id) {
    console.log('Transaction without order metadata:', transaction.id);
    return;
  }
  
  try {
    // Update order status
    await orderService.updateOrderStatus(metadata.order_id, 'payment_detected', {
      transactionId: transaction.id,
      transactionHash: transaction.hash,
      amount: transaction.amount,
      confirmations: transaction.confirmations
    });
    
    // Send notification to customer
    await notificationService.sendPaymentDetectedNotification(metadata.order_id);
    
    console.log(`Payment detected for order ${metadata.order_id}`);
  } catch (error) {
    console.error(`Failed to process detected transaction for order ${metadata.order_id}:`, error);
  }
}

async function handleTransactionConfirmed(transaction) {
  const { metadata } = transaction;
  
  if (!metadata || !metadata.order_id) {
    console.log('Confirmed transaction without order metadata:', transaction.id);
    return;
  }
  
  try {
    const order = await orderService.getOrder(metadata.order_id);
    
    if (!order) {
      console.error(`Order not found: ${metadata.order_id}`);
      return;
    }
    
    // Verify payment amount
    const expectedAmount = parseFloat(metadata.amount);
    const receivedAmount = parseFloat(transaction.amount);
    
    if (receivedAmount < expectedAmount) {
      console.error(`Insufficient payment for order ${metadata.order_id}: expected ${expectedAmount}, received ${receivedAmount}`);
      await orderService.updateOrderStatus(metadata.order_id, 'payment_insufficient');
      return;
    }
    
    // Mark order as paid and confirmed
    await orderService.updateOrderStatus(metadata.order_id, 'confirmed', {
      transactionId: transaction.id,
      transactionHash: transaction.hash,
      amount: transaction.amount,
      confirmations: transaction.confirmations,
      confirmedAt: new Date()
    });
    
    // Process the order (fulfill, ship, etc.)
    await orderService.processOrder(metadata.order_id);
    
    // Send confirmation notifications
    await notificationService.sendPaymentConfirmedNotification(metadata.order_id);
    
    console.log(`Payment confirmed and order processed: ${metadata.order_id}`);
  } catch (error) {
    console.error(`Failed to process confirmed transaction for order ${metadata.order_id}:`, error);
  }
}

async function handleTransactionFailed(transaction) {
  const { metadata } = transaction;
  
  if (!metadata || !metadata.order_id) {
    return;
  }
  
  try {
    await orderService.updateOrderStatus(metadata.order_id, 'payment_failed', {
      transactionId: transaction.id,
      transactionHash: transaction.hash,
      failureReason: transaction.failure_reason
    });
    
    await notificationService.sendPaymentFailedNotification(metadata.order_id);
    
    console.log(`Payment failed for order ${metadata.order_id}`);
  } catch (error) {
    console.error(`Failed to process failed transaction for order ${metadata.order_id}:`, error);
  }
}

module.exports = router;

5. Order Service Implementation

javascript
// services/OrderService.js
class OrderService {
  constructor() {
    // Initialize database connection
  }

  async getOrder(orderId) {
    // Get order from database
    // This is a mock implementation
    return {
      id: orderId,
      customerId: 'customer_123',
      total: 100.50,
      currency: 'USD',
      status: 'pending',
      items: [
        { name: 'Product 1', price: 50.25, quantity: 1 },
        { name: 'Product 2', price: 50.25, quantity: 1 }
      ],
      paymentMethod: {
        network: 'ethereum',
        coin: 'usdt'
      }
    };
  }

  async getOrderWithTransaction(orderId) {
    const order = await this.getOrder(orderId);
    
    // Add transaction info if available
    if (order.transactionId) {
      order.transaction = {
        id: order.transactionId,
        hash: order.transactionHash,
        confirmations: order.confirmations || 0,
        status: order.transactionStatus || 'pending'
      };
    }
    
    return order;
  }

  async updateOrderStatus(orderId, status, transactionData = {}) {
    console.log(`Updating order ${orderId} status to ${status}`, transactionData);
    
    // Update order in database
    // Implementation depends on your database
    
    return true;
  }

  async processOrder(orderId) {
    console.log(`Processing order ${orderId}`);
    
    // Implement order fulfillment logic:
    // - Update inventory
    // - Generate shipping labels
    // - Send to fulfillment center
    // - Update order status to 'processing'
    
    return true;
  }
}

module.exports = OrderService;

6. Notification Service

javascript
// services/NotificationService.js
class NotificationService {
  async sendPaymentDetectedNotification(orderId) {
    console.log(`Sending payment detected notification for order ${orderId}`);
    
    // Send email/SMS to customer
    // Implementation depends on your notification provider
  }

  async sendPaymentConfirmedNotification(orderId) {
    console.log(`Sending payment confirmed notification for order ${orderId}`);
    
    // Send confirmation email with order details
    // Update customer dashboard
    // Send internal notifications to fulfillment team
  }

  async sendPaymentFailedNotification(orderId) {
    console.log(`Sending payment failed notification for order ${orderId}`);
    
    // Send failure notification to customer
    // Provide alternative payment options
  }
}

module.exports = NotificationService;

Usage Flow

1. Customer Places Order

javascript
// Customer checkout process
const order = await orderService.createOrder({
  customerId: 'customer_123',
  items: [
    { productId: 'prod_1', quantity: 1, price: 50.25 },
    { productId: 'prod_2', quantity: 1, price: 50.25 }
  ],
  paymentMethod: {
    type: 'crypto',
    network: 'ethereum',
    coin: 'usdt'
  }
});

// Redirect to payment page
window.location.href = `/payment?orderId=${order.id}`;

2. Payment Page Loads

  • Generates unique payment address
  • Displays QR code and payment instructions
  • Starts monitoring for payment

3. Customer Sends Payment

  • Customer scans QR code or copies address
  • Sends exact amount to the address
  • System detects transaction via webhook

4. Payment Confirmation

  • Transaction gets confirmed on blockchain
  • Webhook triggers order processing
  • Customer receives confirmation
  • Order fulfillment begins

Error Handling

Payment Timeout

javascript
// Handle expired payments
setTimeout(async () => {
  const order = await orderService.getOrder(orderId);
  if (order.status === 'awaiting_payment') {
    await orderService.updateOrderStatus(orderId, 'expired');
    await notificationService.sendPaymentExpiredNotification(orderId);
  }
}, 24 * 60 * 60 * 1000); // 24 hours

Insufficient Payment

javascript
// Handle underpayment
if (receivedAmount < expectedAmount) {
  const shortfall = expectedAmount - receivedAmount;
  await orderService.updateOrderStatus(orderId, 'payment_insufficient', {
    receivedAmount,
    expectedAmount,
    shortfall
  });
  
  // Offer options: refund or pay difference
  await notificationService.sendInsufficientPaymentNotification(orderId, shortfall);
}

Overpayment

javascript
// Handle overpayment
if (receivedAmount > expectedAmount) {
  const overpayment = receivedAmount - expectedAmount;
  await orderService.updateOrderStatus(orderId, 'confirmed', {
    overpayment
  });
  
  // Process refund for overpayment
  await paymentService.processRefund(orderId, overpayment);
}

Security Considerations

  1. Webhook Signature Verification: Always verify webhook signatures
  2. Payment Validation: Verify amounts and addresses
  3. Timeout Handling: Implement payment expiration
  4. Idempotency: Handle duplicate webhooks gracefully
  5. Error Logging: Log all payment events for audit

This complete payment flow example demonstrates production-ready cryptocurrency payment processing with proper error handling, monitoring, and customer experience.

Released under the MIT License.