Red Eye Tickets

Production

One checkout, three payment protocols, no duplicate charges

Red Eye had to make cards, Apple Pay, and Google Pay feel like one checkout while respecting different provider contracts, single-use tokens, asynchronous processor outcomes, and a strict boundary around financial data.

February–July 2026Authorize.Net + walletsSubstantial implementation contribution
01

The assumption that changed

The first implementation tried to normalize both wallet providers through Authorize.Net Accept.js. Repository history quickly exposed the flaw: the Apple Pay and Google Pay payloads were already provider tokens, but their accepted descriptors, request shapes, merchant validation, and failure behavior were not interchangeable.

  1. Manual cards: Accept.js tokenizes PAN and CVV in the browser before Red Eye receives opaque data.
  2. Apple Pay: a separately validated Apple payment token goes to Rails as COMMON.APPLE.INAPP.PAYMENT, with server-side merchant validation and certificate readiness.
  3. Google Pay: Google requests an Authorize.Net gateway token that Rails maps as COMMON.GOOGLE.INAPP.PAYMENT.
  4. Shared capture boundary: protocol-specific adapters converge only after validation, at the order and payment-ledger layer.

The lesson was not to force a clean abstraction before understanding each provider. The shared interface belongs around business invariants—order identity, amount, capture state, and ticket issuance—not around dissimilar token protocols.

02

The constraints behind the interface

Different authentication

Browser-safe card identifiers, server gateway credentials, an Apple merchant certificate and domain association, and Google gateway configuration each have separate readiness rules.

Single-use tokens

A legitimate retry receives a fresh token, so the token cannot be the durable identity used to prevent a second charge.

Asynchronous truth

A capture can be processing or lack a transaction ID while a later webhook, settlement sync, or gateway lookup supplies the final state.

Uneven customer data

Wallets can return relay email, missing phone data, or address shapes that cause false AVS declines if handled like manual cards.

03

Correctness decisions

Prevent duplicate money movement

  • Confirm and capture use persisted X-Idempotency-Key values scoped to the action.
  • The capture digest binds to the order and deliberately excludes the replaceable single-use token.
  • Successful replays return the existing payment instead of calling the gateway again.
  • Concurrent requests re-read the persisted record rather than surfacing a uniqueness race to the buyer.

Keep ledger state honest

  • Gateway success without a transaction ID becomes pending_capture; tickets are not issued yet.
  • Late pending or decline events cannot downgrade an already paid order.
  • Signed webhook replay, gateway refresh, and settlement reconciliation repair delayed final state.
  • Refunds, reversals, and chargebacks follow separate state transitions instead of a generic status overwrite.
04

Provider friction became regression coverage

  • Wallet billing fields were removed from the gateway AVS payload after incomplete wallet addresses produced false declines; name and country remain while the cryptogram carries wallet authorization.
  • Google Pay can report a concrete error and then cancel, or hold focus without an immediate callback. The UI preserves the specific error and waits for focus to return before declaring “no callback.”
  • An unsupported top-level Google Pay request field was removed and the accepted schema was locked with a test.
  • A wallet authorization can outlive its checkout session. The product refreshes preview and idempotency state, then asks for a controlled retry.
  • Browser capability alone is not Apple Pay readiness. Server certificate, domain, environment, and merchant-validation checks can hide the wallet and preserve card fallback.
05

Operating the integration

Fail-closed configuration

Production checks validate the card tokenization host and integrity policy, Apple certificate and domain material, and Google Pay's deployed environment before buyers depend on them.

Behavior monitoring

Method-health windows distinguish tokenization from capture failures so a broken wallet can be identified without calling every checkout problem a decline.

Reconciliation

Pending sync, signed webhooks, settlement comparison, transaction refresh, refund sync, and ledger audits cover different ways gateway and local state can drift.

Privacy boundary

Logs filter tokens, opaque data, idempotency keys, PAN, and CVV. Reporting keeps constrained aggregates rather than raw processor rows.

06

Inspecting the evidence

The implementation is committed across the browser, Rails capture boundary, reconciliation services, monitoring, tests, and runbooks. These are representative entry points rather than a claim that one file owns the system.

  • ApplePayButton.jsx and GooglePayButton.jsx: provider-specific requests, contact normalization, fallback, and callback recovery.
  • apple_pay_controller.rb and Wallets::ApplePayReadiness: URL allowlisting, mutual TLS merchant validation, and certificate checks.
  • orders/payments_controller.rb: descriptor and token validation, provider adapters, order-bound idempotency, pending state, and safe errors.
  • authorize_net/webhook_controller.rb and Orders::ReconcileFromWebhook: HMAC validation, replay defense, and out-of-order state handling.
  • Request, component, service, and job specs cover provider mapping, wallet AVS, callback anomalies, idempotent replay, settlement, refunds, and stale events.

Evidence limit: the guarded live purchase/refund lane exercises manual card entry. Wallet paths have component and request coverage; full automated live-wallet E2E is not claimed.

07

Outcome, ownership, and what I would revisit

I authored the core provider-specific adapters and much of the readiness and recovery work; two later public-checkout and release-safety changes were co-authored. The shipped result supports provider-specific Apple Pay and Google Pay handling on the Authorize.Net capture pipeline, with duplicate-charge prevention, pending-state recovery, webhook reconciliation, settlement repair, configuration monitoring, and release gates.

  • Add 3-D Secure only with an explicit liability, conversion, and recovery design; fields exist, but no 3DS flow is enabled.
  • Continue reducing dependence on runtime third-party script availability without weakening production host and integrity checks.
  • Attach a dated, cohort-defined analytics note before attributing any conversion or failure-rate change to wallet rollout.
  • Expand guarded live-wallet coverage before treating component and request tests as complete end-to-end proof.