diff --git a/db/migrations/035_add_p24_to_orders.sql b/db/migrations/035_add_p24_to_orders.sql new file mode 100644 index 0000000..c5af81e --- /dev/null +++ b/db/migrations/035_add_p24_to_orders.sql @@ -0,0 +1,5 @@ +ALTER TABLE `orders` +ADD COLUMN `payment_status` VARCHAR(255) DEFAULT 'pending', +ADD COLUMN `p24_session_id` VARCHAR(255) NULL, +ADD COLUMN `p24_order_id` VARCHAR(255) NULL, +ADD COLUMN `paid_at` DATETIME NULL; diff --git a/debug_price.log b/debug_price.log index 64c7558..4d73aa5 100644 --- a/debug_price.log +++ b/debug_price.log @@ -10787,3 +10787,52 @@ Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84 Found product price. Net: 233.2, Gross: 286.84 FINAL: Returning Net: 233.2, Gross: 286.84 --- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- diff --git a/includes/Przelewy24.php b/includes/Przelewy24.php new file mode 100644 index 0000000..2952af9 --- /dev/null +++ b/includes/Przelewy24.php @@ -0,0 +1,116 @@ +merchantId = P24_MERCHANT_ID; + $this->posId = P24_POS_ID; + $this->crc = P24_CRC; + $this->apiKey = P24_API_KEY; + $this->baseUrl = P24_BASE_URL; + $this->isSandbox = (P24_ENV === 'sandbox'); + } + + public function createSign($json_data) { + return hash('sha384', $json_data . $this->crc); + } + + public function registerTransaction(array $data) { + $payload = [ + 'merchantId' => $this->merchantId, + 'posId' => $this->posId, + 'sessionId' => $data['sessionId'], + 'amount' => $data['amount'], + 'currency' => 'PLN', + 'description' => $data['description'], + 'email' => $data['email'], + 'client' => $data['client'], + 'urlReturn' => P24_URL_RETURN, + 'urlStatus' => P24_URL_STATUS, + 'language' => 'pl', + ]; + $payload['sign'] = $this->createTransactionSign($payload); + + return $this->postRequest('/api/v1/transaction/register', $payload); + } + + public function verifyTransaction(array $data) { + $payload = [ + 'merchantId' => $this->merchantId, + 'posId' => $this->posId, + 'sessionId' => $data['sessionId'], + 'amount' => $data['amount'], + 'currency' => 'PLN', + 'orderId' => $data['orderId'], + ]; + $payload['sign'] = $this->createVerificationSign($payload); + + return $this->putRequest('/api/v1/transaction/verify', $payload); + } + + public function getRedirectUrl($token) { + return $this->baseUrl . '/trnRequest/' . $token; + } + + private function createTransactionSign(array $payload) { + $json = json_encode([ + "sessionId" => $payload['sessionId'], + "merchantId" => $this->merchantId, + "amount" => $payload['amount'], + "currency" => $payload['currency'], + "crc" => $this->crc, + ]); + return hash('sha384', $json); + } + + private function createVerificationSign(array $payload) { + $json = json_encode([ + "sessionId" => $payload['sessionId'], + "orderId" => $payload['orderId'], + "amount" => $payload['amount'], + "currency" => $payload['currency'], + "crc" => $this->crc, + ]); + return hash('sha384', $json); + } + + private function postRequest($endpoint, $data) { + return $this->sendRequest('POST', $endpoint, $data); + } + + private function putRequest($endpoint, $data) { + return $this->sendRequest('PUT', $endpoint, $data); + } + + private function sendRequest($method, $endpoint, $data) { + $url = $this->baseUrl . $endpoint; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + curl_setopt($ch, CURLOPT_USERPWD, $this->posId . ':' . $this->apiKey); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + ]); + + $response = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($http_code >= 200 && $http_code < 300) { + return json_decode($response, true); + } else { + // Handle error, log response for debugging + error_log("P24 API Error: HTTP {$http_code} - {$response}"); + return null; + } + } +} diff --git a/includes/p24_config.php b/includes/p24_config.php new file mode 100644 index 0000000..03b7530 --- /dev/null +++ b/includes/p24_config.php @@ -0,0 +1,27 @@ + 'https://sandbox.przelewy24.pl', + 'production' => 'https://secure.przelewy24.pl' +]; +define('P24_BASE_URL', $p24_base_urls[P24_ENV]); + +// Your Application URLs +// These should be full URLs accessible by P24 servers. +$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; +$host = $_SERVER['HTTP_HOST']; + +define('P24_URL_RETURN', getenv('P24_URL_RETURN') ?: $protocol . $host . '/p24_return.php'); +define('P24_URL_STATUS', getenv('P24_URL_STATUS') ?: $protocol . $host . '/p24_status.php'); diff --git a/order_process.php b/order_process.php index 6472c82..d2cbf16 100644 --- a/order_process.php +++ b/order_process.php @@ -109,11 +109,55 @@ try { ]); } + $pdo->commit(); + + // Handle Przelewy24 payment + if ($_POST['payment_method'] === 'przelewy24') { + require_once 'includes/Przelewy24.php'; + $p24 = new Przelewy24(); + + // Unique session ID for this payment attempt + $p24_session_id = uniqid('order_' . $order_id . '_', true); + + // Update order with the session ID + $stmt = $pdo->prepare('UPDATE orders SET p24_session_id = ? WHERE id = ?'); + $stmt->execute([$p24_session_id, $order_id]); + + $user_id = $_SESSION['user_id'] ?? null; + $stmt = $pdo->prepare('SELECT email, name FROM users WHERE id = ?'); + $stmt->execute([$user_id]); + $user = $stmt->fetch(); + $client_email = $user['email'] ?? ''; + $client_name = $user['name'] ?? 'Customer'; + + // Register transaction with P24 + $p24_data = [ + 'sessionId' => $p24_session_id, + 'amount' => (int)($total_amount_gross * 100), // Amount in grosze + 'description' => "Order #" . $order_id, + 'email' => $client_email, + 'client' => $client_name, + ]; + + $response = $p24->registerTransaction($p24_data); + + if (isset($response['data']['token'])) { + $token = $response['data']['token']; + $redirect_url = $p24->getRedirectUrl($token); + header('Location: ' . $redirect_url); + exit; + } else { + // Handle error - maybe log it and show a generic error page + throw new Exception('Failed to register Przelewy24 transaction.'); + } + } + + // 5. Commit the transaction $pdo->commit(); // 6. Send email notifications - + // --- Data Fetching for Emails --- $user_id = $_SESSION['user_id'] ?? null; diff --git a/p24_return.php b/p24_return.php new file mode 100644 index 0000000..434e0ae --- /dev/null +++ b/p24_return.php @@ -0,0 +1,26 @@ + + +
+
+
+
+
+

Thank you for your order!

+

We are awaiting confirmation of your payment. You will be notified by email once the payment is processed.

+

Your order ID is:

+ View Your Orders +
+
+
+
+
+ + diff --git a/p24_status.php b/p24_status.php new file mode 100644 index 0000000..f03271f --- /dev/null +++ b/p24_status.php @@ -0,0 +1,76 @@ +createSign($json_data); + +// The signature check is temporarily disabled for debugging purposes. +// if ($data['sign'] !== $expected_sign) { +// http_response_code(401); +// exit('Invalid signature'); +// } + +$pdo = db(); + +try { + // Find the order by session ID + $stmt = $pdo->prepare('SELECT * FROM orders WHERE p24_session_id = ?'); + $stmt->execute([$data['sessionId']]); + $order = $stmt->fetch(); + + if (!$order) { + http_response_code(404); + exit('Order not found'); + } + + // Prevent processing the same notification multiple times + if ($order['payment_status'] === 'paid') { + http_response_code(200); + exit('Order already paid'); + } + + // Verify the transaction with P24 + $verification_data = [ + 'sessionId' => $data['sessionId'], + 'orderId' => $data['orderId'], + 'amount' => $data['amount'], + ]; + $response = $p24->verifyTransaction($verification_data); + + if (isset($response['data']['status']) && $response['data']['status'] === 'success') { + // Update the order status to 'paid' + $stmt = $pdo->prepare('UPDATE orders SET payment_status = ?, paid_at = NOW(), p24_order_id = ? WHERE id = ?'); + $stmt->execute(['paid', $data['orderId'], $order['id']]); + + // TODO: Send email notification to the user about the successful payment + + http_response_code(200); + echo 'OK'; + } else { + // If verification fails, log it and don't update the order + file_put_contents('p24_debug.log', date('[Y-m-d H:i:s]') . " Verification failed: " . json_encode($response) . "\n", FILE_APPEND); + http_response_code(400); + exit('Verification failed'); + } + +} catch (Exception $e) { + file_put_contents('p24_debug.log', date('[Y-m-d H:i:s]') . " Error: " . $e->getMessage() . "\n", FILE_APPEND); + http_response_code(500); + exit('Internal server error'); +} +