Files
2026-08-20 18:39:37 +07:00

235 lines
5.7 KiB
PHP

<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/callback_error.log');
require __DIR__ . '/../../../config/connect.php';
header("Content-Type: application/json");
// =====================
// LOAD PUBLIC KEY
// =====================
$publicKeyString = file_get_contents(__DIR__ . '/public_key_prod.pem');
$publicKey = openssl_get_publickey($publicKeyString);
// =====================
// RAW BODY
// =====================
$rawBody = file_get_contents('php://input');
$body = json_decode($rawBody, true) ?: [];
// =====================
// HEADERS
// =====================
$headers = function_exists('getallheaders') ? getallheaders() : [];
$signature = $headers['X-Signature'] ?? $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$timestamp = $headers['X-Timestamp'] ?? $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$method = $_SERVER['REQUEST_METHOD'] ?? 'POST';
// =====================
// PATH DINAMIS (WAJIB)
// =====================
$uri = $_SERVER['REQUEST_URI'] ?? '';
$path = parse_url($uri, PHP_URL_PATH);
// =====================
// HELPER
// =====================
function clean($v){
return is_string($v) ? trim($v) : $v;
}
// =====================
// VALIDATE SIGNATURE
// =====================
function validateSignature($publicKey, $method, $path, $rawBody, $timestamp, $signature)
{
$hashBody = strtolower(bin2hex(hash('sha256', $rawBody, true)));
$stringToSign = implode(':', [
$method,
$path,
$hashBody,
$timestamp
]);
file_put_contents(__DIR__.'/stringtosign.log', $stringToSign.PHP_EOL, FILE_APPEND);
return openssl_verify(
$stringToSign,
base64_decode($signature),
$publicKey,
OPENSSL_ALGO_SHA256
) === 1;
}
// =====================
// VALIDASI SIGNATURE
// =====================
if (!validateSignature($publicKey, $method, $path, $rawBody, $timestamp, $signature)) {
http_response_code(400);
echo json_encode([
'responseCode' => '4011100',
'responseMessage' => 'Invalid Signature'
]);
exit;
}
// =====================
// DETECT TYPE
// =====================
function detectType($path, $body){
if (strpos($path, 'transfer-va') !== false || isset($body['virtualAccountNo'])) {
return 'VA';
}
if (strpos($path, 'qris') !== false) {
return 'QRIS';
}
return 'EWALLET';
}
$type = detectType($path, $body);
// =====================
// NORMALISASI
// =====================
$trxId = clean(
$body['trxId']
?? $body['originalPartnerReferenceNo']
?? null
);
$invoiceId = clean(
$body['paymentRequestId']
?? $body['referenceNo']
?? null
);
$amount = floatval(
$body['paidAmount']['value']
?? $body['amount']['value']
?? 0
);
$channel = clean(
$body['additionalInfo']['channel']
?? $body['ewalletType']
?? $type
);
// =====================
// STATUS
// =====================
$statusCode = $body['latestTransactionStatus'] ?? '00';
$status = ($statusCode === '00') ? 'PAID' : 'FAILED';
// =====================
// IDEMPOTENT (ANTI DOUBLE)
// =====================
$check = $pdo->prepare("SELECT status FROM payment_winpay WHERE trx_id = ?");
$check->execute([$trxId]);
$existing = $check->fetch(PDO::FETCH_ASSOC);
if ($existing && $existing['status'] === 'PAID') {
echo json_encode([
'responseCode' => '2000000',
'responseMessage' => 'ALREADY PROCESSED'
]);
exit;
}
// =====================
// 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' => $status,
':callback' => $rawBody,
':trx_id' => $trxId
]);
// =====================
// SAVE LOG (FIXED)
// =====================
try {
$stmtLog = $pdo->prepare("
INSERT INTO payment_winpay_log (
trx_id, type, endpoint, method,
request_body, response_body,
status, created_at
) VALUES (
:trx_id, :type, :endpoint, :method,
:request_body, :response_body,
:status, NOW()
)
");
$stmtLog->execute([
':trx_id' => $trxId,
':type' => 'CALLBACK',
':endpoint' => $path, // ini paling valid dari request
':method' => $method,
':request_body' => $rawBody,
':response_body' => json_encode([
'status' => $status,
'type' => $type
]),
':status' => $status
]);
} catch (Exception $e) {
file_put_contents(__DIR__.'/callback_log_error.log',
date('Y-m-d H:i:s').' '.$e->getMessage().PHP_EOL,
FILE_APPEND
);
}
} catch (Exception $e) {
file_put_contents(__DIR__.'/callback_db_error.log',
date('Y-m-d H:i:s').' '.$e->getMessage().PHP_EOL,
FILE_APPEND
);
}
// =====================
// RESPONSE CODE DINAMIS
// =====================
$responseCode = '2000000';
if ($type === 'QRIS') {
$responseCode = '2005200';
$responseMessage = 'success';
} elseif ($type === 'EWALLET') {
$responseCode = '2005600';
$responseMessage = 'Successful';
} elseif ($type === 'VA') {
$responseCode = '2002500';
$responseMessage = 'Successful';
}
// =====================
// RESPONSE
// =====================
echo json_encode([
'responseCode' => $responseCode,
'responseMessage' => $responseMessage
]);