Feature: checkout.php now lets logged-in customers apply existing wallet
balance or redeem a gift card code (which tops up wallet first, then
applies) toward their order total. Deduction is deferred to payment
success (markSquarePaymentResult in includes/square.php), never at order
creation, so an abandoned checkout never loses real wallet money - mirrors
how loyalty points already work here, unlike stock which is decremented
eagerly. payment.php gains a second Square Gift Card tab (payments.giftCard()
SDK method) alongside the card form, both hitting the same
create-square-payment.php endpoint since Square treats both source types
identically.
New api/apply-wallet-credit.php validates/quotes an amount without writing
anything - actual spend happens only via markSquarePaymentResult(). The
gift-card-to-wallet transaction logic was extracted out of
api/redeem-gift-card.php into a shared loyalty()->redeemGiftCardToWallet()
so the Wallet page and checkout both call the same code.
Also fixed three unrelated pre-existing bugs surfaced while testing this:
- loyalty.php awardPoints() reused the same named PDO parameter (:points)
twice in one UPDATE - fails under real prepared statements, meaning
loyalty points (and the email sent right after them) were silently never
awarded for any order tied to a logged-in customer.
- redeemGiftCardToWallet (formerly inline in redeem-gift-card.php) referenced
a gift_cards.updated_at column that does not exist in the schema, and used
invalid enum values (gift_card_transactions.type=redeem,
wallet_transactions.type=gift_card) that do not match the actual enum
definitions - gift card redemption has likely never worked at all.
- account/rewards.php was missing the line that loads
account.css, unlike every other account/*.php page, so its sidebar/layout
rendered unstyled.
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.
- admin/api/upload-splash.php had NO admin-auth check (included the public
customer header, not admin/includes/header.php) and both upload endpoints
trusted the client-supplied MIME type and filename extension, so an
attacker could name a file "shell.php", spoof Content-Type: image/png,
and get PHP written into a web-reachable uploads/ directory. Added
AdminAuth check and centralized real-content validation (getimagesize +
server-side extension mapping) in a new handleImageUpload() helper used
by both admin/upload-image.php and admin/api/upload-splash.php.
- Removed CURLOPT_SSL_VERIFYPEER => false from the CyberMail email calls
in includes/email.php and includes/functions.php (MITM risk on the API
key). Rewrote functions.php's sendEmail() as a thin wrapper around
Email::send() so there's one implementation instead of two that could
drift (this is what had the second copy of the TLS bypass).
- Escaped customer-controlled fields (customer_name, tracking info, reset
URL) before interpolating into outbound HTML emails — name is free text
from registration/checkout with no length/char restriction, so it was
stored-HTML-injectable into every transactional email.
- Fixed api/redeem-gift-card.php referencing a nonexistent `balance` column
on gift_cards (actual column is current_balance) — gift card redemption
was completely broken, always returning "no remaining balance".
- Fixed api/submit-review.php inserting into nonexistent `content`/`status`
columns on reviews (actual columns are `comment`/`is_approved`) — review
submission was crashing on every request.
- Hardened .htaccess: block /db/*, /.git/*, and *.sql. Live site currently
serves db/schema.sql and the full .git directory (including .git/config,
which contains a GitHub PAT with push access) over HTTP — the existing
config/includes RedirectMatch rules are also not being enforced live,
see report for details; this needs a server-level fix too.
- Cleaned up README's leftover install instructions pointing at a deleted
create-admin.php with a documented default password.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fresh Tom's Java Jive logo with coffee cup, Tom's Java Jive text,
Artisan Coffee Roaster, and www.tomsjavajive.com. Bump logo display
height to 65px to suit square aspect ratio.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merged Company + Support into a single Help column, removed Sub
Categories section. Tightened grid gap, padding, and list spacing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- merchant-feed.php: RSS 2.0 feed with all active products; includes
title, description, image_link, price, availability, brand, shipping,
google_product_category for each item; URL to submit in Merchant Center
- header.php: placeholder GSC meta tag (replace PASTE_GSC_CODE_HERE with
verification content value from Search Console)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- product.php: set metaTitle, metaDescription, canonicalUrl, ogImage,
ogType=product, productSchema (JSON-LD with price/availability/reviews),
and breadcrumbs variables for header.php to consume
- sitemap.php: dynamic XML sitemap generated from DB — includes all 30
active products + static pages; robots.txt now points here
- header.php: fix favicon links (favicon.ico in root + icon-192.png);
fix productSchema output (was double-encoding via json_encode)
- robots.txt: point Sitemap directive to /sitemap.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LoyaltyProgram now loads tiers from loyalty_tiers DB table in constructor
with fallback to hardcoded defaults if table is empty
- awardPoints() accepts order_id param with duplicate-prevention check so
points cannot be double-awarded for the same order
- Inserts balance_after into loyalty_transactions for accurate history
- payment-status.php: award points after Stripe checkout session or
PaymentIntent confirmed as paid
- create-checkout-session.php: award points in demo mode payment path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove service worker registration from footer (SW is already self-unregistering)
- Add mobile-web-app-capable meta to fix deprecation warning
- Remove missing icon references from manifest (only 192/512 PNGs exist)
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>
- 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>
- 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.