'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 ]);