Skip to main content

PHP

Integrate Coinsnap into any PHP application — Laravel, Symfony, or plain PHP.


Environment variables

.env
COINSNAP_API_KEY=cs_live_xxxxxxxxxxxxxxxxxxxx
COINSNAP_STORE_ID=7CVKXVxM7BtbkMEie8yoNeR8EetExpQhJUYEFY3ftfwR
COINSNAP_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxx

Create an invoice

checkout.php
<?php
$storeId = getenv('COINSNAP_STORE_ID');
$apiKey = getenv('COINSNAP_API_KEY');

$ch = curl_init("https://app.coinsnap.io/api/v1/stores/{$storeId}/invoices");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 49.99,
'currency' => 'EUR',
'orderId' => 'order-' . $orderId,
'buyerEmail' => $customer->email,
'redirectUrl' => 'https://yoursite.com/orders/' . $orderId . '/success',
]),
]);

$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$invoice = json_decode($response, true);

if ($code >= 400) {
throw new RuntimeException('Coinsnap error ' . $code . ': ' . ($invoice['message'] ?? ''));
}

// Save invoice ID and redirect customer
$db->updateOrder($orderId, ['invoice_id' => $invoice['id']]);
header('Location: ' . $invoice['checkoutLink']);
exit;

Handle webhooks

webhook.php
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_COINSNAP_SIG'] ?? '';
$secret = getenv('COINSNAP_WEBHOOK_SECRET');

if (!hash_equals('sha256=' . hash_hmac('sha256', $payload, $secret), $signature)) {
http_response_code(401);
exit('Unauthorized');
}

$event = json_decode($payload, true);
$order_id = $event['metadata']['orderId'] ?? null;

switch ($event['type']) {
case 'New':
// Payment attempt started — no action needed
break;

case 'Processing':
// Full amount received on-chain, waiting for block confirmation
$db->updateOrder($order_id, ['status' => 'processing']);
break;

case 'Settled':
// Idempotency guard
$order = $db->fetchOne('SELECT status FROM orders WHERE id = ?', [$order_id]);
if (!$order || $order['status'] === 'paid') break;

$db->updateOrder($order_id, ['status' => 'paid', 'paid_at' => date('Y-m-d H:i:s')]);

if ($event['additionalStatus'] === 'Overpaid') {
// Customer paid more than the invoice amount — decide on refund policy
notifyMerchantOfOverpayment($order_id, $event);
}
if ($event['additionalStatus'] === 'PaidAfterExpiration') {
// Payment received after the invoice expired — review manually
flagForManualReview($order_id);
break;
}

triggerFulfillment($order_id);
break;

case 'Expired':
if ($event['additionalStatus'] === 'Underpaid') {
// Invoice expired with partial payment — contact customer
$db->updateOrder($order_id, ['status' => 'underpaid']);
notifyCustomerOfUnderpayment($order_id);
} else {
$db->updateOrder($order_id, ['status' => 'expired']);
}
break;

case 'Invalid':
// Invoice manually marked invalid or otherwise rejected
$db->updateOrder($order_id, ['status' => 'cancelled']);
break;
}

http_response_code(200);
echo 'OK';

Webhook statuses and suggested actions:

Laravel

app/Http/Controllers/PaymentController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class PaymentController extends Controller
{
public function checkout(Request $request)
{
$order = Order::findOrFail($request->order_id);

$response = Http::withHeaders([
'x-api-key' => config('services.coinsnap.api_key'),
])->post(
'https://app.coinsnap.io/api/v1/stores/' . config('services.coinsnap.store_id') . '/invoices',
[
'amount' => $order->total,
'currency' => 'EUR',
'orderId' => (string) $order->id,
'buyerEmail' => $order->customer->email,
'redirectUrl' => route('orders.show', $order),
]
);

$invoice = $response->throw()->json();
$order->update(['coinsnap_invoice_id' => $invoice['id']]);

return redirect($invoice['checkoutLink']);
}

public function webhook(Request $request)
{
$payload = $request->getContent();
$signature = $request->header('X-Coinsnap-Sig');
$secret = config('services.coinsnap.webhook_secret');

if (!hash_equals('sha256=' . hash_hmac('sha256', $payload, $secret), $signature)) {
abort(401);
}

$event = $request->json()->all();
$order = Order::where('coinsnap_invoice_id', $event['invoiceId'])->firstOrFail();

match ($event['type']) {
'Settled' => $order->update(['status' => 'paid']),
'Expired' => $order->update(['status' => 'expired']),
default => null,
};

return response('OK', 200);
}
}
config/services.php (add)
'coinsnap' => [
'api_key' => env('COINSNAP_API_KEY'),
'store_id' => env('COINSNAP_STORE_ID'),
'webhook_secret' => env('COINSNAP_WEBHOOK_SECRET'),
],