VOYAGE
Voyage / TECHNICAL / Payments

Payments

The payments subsystem includes cart checkout, discount application, order/payment records, Stripe PaymentIntent integration, refunds, and Stripe webhook handling (workers/payments/src/routes/cart.ts:271-648, workers/...

Payments

Current Shape

The payments subsystem includes cart checkout, discount application, order/payment records, Stripe PaymentIntent integration, refunds, and Stripe webhook handling (workers/payments/src/routes/cart.ts:271-648, workers/payments/src/routes/discounts.ts:245-330, workers/payments/src/routes/refunds.ts:37-100, workers/payments/src/routes/payments.ts:722-832).

Cart rows support active, checkout, completed, abandoned, and cancelled lifecycle states, and cart items can represent appointment, event ticket, queue service, add-on, and discount items (workers/payments/src/db/cart-schema.sql:1-35).

Stripe Provider

The Stripe provider stores its API base URL and secret key on construction and treats missing or placeholder keys as test mode (workers/payments/src/providers/stripe.ts:20-27).

Non-test Stripe requests are sent as form-encoded requests to the Stripe API and include the configured bearer token (workers/payments/src/providers/stripe.ts:35-60).

PaymentIntent creation returns a synthetic succeeded test intent in test mode and calls /payment_intents in real mode (workers/payments/src/providers/stripe.ts:62-94).

The provider implements confirm, retrieve, and refund operations, and maps Stripe payment and refund statuses into internal status values (workers/payments/src/providers/stripe.ts:96-177, workers/payments/src/providers/stripe.ts:274-305).

Cart Checkout

Cart checkout validates that the cart is active, not expired, and not empty before it creates order or booking records (workers/payments/src/routes/cart.ts:271-313).

Appointment checkout validates each referenced hold and rejects holds that are inactive or expired before converting them to bookings (workers/payments/src/routes/cart.ts:315-350).

Checkout inserts a pending order, creates bookings from appointment holds, creates related service sessions, marks holds converted, inserts order items, and marks the cart completed in a database batch (workers/payments/src/routes/cart.ts:383-568).

Booking creation during cart checkout carries service deposit, name, booking reference, and check-in code data into the booking flow (workers/payments/src/routes/cart.ts:409-497).

Deposits and Balance

Checkout calculates deposit totals separately from total charge amount and selects deposit-only charging when deposit checkout is requested and deposits exist (workers/payments/src/routes/cart.ts:570-595).

Deposit checkout marks the order deposit_pending, creates a Stripe PaymentIntent with deposit/full metadata, stores the local payment-intent row, returns deposit amount and balance due, and marks bookings deposit_paid when a deposit intent is created in the current implementation (workers/payments/src/routes/cart.ts:596-659).

Stripe webhook success handling updates payment status, order status, and booking deposit status based on the PaymentIntent metadata and event type (workers/payments/src/routes/payments.ts:776-819).

Payment failure webhook handling updates local payment records to failed and records the provider error payload (workers/payments/src/routes/payments.ts:820-827).

Discounts

Discount validation checks active state, validity dates, and maximum use count before a discount can be applied (workers/payments/src/routes/discounts.ts:245-264).

Discount calculation supports percentage and fixed-amount reductions and caps the computed discount at the subtotal (workers/payments/src/routes/discounts.ts:267-275).

Usage increments re-check max_uses immediately before incrementing current_uses, which protects against using a discount past its configured limit (workers/payments/src/routes/discounts.ts:305-330).

Cart discount application is exposed as a cart route and ties discount evaluation to the active cart workflow (workers/payments/src/routes/cart.ts:720-807).

Refunds

Refund creation requires an existing succeeded payment before it will attempt a provider refund (workers/payments/src/routes/refunds.ts:37-50).

The refund route sums existing nonfailed refunds and rejects requests that would refund more than the captured payment amount (workers/payments/src/routes/refunds.ts:53-68).

After provider refund creation, the route inserts and returns the local refund record (workers/payments/src/routes/refunds.ts:70-100).

Webhooks

Stripe webhook verification first extracts the raw body and signature header, then uses async HMAC verification when a webhook secret is configured; without a configured secret, the current fallback fails closed and returns INVALID_SIGNATURE (workers/payments/src/routes/payments.ts:728-749, workers/payments/src/providers/stripe.ts:184-216).

HMAC verification signs {timestamp}.{payload} with SHA-256, enforces a default five-minute tolerance, and uses constant-time comparison for the expected signature (workers/payments/src/providers/stripe.ts:203-271).

Webhook event parsing extracts provider event ID, PaymentIntent ID, order ID, and payment ID before applying event-specific mutations (workers/payments/src/routes/payments.ts:751-775).

Corrections From Frozen HTML

The frozen page's Stripe-provider framing remains correct, but current docs must include deposit checkout, max-use discount enforcement, cumulative refund protection, and HMAC webhook verification because those code paths are present now (workers/payments/src/routes/cart.ts:570-659, workers/payments/src/routes/discounts.ts:305-330, workers/payments/src/routes/refunds.ts:53-68, workers/payments/src/providers/stripe.ts:203-271).


last verified: 2026-04-24, commit 14f3b190db8817399dcd30e1dc4e1ae7674bbf8a