Files
kashilo/docs/pow-server/btcpay-webhook.php

154 lines
4.0 KiB
PHP

<?php
require __DIR__ . '/config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
exit;
}
$rawBody = file_get_contents('php://input');
if (!$rawBody) {
http_response_code(400);
echo json_encode(['error' => 'Empty body']);
exit;
}
$payload = json_decode($rawBody, true);
if ($payload === null) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}
// Verify HMAC signature if secret is configured
if (BTCPAY_WEBHOOK_SECRET) {
$sigHeader = $_SERVER['HTTP_BTCPAY_SIG'] ?? '';
$expectedSig = 'sha256=' . hash_hmac('sha256', $rawBody, BTCPAY_WEBHOOK_SECRET);
if (!hash_equals($expectedSig, $sigHeader)) {
http_response_code(403);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
}
$type = $payload['type'] ?? null;
if (!$type) {
http_response_code(400);
echo json_encode(['error' => 'Missing type']);
exit;
}
// Only handle settled invoices
if ($type !== 'InvoiceSettled' && $type !== 'InvoicePaymentSettled') {
echo json_encode(['ok' => true, 'action' => 'ignored', 'type' => $type]);
exit;
}
// Read listingId directly from webhook payload metadata
$listingId = $payload['metadata']['listingId'] ?? null;
if (!$listingId) {
echo json_encode(['ok' => true, 'action' => 'skipped', 'reason' => 'No listingId in metadata']);
exit;
}
// Update listing in Directus: publish + set paid
$expiresAt = date('c', strtotime('+30 days'));
$now = date('c');
$directusPayload = json_encode([
'status' => 'published',
'payment_status' => 'paid',
'paid_at' => $now,
'expires_at' => $expiresAt,
]);
$directusUrl = DIRECTUS_URL . '/items/listings/' . urlencode($listingId);
$ch = curl_init($directusUrl);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => $directusPayload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . DIRECTUS_TOKEN,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
]);
$directusResponse = curl_exec($ch);
$directusStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($directusResponse === false || $curlError) {
http_response_code(502);
echo json_encode([
'error' => 'Connection to Directus failed',
'listingId' => $listingId,
'curl_error' => $curlError,
]);
exit;
}
if ($directusStatus >= 400) {
http_response_code(502);
echo json_encode([
'error' => 'Directus returned error',
'status' => $directusStatus,
'listingId' => $listingId,
'response' => substr($directusResponse, 0, 500),
]);
exit;
}
// Fetch listing to get owner
$getUrl = DIRECTUS_URL . '/items/listings/' . urlencode($listingId) . '?fields=user_created';
$gch = curl_init($getUrl);
curl_setopt_array($gch, [
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . DIRECTUS_TOKEN,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$getResponse = curl_exec($gch);
curl_close($gch);
$listingData = json_decode($getResponse, true);
$userCreated = $listingData['data']['user_created'] ?? null;
// Create notification for listing owner
if ($userCreated) {
$notifPayload = json_encode([
'user_hash' => $userCreated,
'type' => 'listing_published',
'reference_id' => $listingId,
'read' => false,
]);
$notifUrl = DIRECTUS_URL . '/items/notifications';
$nch = curl_init($notifUrl);
curl_setopt_array($nch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $notifPayload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . DIRECTUS_TOKEN,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
curl_exec($nch);
curl_close($nch);
}
echo json_encode(['ok' => true, 'listingId' => $listingId, 'action' => 'published']);