194 lines
5.2 KiB
PHP
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"
|
|
]); |