Prerequisites
- PHP 7.4 or higher
- cURL extension enabled
- Card2Crypto account with API key
Quick Start
1. 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
.gitignore
:
Copy
.env
2. Load Environment Variables
Copy
<?php
// config.php
// Load .env file
if (file_exists(__DIR__ . '/.env')) {
$lines = file(__DIR__ . '/.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos($line, '=') !== false && strpos($line, '#') !== 0) {
list($name, $value) = explode('=', $line, 2);
putenv(trim($name) . '=' . trim($value));
}
}
}
define('CARD2CRYPTO_API_KEY', getenv('CARD2CRYPTO_API_KEY'));
define('CARD2CRYPTO_WEBHOOK_SECRET', getenv('CARD2CRYPTO_WEBHOOK_SECRET'));
define('CARD2CRYPTO_API_URL', 'https://card2crypto.cc/api/v1');
3. Create Card2Crypto Class
Copy
<?php
// lib/Card2Crypto.php
class Card2Crypto {
private $apiKey;
private $baseUrl = 'https://card2crypto.cc/api/v1';
public function __construct($apiKey) {
$this->apiKey = $apiKey;
}
public function createPayment($data) {
$endpoint = $this->baseUrl . '/payments';
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
throw new Exception('cURL error: ' . curl_error($ch));
}
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode !== 200 || !$result['success']) {
throw new Exception($result['error'] ?? 'Payment creation failed');
}
return $result['data'];
}
public function getPayment($paymentId) {
$endpoint = $this->baseUrl . '/payments/' . $paymentId;
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiKey
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
throw new Exception('cURL error: ' . curl_error($ch));
}
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode !== 200 || !$result['success']) {
throw new Exception($result['error'] ?? 'Payment retrieval failed');
}
return $result['data'];
}
public function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', json_encode($payload), $secret);
return hash_equals($expectedSignature, $signature);
}
}
Creating Payments
Basic Payment Creation
Copy
<?php
// create-payment.php
require_once 'config.php';
require_once 'lib/Card2Crypto.php';
$c2c = new Card2Crypto(CARD2CRYPTO_API_KEY);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$payment = $c2c->createPayment([
'amount' => (int)$_POST['amount'], // Amount in cents
'currency' => 'usd',
'return_url' => 'https://yoursite.com/payment-complete.php',
'customer_email' => $_POST['email'] ?? null,
'customer_name' => $_POST['name'] ?? null,
'metadata' => [
'order_id' => $_POST['order_id'] ?? null
]
]);
// Redirect to checkout
header('Location: ' . $payment['checkout_url']);
exit;
} catch (Exception $e) {
echo 'Error: ' . htmlspecialchars($e->getMessage());
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Create Payment</title>
</head>
<body>
<h1>Create Payment</h1>
<form method="POST">
<label>
Amount (in cents):
<input type="number" name="amount" required min="50">
</label>
<br>
<label>
Email:
<input type="email" name="email">
</label>
<br>
<label>
Name:
<input type="text" name="name">
</label>
<br>
<label>
Order ID:
<input type="text" name="order_id">
</label>
<br>
<button type="submit">Pay Now</button>
</form>
</body>
</html>
Payment with Database
Copy
<?php
// create-order.php
require_once 'config.php';
require_once 'lib/Card2Crypto.php';
require_once 'lib/Database.php';
$db = new PDO('mysql:host=localhost;dbname=mydb', 'username', 'password');
$c2c = new Card2Crypto(CARD2CRYPTO_API_KEY);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
// Create order in database
$stmt = $db->prepare('
INSERT INTO orders (user_id, product_name, amount, status, created_at)
VALUES (?, ?, ?, ?, NOW())
');
$stmt->execute([
$_POST['user_id'],
$_POST['product_name'],
$_POST['amount'],
'pending'
]);
$orderId = $db->lastInsertId();
// Create payment
$payment = $c2c->createPayment([
'amount' => (int)$_POST['amount'],
'currency' => 'usd',
'return_url' => 'https://yoursite.com/order-complete.php?order_id=' . $orderId,
'metadata' => [
'order_id' => $orderId,
'user_id' => $_POST['user_id']
]
]);
// Update order with payment ID
$stmt = $db->prepare('UPDATE orders SET payment_id = ? WHERE id = ?');
$stmt->execute([$payment['id'], $orderId]);
// Redirect to checkout
header('Location: ' . $payment['checkout_url']);
exit;
} catch (Exception $e) {
echo 'Error: ' . htmlspecialchars($e->getMessage());
}
}
Handling Return URL
Copy
<?php
// payment-complete.php
require_once 'config.php';
require_once 'lib/Card2Crypto.php';
$c2c = new Card2Crypto(CARD2CRYPTO_API_KEY);
if (!isset($_GET['payment_id'])) {
http_response_code(400);
die('Missing payment ID');
}
try {
$payment = $c2c->getPayment($_GET['payment_id']);
?>
<!DOCTYPE html>
<html>
<head>
<title>Payment Status</title>
</head>
<body>
<h1>Payment Status</h1>
<?php if ($payment['status'] === 'completed'): ?>
<h2>Payment Successful!</h2>
<p>Payment ID: <?= htmlspecialchars($payment['id']) ?></p>
<p>Amount: $<?= number_format($payment['amount'] / 100, 2) ?></p>
<p>Status: Completed</p>
<?php elseif ($payment['status'] === 'pending'): ?>
<h2>Payment Processing</h2>
<p>Your payment is still being processed...</p>
<?php else: ?>
<h2>Payment Failed</h2>
<p>Your payment could not be processed. Please try again.</p>
<?php endif; ?>
</body>
</html>
<?php
} catch (Exception $e) {
http_response_code(500);
echo 'Error: ' . htmlspecialchars($e->getMessage());
}
Webhook Integration
Basic Webhook Handler
Copy
<?php
// webhook.php
require_once 'config.php';
require_once 'lib/Card2Crypto.php';
$c2c = new Card2Crypto(CARD2CRYPTO_API_KEY);
// Get raw POST body
$payload = json_decode(file_get_contents('php://input'), true);
$signature = $_SERVER['HTTP_X_CARD2CRYPTO_SIGNATURE'] ?? '';
// Verify signature
if (!$c2c->verifyWebhookSignature($payload, $signature, CARD2CRYPTO_WEBHOOK_SECRET)) {
http_response_code(401);
die('Invalid signature');
}
// Return 200 OK immediately
http_response_code(200);
echo 'OK';
// Log webhook
error_log('Webhook received: ' . $payload['event']);
// Process webhook based on event type
switch ($payload['event']) {
case 'payment.completed':
handlePaymentCompleted($payload['payment']);
break;
case 'payment.failed':
handlePaymentFailed($payload['payment']);
break;
case 'payment.refunded':
handlePaymentRefunded($payload['payment']);
break;
default:
error_log('Unknown event type: ' . $payload['event']);
}
function handlePaymentCompleted($payment) {
$orderId = $payment['metadata']['order_id'] ?? null;
if (!$orderId) {
error_log('No order ID in payment metadata');
return;
}
try {
$db = new PDO('mysql:host=localhost;dbname=mydb', 'username', 'password');
// Update order status
$stmt = $db->prepare('UPDATE orders SET status = ? WHERE id = ?');
$stmt->execute(['paid', $orderId]);
error_log("Order $orderId marked as paid");
// Grant access to product
// grantAccess($payment['customer_email']);
// Send confirmation email
// sendEmail($payment['customer_email'], 'Payment confirmed!');
} catch (Exception $e) {
error_log('Error processing payment: ' . $e->getMessage());
}
}
function handlePaymentFailed($payment) {
$orderId = $payment['metadata']['order_id'] ?? null;
if ($orderId) {
try {
$db = new PDO('mysql:host=localhost;dbname=mydb', 'username', 'password');
$stmt = $db->prepare('UPDATE orders SET status = ? WHERE id = ?');
$stmt->execute(['payment_failed', $orderId]);
error_log("Order $orderId marked as failed");
} catch (Exception $e) {
error_log('Error: ' . $e->getMessage());
}
}
}
function handlePaymentRefunded($payment) {
$orderId = $payment['metadata']['order_id'] ?? null;
if ($orderId) {
try {
$db = new PDO('mysql:host=localhost;dbname=mydb', 'username', 'password');
$stmt = $db->prepare('UPDATE orders SET status = ? WHERE id = ?');
$stmt->execute(['refunded', $orderId]);
// Revoke access
// revokeAccess($payment['customer_email']);
error_log("Order $orderId refunded");
} catch (Exception $e) {
error_log('Error: ' . $e->getMessage());
}
}
}
Complete Example with Database
Copy
<?php
// Database schema (MySQL)
/*
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
product_name VARCHAR(255) NOT NULL,
amount INT NOT NULL,
status VARCHAR(50) DEFAULT 'pending',
payment_id VARCHAR(255) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX(user_id),
INDEX(payment_id)
);
*/
// lib/Database.php
class Database {
private $pdo;
public function __construct() {
$this->pdo = new PDO(
'mysql:host=localhost;dbname=mydb',
getenv('DB_USERNAME'),
getenv('DB_PASSWORD'),
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
public function createOrder($userId, $productName, $amount) {
$stmt = $this->pdo->prepare('
INSERT INTO orders (user_id, product_name, amount, status)
VALUES (?, ?, ?, ?)
');
$stmt->execute([$userId, $productName, $amount, 'pending']);
return $this->pdo->lastInsertId();
}
public function getOrder($id) {
$stmt = $this->pdo->prepare('SELECT * FROM orders WHERE id = ?');
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function updateOrderPaymentId($orderId, $paymentId) {
$stmt = $this->pdo->prepare('UPDATE orders SET payment_id = ? WHERE id = ?');
$stmt->execute([$paymentId, $orderId]);
}
public function updateOrderStatus($orderId, $status) {
$stmt = $this->pdo->prepare('UPDATE orders SET status = ? WHERE id = ?');
$stmt->execute([$status, $orderId]);
}
}
// checkout.php
require_once 'config.php';
require_once 'lib/Card2Crypto.php';
require_once 'lib/Database.php';
$c2c = new Card2Crypto(CARD2CRYPTO_API_KEY);
$db = new Database();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
// Create order
$orderId = $db->createOrder(
$_POST['user_id'],
$_POST['product_name'],
$_POST['amount']
);
// Create payment
$payment = $c2c->createPayment([
'amount' => (int)$_POST['amount'],
'currency' => 'usd',
'return_url' => 'https://yoursite.com/complete.php?order_id=' . $orderId,
'metadata' => [
'order_id' => $orderId,
'user_id' => $_POST['user_id']
]
]);
// Update order with payment ID
$db->updateOrderPaymentId($orderId, $payment['id']);
// Redirect to checkout
header('Location: ' . $payment['checkout_url']);
exit;
} catch (Exception $e) {
http_response_code(500);
echo 'Error: ' . htmlspecialchars($e->getMessage());
}
}
Error Handling
Copy
<?php
// lib/Card2CryptoException.php
class Card2CryptoException extends Exception {
private $statusCode;
private $response;
public function __construct($message, $statusCode = 500, $response = null) {
parent::__construct($message);
$this->statusCode = $statusCode;
$this->response = $response;
}
public function getStatusCode() {
return $this->statusCode;
}
public function getResponse() {
return $this->response;
}
}
// Updated Card2Crypto class with better error handling
class Card2Crypto {
// ... existing code ...
public function createPayment($data) {
$endpoint = $this->baseUrl . '/payments';
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
throw new Card2CryptoException('Network error: ' . curl_error($ch));
}
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode !== 200) {
throw new Card2CryptoException(
$result['error'] ?? 'Payment creation failed',
$httpCode,
$result
);
}
if (!$result['success']) {
throw new Card2CryptoException($result['error'] ?? 'Unknown error', $httpCode);
}
return $result['data'];
}
}
// Usage with error handling
try {
$payment = $c2c->createPayment([
'amount' => 10000,
'currency' => 'usd',
'return_url' => 'https://yoursite.com/success'
]);
header('Location: ' . $payment['checkout_url']);
exit;
} catch (Card2CryptoException $e) {
error_log('Card2Crypto error (' . $e->getStatusCode() . '): ' . $e->getMessage());
http_response_code($e->getStatusCode());
echo 'Payment error: ' . htmlspecialchars($e->getMessage());
} catch (Exception $e) {
error_log('Unexpected error: ' . $e->getMessage());
http_response_code(500);
echo 'An unexpected error occurred';
}
Testing
Local Testing
Copy
# Install PHP built-in server
php -S localhost:8000
# Use ngrok to expose locally
ngrok http 8000
# Use ngrok URL in webhook settings
# https://abc123.ngrok.io/webhook.php
Best Practices
- Never expose API keys in frontend code
- Always verify webhook signatures
- Use prepared statements to prevent SQL injection
- Validate and sanitize all user input
- Log all payment-related events
- Handle errors gracefully
- Use HTTPS in production
- Keep dependencies updated
- Test with real payments before going live
- Implement proper error logging
Security Checklist
- Store API keys in environment variables
- Use HTTPS for all payment pages
- Verify webhook signatures
- Sanitize all user input
- Use prepared statements for database queries
- Enable PHP error logging
- Set appropriate file permissions
- Keep PHP version updated
- Use secure session handling