SW was caching shop pages and JS files, serving stale versions without
the inline onclick handler. Replacing with self-unregistering SW.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All api/*.php files include functions.php but none called session_start(),
so $_SESSION writes were lost after each request. Cart appeared to work
(API returned cart_count:1) but nothing was ever saved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Service worker was caching main.js (cache-first strategy) so event listeners
may not have been running. Added filemtime version param to main.js like CSS.
Also added inline onclick to shop page buttons so they work regardless of
whether event delegation is functional.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prefixed is_active, category, product_type_id, name, description, and ORDER BY columns with table alias p to resolve ambiguity with the product_types JOIN.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows how many active products are linked to each type, linked to the filtered products list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add display:block to .product-card-image so padding-top aspect ratio works on anchor tags
- Add Cache-Control: no-transform header to disable Cloudflare Rocket Loader (was deferring main.js and breaking add-to-cart click handlers)
- Add Sub Categories filter row on shop page using product_types table
- Show category · sub-category on product cards
- Add Sub Categories section to footer
- Preserve subcat param across category/sort filter links
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- faq.php: accordion FAQ with Orders, Coffee & Products, Coffee Freshness & Storage, and Account sections
- shipping.php: rates table (3-5 days after processing), processing time, delivery flow
- returns.php: three-tier policy (your/our/shared responsibility) adapted from DripShipper
- track-order.php: order lookup by order number + email, progress steps, tracking link
- privacy.php: full privacy policy adapted for Toms Java Jive
- footer.php: added Privacy Policy link to Support section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
- Email::send(): add curl_error() check so transport failures (timeout,
DNS, TLS) return a diagnosable error string instead of Unknown error
- Email::send(): strip metadata key from options before array_merge so
non-API fields are never sent to CyberMail endpoint
- Email::send() + sendEmail(): include from-name in From field using
RFC 5322 "Name <email>" format so fromName DB setting takes effect
- email-log.php: replace unbounded page-link loop with a windowed
paginator (first/last 2 pages + ±2 around current) with ellipsis
gaps — prevents hundreds of anchors rendering at scale
Constructor now reads cybermail_from_email and cybermail_from_name from
the settings table via getSetting(), falling back to constants. Matches
the pattern already used for cybermail_api_key and the global sendEmail()
wrapper in functions.php. Admin integrations page changes now take effect
across all email paths.
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.