Skip to main content

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
Add to .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;
Usage:
// 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

  1. Never expose API keys in frontend code
  2. Always verify webhook signatures
  3. Return 200 OK immediately in webhook handlers
  4. Process webhooks asynchronously
  5. Implement idempotency for critical operations
  6. Log all payment-related events
  7. Use environment variables for secrets
  8. Validate all user input
  9. Handle all payment statuses
  10. Test with real payments before going live

Next Steps

I