config->get('square.enabled', false)) { return [ 'success' => true, 'provider' => 'demo', 'reference' => 'DEMO-' . strtoupper(bin2hex(random_bytes(5))), 'status' => 'COMPLETED', ]; } if ($sourceId === '') { throw new RuntimeException('Card details are required.'); } if (!function_exists('curl_init')) { throw new RuntimeException('The PHP cURL extension is required for Square payments.'); } $environment = (string) $this->config->get('square.environment', 'sandbox'); $baseUrl = $environment === 'production' ? 'https://connect.squareup.com' : 'https://connect.squareupsandbox.com'; $accessToken = (string) $this->config->get('square.access_token'); $locationId = (string) $this->config->get('square.location_id'); if ($accessToken === '' || $locationId === '') { throw new RuntimeException('Square is enabled but its server credentials are incomplete.'); } $payload = json_encode([ 'source_id' => $sourceId, 'idempotency_key' => substr(hash('sha256', 'fatbottom-payment|' . $orderNumber), 0, 45), 'amount_money' => [ 'amount' => $amountCents, 'currency' => 'USD', ], 'location_id' => $locationId, 'reference_id' => $orderNumber, 'note' => 'Online pickup order ' . $orderNumber, 'autocomplete' => true, ], JSON_THROW_ON_ERROR); $curl = curl_init($baseUrl . '/v2/payments'); curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $accessToken, 'Content-Type: application/json', 'Square-Version: 2026-05-20', ], CURLOPT_TIMEOUT => 30, ]); $response = curl_exec($curl); $status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE); $error = curl_error($curl); curl_close($curl); $decoded = is_string($response) ? json_decode($response, true) : null; if ($response === false || $status < 200 || $status >= 300 || !isset($decoded['payment'])) { $message = $decoded['errors'][0]['detail'] ?? $error ?: 'Payment was declined.'; throw new RuntimeException((string) $message); } return [ 'success' => true, 'provider' => 'square', 'reference' => (string) $decoded['payment']['id'], 'status' => (string) $decoded['payment']['status'], ]; } public function refund(string $paymentReference, int $amountCents, string $reason): array { if (!(bool) $this->config->get('square.enabled', false) || str_starts_with($paymentReference, 'DEMO-')) { return ['success' => true, 'reference' => 'REF-DEMO-' . strtoupper(bin2hex(random_bytes(4)))]; } if (!function_exists('curl_init')) { throw new RuntimeException('The PHP cURL extension is required for Square refunds.'); } $environment = (string) $this->config->get('square.environment', 'sandbox'); $baseUrl = $environment === 'production' ? 'https://connect.squareup.com' : 'https://connect.squareupsandbox.com'; $payload = json_encode([ 'idempotency_key' => bin2hex(random_bytes(16)), 'payment_id' => $paymentReference, 'amount_money' => ['amount' => $amountCents, 'currency' => 'USD'], 'reason' => substr($reason, 0, 190), ], JSON_THROW_ON_ERROR); $curl = curl_init($baseUrl . '/v2/refunds'); curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $this->config->get('square.access_token'), 'Content-Type: application/json', 'Square-Version: 2026-05-20', ], CURLOPT_TIMEOUT => 30, ]); $response = curl_exec($curl); $status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE); curl_close($curl); $decoded = is_string($response) ? json_decode($response, true) : null; if ($status < 200 || $status >= 300 || !isset($decoded['refund'])) { throw new RuntimeException((string) ($decoded['errors'][0]['detail'] ?? 'Refund failed.')); } return ['success' => true, 'reference' => (string) $decoded['refund']['id']]; } }