Files
manjapro_v6/paymentlink/callback/doku/index.php
T
2026-08-20 18:39:37 +07:00

194 lines
5.2 KiB
PHP

<?php
// ================= GLOBAL ERROR HANDLER =================
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/php_error.log');
// tangkap fatal error
register_shutdown_function(function () {
$error = error_get_last();
if ($error !== NULL) {
file_put_contents(
__DIR__ . '/fatal_error.log',
date('Y-m-d H:i:s') . " | " . json_encode($error) . "\n",
FILE_APPEND
);
}
});
// ================= LOGGER =================
function writeLog($title, $data = null)
{
$logFile = __DIR__ . '/callback_debug.log';
try {
if (!file_exists($logFile)) {
touch($logFile);
chmod($logFile, 0777);
}
$log = "=============================\n";
$log .= "TIME : " . date('Y-m-d H:i:s') . "\n";
$log .= "TITLE: " . $title . "\n";
if ($data !== null) {
if (is_array($data) || is_object($data)) {
$log .= "DATA : " . json_encode($data, JSON_PRETTY_PRINT) . "\n";
} else {
$log .= "DATA : " . $data . "\n";
}
}
$log .= "=============================\n\n";
file_put_contents($logFile, $log, FILE_APPEND);
} catch (Throwable $e) {
file_put_contents(
__DIR__ . '/callback_fallback.log',
date('Y-m-d H:i:s') . " | LOG ERROR: " . $e->getMessage() . "\n",
FILE_APPEND
);
}
}
// ================= LOAD CONFIG =================
$config = require __DIR__ . '/doku_config.php';
$secretKey = trim($config['secret_key']);
// ================= CONNECT DB =================
try {
require __DIR__ . '/../../../config/connect.php';
} catch (Throwable $e) {
writeLog("ERROR DB CONNECT", $e->getMessage());
}
// ================= AMBIL BODY =================
$rawBody = file_get_contents("php://input");
$data = json_decode($rawBody, true);
// ================= AMBIL HEADER =================
if (function_exists('getallheaders')) {
$headers = getallheaders();
} else {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace('_', '-', substr($name, 5))] = $value;
}
}
}
// ================= NORMALISASI HEADER =================
$signature = $headers['Signature'] ?? $headers['signature'] ?? '';
$requestId = $headers['Request-Id'] ?? $headers['request-id'] ?? '';
$timestamp = $headers['Request-Timestamp'] ?? $headers['request-timestamp'] ?? '';
$clientId = $headers['Client-Id'] ?? $headers['client-id'] ?? '';
$target = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// ================= VALIDASI SIGNATURE =================
$digest = base64_encode(hash('sha256', $rawBody, true));
$stringToSign =
"Client-Id:" . $clientId . "\n" .
"Request-Id:" . $requestId . "\n" .
"Request-Timestamp:" . $timestamp . "\n" .
"Request-Target:" . $target . "\n" .
"Digest:" . $digest;
$generated = "HMACSHA256=" . base64_encode(
hash_hmac('sha256', $stringToSign, $secretKey, true)
);
if ($generated !== $signature) {
writeLog("ERROR SIGNATURE INVALID");
http_response_code(401);
echo "INVALID SIGNATURE";
exit;
}
// ================= PARSE DATA =================
$invoice = $data['order']['invoice_number'] ?? null;
$amount = $data['order']['amount'] ?? 0;
$status = strtoupper($data['transaction']['status'] ?? 'PENDING');
// ================= VALIDASI =================
if (!$invoice) {
writeLog("ERROR NO INVOICE");
http_response_code(400);
exit('INVALID DATA');
}
// ================= CEK DB =================
$stmt = $pdo->prepare("SELECT id,status,amount FROM payment_winpay WHERE trx_id = ?");
$stmt->execute([$invoice]);
$trx = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$trx) {
writeLog("ERROR NOT FOUND");
http_response_code(404);
exit('NOT FOUND');
}
// ================= VALIDASI AMOUNT =================
if ((int)$trx['amount'] !== (int)$amount) {
writeLog("ERROR AMOUNT MISMATCH", [
'db' => $trx['amount'],
'callback' => $amount
]);
http_response_code(400);
exit('INVALID AMOUNT');
}
// ================= ANTI DOUBLE =================
if ($trx['status'] === 'PAID') {
writeLog("ALREADY PAID");
echo json_encode(['message' => 'ALREADY PAID']);
exit;
}
// ================= MAP STATUS =================
switch ($status) {
case 'SUCCESS':
$dbStatus = 'PAID';
break;
case 'EXPIRED':
$dbStatus = 'EXPIRED';
break;
default:
$dbStatus = 'PENDING';
}
// ================= UPDATE DB =================
try {
$stmt = $pdo->prepare("
UPDATE payment_winpay
SET
status = :status,
paid_at = CASE WHEN :status = 'PAID' THEN NOW() ELSE paid_at END,
response_callback = :callback,
updated_at = NOW()
WHERE trx_id = :trx_id
");
$stmt->execute([
':status' => $dbStatus,
':callback' => $rawBody,
':trx_id' => $invoice
]);
} catch (Throwable $e) {
writeLog("DB UPDATE ERROR", $e->getMessage());
http_response_code(500);
exit('DB ERROR');
}
echo json_encode([
"responseCode" => "2000000",
"responseMessage" => "SUCCESS"
]);