235 lines
5.7 KiB
PHP
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
|
|
]); |