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:
- Create a test invoice
- Use a testnet wallet to send payment
- Verify webhook receives the confirmation
- Check invoice status updates
Best Practices
- Always verify webhook signatures - Never trust unsigned webhooks
- Use idempotency keys - Prevent duplicate transactions
- Handle errors gracefully - Provide clear error messages
- Implement retry logic - Network requests can fail
- Log everything - Helps with debugging and auditing
- 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!
