Files
HotLunches/paypal_checkout_api.php
2026-10-19 15:22:52 -07:00

268 lines
8.6 KiB
PHP

<?php
/**
* PayPal Checkout API Helper
*
* Handles communication with PayPal REST API v2
* Uses client ID and secret for OAuth authentication
*/
require_once __DIR__ . '/paypal_checkout_config.php';
class PayPalCheckoutAPI {
private $clientId;
private $clientSecret;
private $baseUrl;
private $accessToken;
public function __construct() {
$this->clientId = getPayPalClientId();
$this->clientSecret = getPayPalClientSecret();
$this->baseUrl = getPayPalBaseUrl();
}
private function httpRequest($method, $url, $headers = [], $body = null, $basicAuth = null) {
$method = strtoupper($method);
$verifySsl = defined('PAYPAL_VERIFY_SSL') ? (bool)PAYPAL_VERIFY_SSL : true;
// Prefer cURL when available
if (function_exists('curl_init')) {
$ch = curl_init();
$opts = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => $verifySsl,
CURLOPT_SSL_VERIFYHOST => $verifySsl ? 2 : 0,
CURLOPT_TIMEOUT => 30,
];
if ($basicAuth) {
$opts[CURLOPT_USERPWD] = $basicAuth;
}
if ($body !== null) {
$opts[CURLOPT_POSTFIELDS] = $body;
}
curl_setopt_array($ch, $opts);
$responseBody = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception("cURL Error: $error");
}
return [$httpCode, $responseBody];
}
// Fallback to file_get_contents if cURL isn't enabled
$headerLines = $headers;
if ($basicAuth) {
$headerLines[] = 'Authorization: Basic ' . base64_encode($basicAuth);
}
$context = stream_context_create([
'http' => [
'method' => $method,
'header' => implode("\r\n", $headerLines),
'content' => $body ?? '',
'ignore_errors' => true,
'timeout' => 30,
],
'ssl' => [
'verify_peer' => $verifySsl,
'verify_peer_name' => $verifySsl,
],
]);
$responseBody = @file_get_contents($url, false, $context);
$httpCode = 0;
if (isset($http_response_header) && is_array($http_response_header)) {
foreach ($http_response_header as $h) {
if (preg_match('#^HTTP/\S+\s+(\d{3})#', $h, $m)) {
$httpCode = intval($m[1]);
break;
}
}
}
if ($responseBody === false) {
$lastError = error_get_last();
$msg = $lastError['message'] ?? 'HTTP request failed';
throw new Exception($msg);
}
return [$httpCode, $responseBody];
}
/**
* Get OAuth 2.0 access token from PayPal
*/
public function getAccessToken() {
if ($this->accessToken) {
return $this->accessToken;
}
[$httpCode, $response] = $this->httpRequest(
'POST',
$this->baseUrl . '/v1/oauth2/token',
[
'Accept: application/json',
'Accept-Language: en_US',
'Content-Type: application/x-www-form-urlencoded',
],
'grant_type=client_credentials',
$this->clientId . ':' . $this->clientSecret
);
$data = json_decode($response, true);
if ($httpCode !== 200 || !isset($data['access_token'])) {
$errorMsg = isset($data['error_description']) ? $data['error_description'] : 'Failed to get access token';
throw new Exception("PayPal Auth Error: $errorMsg");
}
$this->accessToken = $data['access_token'];
return $this->accessToken;
}
/**
* Create a PayPal order
*
* @param float $amount The order amount
* @param string $currency Currency code (e.g., 'USD', 'CAD')
* @param string $description Order description
* @param string $customId Your internal reference ID (e.g., TRXnum)
* @return array Order data including order ID
*/
public function createOrder($amount, $currency, $description = '', $customId = '') {
$token = $this->getAccessToken();
$orderData = [
'intent' => 'CAPTURE',
'purchase_units' => [
[
'amount' => [
'currency_code' => $currency,
'value' => number_format($amount, 2, '.', '')
],
'description' => $description,
'custom_id' => $customId // This is like the 'custom' field in old PayPal
]
],
'application_context' => [
'brand_name' => 'Hot Lunches Online',
'landing_page' => 'NO_PREFERENCE',
'user_action' => 'PAY_NOW',
'return_url' => $this->getCurrentUrl() . $this->appendParam('paypal=success'),
'cancel_url' => $this->getCurrentUrl() . $this->appendParam('paypal=cancel')
]
];
[$httpCode, $response] = $this->httpRequest(
'POST',
$this->baseUrl . '/v2/checkout/orders',
[
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
'PayPal-Request-Id: ' . uniqid('order_', true),
],
json_encode($orderData)
);
$data = json_decode($response, true);
if ($httpCode !== 201 || !isset($data['id'])) {
$errorMsg = isset($data['message']) ? $data['message'] : 'Failed to create order';
if (isset($data['details'])) {
$errorMsg .= ': ' . json_encode($data['details']);
}
throw new Exception("PayPal Create Order Error: $errorMsg");
}
return $data;
}
/**
* Capture a PayPal order (complete the payment)
*
* @param string $orderId PayPal order ID
* @return array Capture result including transaction details
*/
public function captureOrder($orderId) {
$token = $this->getAccessToken();
[$httpCode, $response] = $this->httpRequest(
'POST',
$this->baseUrl . '/v2/checkout/orders/' . $orderId . '/capture',
[
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
'PayPal-Request-Id: ' . uniqid('capture_', true),
],
'{}'
);
$data = json_decode($response, true);
if ($httpCode !== 201 || !isset($data['status'])) {
$errorMsg = isset($data['message']) ? $data['message'] : 'Failed to capture order';
if (isset($data['details'])) {
$errorMsg .= ': ' . json_encode($data['details']);
}
throw new Exception("PayPal Capture Error: $errorMsg");
}
return $data;
}
/**
* Get order details
*
* @param string $orderId PayPal order ID
* @return array Order details
*/
public function getOrder($orderId) {
$token = $this->getAccessToken();
[$httpCode, $response] = $this->httpRequest(
'GET',
$this->baseUrl . '/v2/checkout/orders/' . $orderId,
[
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
]
);
return json_decode($response, true);
}
/**
* Get current page URL (for return/cancel URLs) — includes the path and
* query string, not just the host, so PayPal redirects the buyer back
* to the same page/view they paid from (the one that can capture the
* order, e.g. ?action=account-deposit), not the site root.
*/
private function getCurrentUrl() {
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return $protocol . '://' . $host . $uri;
}
/**
* Append a query param to a URL that may already have a query string.
*/
private function appendParam($param) {
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return (strpos($uri, '?') !== false ? '&' : '?') . $param;
}
}