(31 * 24 * 60 * 60)) { die(json_encode([ "error" => "Range maksimal 1 bulan" ], JSON_PRETTY_PRINT)); } } function generateSignature($method, $endpoint, $body, $timestamp, $privateKeyPath, &$debug = []) { $hashedBody = strtolower(bin2hex(hash('sha256', $body, true))); $stringToSign = implode(":", [ $method, $endpoint, $hashedBody, $timestamp ]); $privateKey = openssl_pkey_get_private(file_get_contents($privateKeyPath)); if (!$privateKey) { die(json_encode(["error" => "Private key tidak valid"])); } openssl_sign($stringToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256); $signatureBase64 = base64_encode($signature); // DEBUG $debug['string_to_sign'] = $stringToSign; $debug['hashed_body'] = $hashedBody; $debug['signature'] = $signatureBase64; return $signatureBase64; } // 🔥 CURL DETAIL function curlPost($url, $headers, $body) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => $headers, CURLOPT_HEADER => true ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $responseHeader = substr($response, 0, $headerSize); $responseBody = substr($response, $headerSize); $error = curl_error($ch); curl_close($ch); return [ "http_code" => $httpCode, "header" => $responseHeader, "body" => $responseBody, "error" => $error ]; } function hitWinpay($method, $endpoint, $body, $baseUrl, $partnerId, $privateKeyPath) { $timestamp = getTimestamp(); $externalId = time() . rand(1000, 9999); $jsonBody = json_encode($body, JSON_UNESCAPED_SLASHES); $signature = generateSignature( $method, $endpoint, $jsonBody, $timestamp, $privateKeyPath ); $headers = [ "Content-Type: application/json", "X-TIMESTAMP: $timestamp", "X-SIGNATURE: $signature", "X-PARTNER-ID: $partnerId", "X-EXTERNAL-ID: $externalId", "CHANNEL-ID: WEB" ]; $res = curlPost($baseUrl . $endpoint, $headers, $jsonBody); // 🔥 kalau curl error if (!empty($res['error'])) { return json_encode([ "status" => false, "error" => $res['error'] ]); } // 🔥 return PURE response dari Winpay return $res['body']; } // ========================= // ROUTER // ========================= $action = $_GET['action'] ?? ''; // ========================= // BALANCE // ========================= if ($action === 'balance') { $accountNo = $_GET['accountNo'] ?? ''; $body = [ "partnerReferenceNo" => uniqid(), "accountNo" => $accountNo, "balanceTypes" => ["Transaction", "Settlement"] ]; echo hitWinpay("POST", "/v1.0/balance-inquiry", $body, $baseUrl, $partnerId, $privateKeyPath); exit; } // ========================= // TRANSACTIONS // ========================= if ($action === 'transactions') { $fromRaw = $_GET['from'] ?? date('Y-m-01'); $toRaw = $_GET['to'] ?? date('Y-m-d'); $from = formatISO8601($fromRaw); $to = formatISO8601($toRaw, true); validateRange($from, $to); $page = $_GET['page'] ?? 1; $limit = $_GET['limit'] ?? 100; $body = [ "partnerReferenceNo" => "REF" . time(), "fromDateTime" => $from, "toDateTime" => $to, "pageSize" => (int)$limit, "pageNumber" => (int)$page ]; echo hitWinpay("POST", "/v1.0/transaction-history-list", $body, $baseUrl, $partnerId, $privateKeyPath); exit; } // ========================= // STATEMENT // ========================= if ($action === 'statement') { $accountNo = $_GET['accountNo'] ?? ''; $token = $_GET['token'] ?? ''; $fromRaw = $_GET['from'] ?? date('Y-m-01'); $toRaw = $_GET['to'] ?? date('Y-m-d'); $from = formatISO8601($fromRaw); $to = formatISO8601($toRaw, true); validateRange($from, $to); $page = $_GET['page'] ?? 1; $limit = $_GET['limit'] ?? 10; $body = [ "partnerReferenceNo" => "REF" . time(), "bankCardToken" => $token, "accountNo" => $accountNo, "fromDateTime" => $from, "toDateTime" => $to, "additionalInfo" => [ "pageSize" => (string)$limit, "pageNumber" => (string)$page ] ]; echo hitWinpay("POST", "/v1.0/bank-statement", $body, $baseUrl, $partnerId, $privateKeyPath); exit; } // ========================= // DEFAULT // ========================= echo json_encode([ "status" => false, "message" => "Invalid action", "available" => ["balance","transactions","statement"] ], JSON_PRETTY_PRINT);