Consolidates payment processing onto the same Square account already used by
tomtomgames.com and parkerslingshotrentals.com. Collapses the two prior parallel
Stripe flows (hosted Checkout + embedded Elements) into a single Square Web
Payments SDK flow, since Payments API is synchronous and removes the original
reason for two paths.
- includes/square.php: squareApi() cURL helper (mirrors the pattern already
used on parkerslingshotrentals.com), markSquarePaymentResult() as the single
source of truth for order completion shared by the sync response, webhook,
and reconciliation poll - fixes a pre-existing bug where loyalty points were
only ever awarded from the polling endpoint, never from the webhook.
- api/create-square-payment.php replaces api/create-payment-intent.php;
api/create-checkout-session.php deleted (no Square equivalent - single flow).
- api/webhook.php rewritten for Squares signature scheme and event types.
- api/payment-status.php repurposed to reconciliation-only fallback.
- payment.php branches on PAYMENT_PROCESSOR so Stripe and Square code coexist
deployed while dormant - flipping one config constant is the cutover/rollback.
- admin/payments.php: added a Square settings card alongside the existing
(now legacy-labeled) Stripe card.
- db/schema.sql + live DB: added square_payment_id/square_order_id columns,
stripe_* columns kept for historical orders.
Not yet cut over - PAYMENT_PROCESSOR still defaults to stripe in
config-secrets.php (outside this repo). Sandbox testing still needed before
flipping to square/production.
Mirrors the checkout.session.completed case which checks
payment_status === paid before acting. Now checks data.status
=== succeeded on the PaymentIntent object, consistent with how
Stripe structures the event and defensive against any future
edge case where the event fires in a non-final state.
Replaced local sendOrderConfirmationEmail() with emailService()->sendOrderConfirmation().
Order confirmations now log to email_log table and use the branded template
(orange header, full subtotal/tax/discount breakdown) instead of the minimal
brown-header version that was invisible to the admin Email Log.
When using Stripe Checkout, both checkout.session.completed and
payment_intent.succeeded fire for the same payment. After the stripe.php
change propagated order_id into PI metadata, the PI handler also found
an order_id and sent a second confirmation email.
Fix: fetch the order first in payment_intent.succeeded and skip if
already confirmed. Also records stripe_payment_intent in this path
for direct PI flows that bypass checkout.session.completed.