Building a Crypto Payment Integration: Step-by-Step Tutorial

Integrating cryptocurrency payments into your application doesn't have to be complicated. In this step-by-step tutorial, we'll build a complete crypto payment integration using a modern payment gateway API.

Prerequisites

Before we begin, make sure you have:

  • A basic understanding of REST APIs
  • Node.js installed (or your preferred backend language)
  • An account with a crypto payment gateway
  • API credentials (API key)

Step 1: Set Up Your Project

First, let's create a new Node.js project:

mkdir crypto-payment-integration
cd crypto-payment-integration
npm init -y
npm install axios dotenv

Create a .env file for your API credentials:

API_KEY=your_api_key_here
API_BASE_URL=https://api.fromchain.plus

Step 2: Create the Payment Service

Create a paymentService.js file:

const axios = require('axios');
require('dotenv').config();

class PaymentService {
  constructor() {
    this.apiKey = process.env.API_KEY;
    this.baseURL = process.env.API_BASE_URL;
    this.client = axios.create({
      baseURL: this.baseURL,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  async createInvoice(amount, currency = 'USDT', description = '') {
    try {
      const response = await this.client.post('/invoices', {
        amount: amount.toString(),
        currency,
        description,
        expiresIn: 3600 // 1 hour
      });
      return response.data;
    } catch (error) {
      console.error('Error creating invoice:', error.response?.data || error.message);
      throw error;
    }
  }

  async getInvoice(invoiceId) {
    try {
      const response = await this.client.get(`/invoices/${invoiceId}`);
      return response.data;
    } catch (error) {
      console.error('Error fetching invoice:', error.response?.data || error.message);
      throw error;
    }
  }

  async createWithdrawal(amount, destinationAddress, currency = 'USDT') {
    try {
      const response = await this.client.post('/withdrawals', {
        amount: amount.toString(),
        destinationAddress,
        currency,
        idempotencyKey: `withdrawal-${Date.now()}`
      });
      return response.data;
    } catch (error) {
      console.error('Error creating withdrawal:', error.response?.data || error.message);
      throw error;
    }
  }
}

module.exports = PaymentService;

Step 3: Set Up Webhook Handler

Webhooks are crucial for real-time payment notifications. Create webhookHandler.js:

const crypto = require('crypto');

class WebhookHandler {
  constructor(webhookSecret) {
    this.secret = webhookSecret;
  }

  verifySignature(payload, signature) {
    const hmac = crypto.createHmac('sha256', this.secret);
    const digest = hmac.update(JSON.stringify(payload)).digest('hex');
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(digest)
    );
  }

  handleWebhook(payload, signature) {
    // Verify webhook signature
    if (!this.verifySignature(payload, signature)) {
      throw new Error('Invalid webhook signature');
    }

    const { event, data } = payload;

    switch (event) {
      case 'invoice.created':
        this.handleInvoiceCreated(data);
        break;
      case 'invoice.confirmed':
        this.handleInvoiceConfirmed(data);
        break;
      case 'invoice.expired':
        this.handleInvoiceExpired(data);
        break;
      case 'withdrawal.completed':
        this.handleWithdrawalCompleted(data);
        break;
      default:
        console.log('Unknown event type:', event);
    }
  }

  handleInvoiceCreated(data) {
    console.log('Invoice created:', data.invoiceId);
    // Update your database, send confirmation email, etc.
  }

  handleInvoiceConfirmed(data) {
    console.log('Invoice confirmed:', data.invoiceId);
    // Mark order as paid, fulfill order, etc.
    this.fulfillOrder(data.invoiceId);
  }

  handleInvoiceExpired(data) {
    console.log('Invoice expired:', data.invoiceId);
    // Cancel order, notify customer, etc.
  }

  handleWithdrawalCompleted(data) {
    console.log('Withdrawal completed:', data.withdrawalId);
    // Update balance, send notification, etc.
  }

  async fulfillOrder(invoiceId) {
    // Your order fulfillment logic here
    console.log(`Fulfilling order for invoice ${invoiceId}`);
  }
}

module.exports = WebhookHandler;

Step 4: Create Express Server

Create a simple Express server to handle payment requests:

const express = require('express');
const PaymentService = require('./paymentService');
const WebhookHandler = require('./webhookHandler');

const app = express();
app.use(express.json());

const paymentService = new PaymentService();
const webhookHandler = new WebhookHandler(process.env.WEBHOOK_SECRET);

// Create invoice endpoint
app.post('/api/payments/create', async (req, res) => {
  try {
    const { amount, description } = req.body;
    const invoice = await paymentService.createInvoice(amount, 'USDT', description);
    
    res.json({
      success: true,
      invoice: {
        id: invoice.id,
        depositAddress: invoice.depositAddress,
        amount: invoice.amount,
        qrCode: invoice.qrCode,
        expiresAt: invoice.expiresAt
      }
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

// Check invoice status
app.get('/api/payments/:invoiceId', async (req, res) => {
  try {
    const invoice = await paymentService.getInvoice(req.params.invoiceId);
    res.json({ success: true, invoice });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

// Webhook endpoint
app.post('/api/webhooks', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  
  try {
    webhookHandler.handleWebhook(req.body, signature);
    res.status(200).json({ received: true });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(400).json({ error: error.message });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Step 5: Frontend Integration

Create a simple HTML page to interact with your API:

<!DOCTYPE html>
<html>
<head>
  <title>Crypto Payment Demo</title>
</head>
<body>
  <h1>Pay with USDT</h1>
  <form id="paymentForm">
    <input type="number" id="amount" placeholder="Amount" step="0.01" required>
    <input type="text" id="description" placeholder="Description">
    <button type="submit">Create Payment</button>
  </form>

  <div id="invoice" style="display: none;">
    <h2>Payment Details</h2>
    <p>Amount: <span id="invoiceAmount"></span> USDT</p>
    <p>Address: <code id="invoiceAddress"></code></p>
    <img id="qrCode" alt="QR Code">
    <p>Status: <span id="invoiceStatus"></span></p>
  </div>

  <script>
    const form = document.getElementById('paymentForm');
    const invoiceDiv = document.getElementById('invoice');

    form.addEventListener('submit', async (e) => {
      e.preventDefault();
      
      const amount = document.getElementById('amount').value;
      const description = document.getElementById('description').value;

      const response = await fetch('/api/payments/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ amount, description })
      });

      const data = await response.json();
      
      if (data.success) {
        document.getElementById('invoiceAmount').textContent = data.invoice.amount;
        document.getElementById('invoiceAddress').textContent = data.invoice.depositAddress;
        document.getElementById('qrCode').src = data.invoice.qrCode;
        invoiceDiv.style.display = 'block';
        
        // Poll for status updates
        pollInvoiceStatus(data.invoice.id);
      }
    });

    async function pollInvoiceStatus(invoiceId) {
      const interval = setInterval(async () => {
        const response = await fetch(`/api/payments/${invoiceId}`);
        const data = await response.json();
        
        if (data.success) {
          document.getElementById('invoiceStatus').textContent = data.invoice.status;
          
          if (data.invoice.status === 'CONFIRMED') {
            clearInterval(interval);
            alert('Payment confirmed!');
          }
        }
      }, 5000); // Poll every 5 seconds
    }
  </script>
</body>
</html>

Step 6: Error Handling

Add proper error handling:

class PaymentError extends Error {
  constructor(message, code, statusCode) {
    super(message);
    this.name = 'PaymentError';
    this.code = code;
    this.statusCode = statusCode;
  }
}

// In your service
async createInvoice(amount, currency, description) {
  try {
    // ... existing code
  } catch (error) {
    if (error.response) {
      throw new PaymentError(
        error.response.data.message,
        error.response.data.code,
        error.response.status
      );
    }
    throw new PaymentError('Network error', 'NETWORK_ERROR', 500);
  }
}

Step 7: Testing

Test your integration:

  1. Create a test invoice
  2. Use a testnet wallet to send payment
  3. Verify webhook receives the confirmation
  4. Check invoice status updates

Best Practices

  1. Always verify webhook signatures - Never trust unsigned webhooks
  2. Use idempotency keys - Prevent duplicate transactions
  3. Handle errors gracefully - Provide clear error messages
  4. Implement retry logic - Network requests can fail
  5. Log everything - Helps with debugging and auditing
  6. Test thoroughly - Use testnet before going live

Next Steps

  • Add database persistence for invoices
  • Implement user authentication
  • Add admin dashboard
  • Set up monitoring and alerts
  • Add more payment methods

Conclusion

Building a crypto payment integration is straightforward with modern APIs. This tutorial covered the basics, but you can extend it with additional features like recurring payments, subscription management, and advanced analytics.

Ready to integrate crypto payments? Get started with FromChain and start accepting USDT payments in minutes!