diff --git a/admin/index.php b/admin/index.php index 2208c1d..9d59218 100644 --- a/admin/index.php +++ b/admin/index.php @@ -174,6 +174,65 @@ if ($isAjax) { exit; } + if ($action === 'square_capture') { + $id = (int)($_POST['id'] ?? 0); + $stmt = db()->prepare("SELECT square_payment_id FROM bookings WHERE id=?"); + $stmt->execute([$id]); + $b = $stmt->fetch(); + $pid = $b['square_payment_id'] ?? ''; + if (!$pid) { echo json_encode(['error'=>'No payment on file']); exit; } + $resp = squareApi('POST', "/payments/{$pid}/complete"); + if (($resp['payment']['status'] ?? '') === 'COMPLETED') { + db()->prepare("UPDATE bookings SET square_payment_status='COMPLETED', deposit_paid=?, deposit_received=1 WHERE id=?") + ->execute([DEPOSIT_AMOUNT, $id]); + echo json_encode(['ok'=>true,'status'=>'COMPLETED']); + } else { + echo json_encode(['error' => $resp['errors'][0]['detail'] ?? 'Capture failed']); + } + exit; + } + + if ($action === 'square_void') { + $id = (int)($_POST['id'] ?? 0); + $stmt = db()->prepare("SELECT square_payment_id FROM bookings WHERE id=?"); + $stmt->execute([$id]); + $b = $stmt->fetch(); + $pid = $b['square_payment_id'] ?? ''; + if (!$pid) { echo json_encode(['error'=>'No payment on file']); exit; } + $resp = squareApi('POST', "/payments/{$pid}/cancel"); + if (($resp['payment']['status'] ?? '') === 'CANCELED') { + db()->prepare("UPDATE bookings SET square_payment_status='CANCELED' WHERE id=?")->execute([$id]); + echo json_encode(['ok'=>true,'status'=>'CANCELED']); + } else { + echo json_encode(['error' => $resp['errors'][0]['detail'] ?? 'Void failed']); + } + exit; + } + + if ($action === 'square_refund') { + $id = (int)($_POST['id'] ?? 0); + $stmt = db()->prepare("SELECT square_payment_id, deposit_paid FROM bookings WHERE id=?"); + $stmt->execute([$id]); + $b = $stmt->fetch(); + $pid = $b['square_payment_id'] ?? ''; + if (!$pid) { echo json_encode(['error'=>'No payment on file']); exit; } + $cents = (int)(((float)($b['deposit_paid'] ?: DEPOSIT_AMOUNT)) * 100); + $resp = squareApi('POST', '/refunds', [ + 'idempotency_key' => $pid . '-refund-' . time(), + 'payment_id' => $pid, + 'amount_money' => ['amount' => $cents, 'currency' => 'USD'], + 'reason' => 'Security deposit refund — booking returned in good condition', + ]); + if (!empty($resp['refund']['id'])) { + db()->prepare("UPDATE bookings SET square_payment_status='REFUNDED', square_refund_id=?, deposit_paid=0 WHERE id=?") + ->execute([$resp['refund']['id'], $id]); + echo json_encode(['ok'=>true,'status'=>'REFUNDED']); + } else { + echo json_encode(['error' => $resp['errors'][0]['detail'] ?? 'Refund failed']); + } + exit; + } + if ($action === 'block_date') { $date = $_POST['date'] ?? ''; $reason = substr($_POST['reason'] ?? '', 0, 200); @@ -581,24 +640,47 @@ textarea.notes-ta:focus{border-color:#f97316} - + +
Deposit Hold: \$" . number_format(DEPOSIT_AMOUNT, 2) . " authorized (not charged — released if booking is declined)
" + : ''; + +// Inject deposit line into confirmation email +$confirmHtml = str_replace( + "Total: {$amountLabel}
", + "Total: {$amountLabel}
{$depositLine}", + $confirmHtml +); + +// Add deposit note to admin email if applicable +if ($depositStatus) { + $adminHtml = str_replace( + "", + "
✓ \$" . number_format(DEPOSIT_AMOUNT, 2) . " deposit hold authorized (Square — not yet captured)
", + $adminHtml + ); +} + sendEmail(ADMIN_EMAIL, 'Parker Slingshot Admin', "New Booking {$ref}: {$name} — {$pkgLabel} on {$dateLabel}", $adminHtml); sendEmail($email, $name, "Booking Request {$ref} — Parker County Slingshot Rentals", $confirmHtml); -echo json_encode(['success'=>true,'ref'=>$ref,'message'=>"Booking request received! Your reference is {$ref}. We'll be in touch shortly."]); +$msg = "Booking request received! Your reference is {$ref}. We'll be in touch shortly."; +if ($depositStatus) $msg .= " A \$" . number_format(DEPOSIT_AMOUNT, 2) . " refundable deposit hold has been placed on your card."; +echo json_encode(['success'=>true,'ref'=>$ref,'deposit_held'=>(bool)$depositStatus,'square_payment_id'=>$sqId??null,'message'=>$msg]); diff --git a/db.php b/db.php index 9579314..0bb8f32 100644 --- a/db.php +++ b/db.php @@ -13,6 +13,12 @@ define('MAIL_FROM', 'noreply@parkerslingshotrentals.com'); define('MAIL_FROM_NAME', 'Parker County Slingshot Rentals'); define('ADMIN_EMAIL', 'info@parkerslingshotrentals.com'); +define('SQUARE_ACCESS_TOKEN', 'EAAAl3FsAu_2ri8kZE_ENEyi2T_C8HXXm5XQFY6Lbnd8SX6FqYp8J_upUeXNYh7v'); +define('SQUARE_APP_ID', 'sq0idp-YSM7BU9IVyOWSzpeP-0nzQ'); +define('SQUARE_LOCATION_ID', 'L8GZYHYKE95CE'); +define('SQUARE_VERSION', '2024-01-18'); +define('DEPOSIT_AMOUNT', 100.00); // $100 refundable security deposit hold + define('PACKAGES', [ 'half-day' => ['label' => 'Half Day (4 hrs)', 'amount' => 99.00, 'days' => 0], 'full-day' => ['label' => 'Full Day (8 hrs)', 'amount' => 169.00, 'days' => 0], @@ -31,6 +37,24 @@ function db(): PDO { return $pdo; } +function squareApi(string $method, string $path, array $body = []): array { + $ch = curl_init('https://connect.squareup.com/v2' . $path); + $headers = [ + 'Authorization: Bearer ' . SQUARE_ACCESS_TOKEN, + 'Content-Type: application/json', + 'Square-Version: ' . SQUARE_VERSION, + ]; + $opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => false]; + if ($method === 'POST') { + $opts[CURLOPT_POST] = true; + $opts[CURLOPT_POSTFIELDS] = $body ? json_encode($body) : '{}'; + } + curl_setopt_array($ch, $opts); + $resp = curl_exec($ch); + curl_close($ch); + return json_decode($resp ?: '{}', true); +} + function generateRef(): string { return 'PSR-' . strtoupper(substr(uniqid(), -6)); } diff --git a/index.html b/index.html index 2681644..d4af8bf 100644 --- a/index.html +++ b/index.html @@ -811,7 +811,17 @@ - + + +
Refundable Deposit — $100
+A $100 hold will be placed on your card — not charged until your booking is confirmed. Released in full if declined or at return.
+ + + +Polaris Slingshot® is a registered trademark of Polaris Inc. We are an independent rental operator.
+