Prerequisites
- Node.js 14+ installed
- npm or yarn package manager
- Card2Crypto account with API key
Quick Start
1. Install Dependencies
Copy
npm install express node-fetch dotenv
2. Set Up Environment Variables
Create a.env
file in your project root:
Copy
# .env
CARD2CRYPTO_API_KEY=c2c_live_your_api_key_here
CARD2CRYPTO_WEBHOOK_SECRET=your_webhook_secret_here
PORT=3000
.gitignore
:
Copy
.env
node_modules/
3. Create Basic Server
Copy
// 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
Copy
// 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
Copy
// 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;
Copy
// 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:Copy
// 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
Copy
// 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
Copy
npm install @prisma/client
npm install -D prisma
npx prisma init
Database Schema
Copy
// 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
Copy
// 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
Copy
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
Copy
# 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
Copy
// 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