Prerequisites
- Node.js 14+ installed
- npm or yarn package manager
- Card2Crypto account with API key
Quick Start
1. Install Dependencies
npm install express node-fetch dotenv
2. Set Up Environment Variables
Create a.env file in your project root:
# .env
CARD2CRYPTO_API_KEY=c2c_live_your_api_key_here
CARD2CRYPTO_WEBHOOK_SECRET=your_webhook_secret_here
PORT=3000
.gitignore:
.env
node_modules/
3. Create Basic Server
// server.js
require('dotenv').config();
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
const API_KEY = process.env.CARD2CRYPTO_API_KEY;
const WEBHOOK_SECRET = process.env.CARD2CRYPTO_WEBHOOK_SECRET;
// Routes will go here
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Creating Payments
Basic Payment Creation
// server.js
app.post('/create-payment', async (req, res) => {
try {
const { amount, email, name, orderId } = req.body;
// Create payment via Card2Crypto API
const response = await fetch('https://card2crypto.cc/api/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: amount, // Amount in cents
currency: 'usd',
return_url: `${req.protocol}://${req.get('host')}/payment-complete`,
customer_email: email,
customer_name: name,
metadata: {
order_id: orderId
}
})
});
const payment = await response.json();
if (payment.success) {
// Redirect customer to checkout
res.redirect(payment.data.checkout_url);
} else {
res.status(400).json({ error: payment.error });
}
} catch (error) {
console.error('Payment creation failed:', error);
res.status(500).json({ error: 'Failed to create payment' });
}
});
Reusable Payment Helper
// lib/card2crypto.js
const fetch = require('node-fetch');
class Card2Crypto {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://card2crypto.cc/api/v1';
}
async createPayment({ amount, currency = 'usd', returnUrl, customerEmail, customerName, metadata }) {
const response = await fetch(`${this.baseUrl}/payments`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount,
currency,
return_url: returnUrl,
customer_email: customerEmail,
customer_name: customerName,
metadata
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Payment creation failed');
}
return data.data;
}
async getPayment(paymentId) {
const response = await fetch(`${this.baseUrl}/payments/${paymentId}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`
}
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Payment retrieval failed');
}
return data.data;
}
}
module.exports = Card2Crypto;
// server.js
const Card2Crypto = require('./lib/card2crypto');
const c2c = new Card2Crypto(process.env.CARD2CRYPTO_API_KEY);
app.post('/create-payment', async (req, res) => {
try {
const payment = await c2c.createPayment({
amount: req.body.amount,
returnUrl: `${req.protocol}://${req.get('host')}/payment-complete`,
customerEmail: req.body.email,
customerName: req.body.name,
metadata: {
order_id: req.body.orderId
}
});
res.redirect(payment.checkout_url);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Handling Return URL
When customer completes payment, they’re redirected to your return URL:// server.js
app.get('/payment-complete', async (req, res) => {
const paymentId = req.query.payment_id;
if (!paymentId) {
return res.status(400).send('Missing payment ID');
}
try {
// Retrieve payment to verify status
const payment = await c2c.getPayment(paymentId);
if (payment.status === 'completed') {
// Payment successful
res.send(`
<html>
<head><title>Payment Successful</title></head>
<body>
<h1>Thank you for your payment!</h1>
<p>Payment ID: ${payment.id}</p>
<p>Amount: $${(payment.amount / 100).toFixed(2)}</p>
</body>
</html>
`);
} else if (payment.status === 'pending') {
res.send('Payment is still processing...');
} else {
res.send('Payment failed. Please try again.');
}
} catch (error) {
console.error('Error retrieving payment:', error);
res.status(500).send('Error verifying payment');
}
});
Webhook Integration
Webhooks provide real-time notifications when payment status changes.Basic Webhook Handler
// server.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/card2crypto', (req, res) => {
const signature = req.headers['x-card2crypto-signature'];
// Verify webhook signature
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}
// Return 200 immediately
res.status(200).send('OK');
// Process webhook asynchronously
processWebhook(req.body).catch(error => {
console.error('Error processing webhook:', error);
});
});
async function processWebhook(event) {
console.log('Webhook received:', event.event);
switch (event.event) {
case 'payment.completed':
await handlePaymentCompleted(event.payment);
break;
case 'payment.failed':
await handlePaymentFailed(event.payment);
break;
case 'payment.refunded':
await handlePaymentRefunded(event.payment);
break;
default:
console.log('Unknown event type:', event.event);
}
}
async function handlePaymentCompleted(payment) {
const orderId = payment.metadata.order_id;
console.log(`Payment completed for order ${orderId}`);
// Update your database
// await db.orders.update({
// where: { id: orderId },
// data: { status: 'paid', payment_id: payment.id }
// });
// Grant access to product/service
// await grantAccess(payment.customer_email);
// Send confirmation email
// await sendEmail(payment.customer_email, 'Payment confirmed!');
}
async function handlePaymentFailed(payment) {
const orderId = payment.metadata.order_id;
console.log(`Payment failed for order ${orderId}`);
// Update order status
// await db.orders.update({
// where: { id: orderId },
// data: { status: 'payment_failed' }
// });
}
async function handlePaymentRefunded(payment) {
const orderId = payment.metadata.order_id;
console.log(`Payment refunded for order ${orderId}`);
// Revoke access
// await revokeAccess(payment.customer_email);
// Update order status
// await db.orders.update({
// where: { id: orderId },
// data: { status: 'refunded' }
// });
}
Complete Example with Database
Here’s a complete example using Prisma ORM:Install Prisma
npm install @prisma/client
npm install -D prisma
npx prisma init
Database Schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Order {
id String @id @default(uuid())
userId String
productName String
amount Int // Amount in cents
status String @default("pending")
paymentId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([paymentId])
}
Full Integration
// server.js
require('dotenv').config();
const express = require('express');
const { PrismaClient } = require('@prisma/client');
const Card2Crypto = require('./lib/card2crypto');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const prisma = new PrismaClient();
const c2c = new Card2Crypto(process.env.CARD2CRYPTO_API_KEY);
// Create order and initiate payment
app.post('/orders', async (req, res) => {
try {
const { userId, productName, amount } = req.body;
// Create order in database
const order = await prisma.order.create({
data: {
userId,
productName,
amount,
status: 'pending'
}
});
// Create payment
const payment = await c2c.createPayment({
amount: amount,
returnUrl: `${req.protocol}://${req.get('host')}/orders/${order.id}/complete`,
metadata: {
order_id: order.id,
user_id: userId
}
});
// Update order with payment ID
await prisma.order.update({
where: { id: order.id },
data: { paymentId: payment.id }
});
res.json({
order_id: order.id,
checkout_url: payment.checkout_url
});
} catch (error) {
console.error('Error creating order:', error);
res.status(500).json({ error: 'Failed to create order' });
}
});
// Payment completion page
app.get('/orders/:id/complete', async (req, res) => {
try {
const order = await prisma.order.findUnique({
where: { id: req.params.id }
});
if (!order) {
return res.status(404).send('Order not found');
}
const payment = await c2c.getPayment(order.paymentId);
if (payment.status === 'completed') {
res.send(`
<h1>Order Complete!</h1>
<p>Order ID: ${order.id}</p>
<p>Amount: $${(order.amount / 100).toFixed(2)}</p>
<p>Status: Paid</p>
`);
} else {
res.send('Payment is still processing...');
}
} catch (error) {
console.error('Error:', error);
res.status(500).send('Error retrieving order');
}
});
// Webhook handler
app.post('/webhooks/card2crypto', async (req, res) => {
const signature = req.headers['x-card2crypto-signature'];
// Verify signature
const expectedSignature = crypto
.createHmac('sha256', process.env.CARD2CRYPTO_WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).send('Invalid signature');
}
res.status(200).send('OK');
// Process webhook
const { event, payment } = req.body;
if (event === 'payment.completed') {
await prisma.order.update({
where: { id: payment.metadata.order_id },
data: { status: 'paid' }
});
console.log(`Order ${payment.metadata.order_id} marked as paid`);
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Error Handling
Robust Error Handling
class Card2CryptoError extends Error {
constructor(message, statusCode, response) {
super(message);
this.name = 'Card2CryptoError';
this.statusCode = statusCode;
this.response = response;
}
}
async function createPayment(data) {
try {
const response = await fetch('https://card2crypto.cc/api/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (!response.ok) {
throw new Card2CryptoError(
result.error || 'Payment creation failed',
response.status,
result
);
}
return result.data;
} catch (error) {
if (error instanceof Card2CryptoError) {
throw error;
}
throw new Card2CryptoError('Network error', 500, null);
}
}
// Usage
app.post('/create-payment', async (req, res) => {
try {
const payment = await createPayment({
amount: req.body.amount,
currency: 'usd',
return_url: 'https://yoursite.com/success'
});
res.json(payment);
} catch (error) {
if (error instanceof Card2CryptoError) {
console.error(`Card2Crypto API error (${error.statusCode}):`, error.message);
res.status(error.statusCode).json({ error: error.message });
} else {
console.error('Unexpected error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
});
Testing
Local Testing with ngrok
# Install ngrok
npm install -g ngrok
# Start your server
node server.js
# In another terminal, start ngrok
ngrok http 3000
# Use the ngrok URL in your webhook settings
# https://abc123.ngrok.io/webhooks/card2crypto
Unit Tests
// tests/card2crypto.test.js
const Card2Crypto = require('../lib/card2crypto');
describe('Card2Crypto', () => {
const c2c = new Card2Crypto(process.env.CARD2CRYPTO_API_KEY);
test('creates payment', async () => {
const payment = await c2c.createPayment({
amount: 10000,
returnUrl: 'https://test.com/success',
metadata: { test: true }
});
expect(payment.id).toBeDefined();
expect(payment.amount).toBe(10000);
expect(payment.status).toBe('pending');
});
test('retrieves payment', async () => {
const created = await c2c.createPayment({
amount: 5000,
returnUrl: 'https://test.com/success'
});
const retrieved = await c2c.getPayment(created.id);
expect(retrieved.id).toBe(created.id);
expect(retrieved.amount).toBe(5000);
});
});
Best Practices
- Never expose API keys in frontend code
- Always verify webhook signatures
- Return 200 OK immediately in webhook handlers
- Process webhooks asynchronously
- Implement idempotency for critical operations
- Log all payment-related events
- Use environment variables for secrets
- Validate all user input
- Handle all payment statuses
- Test with real payments before going live
Next Steps
API Reference
Complete API documentation
Webhook Security
Learn about webhook verification
PHP Guide
Integration guide for PHP
Testing Guide
How to test your integration