# Checkout & payment security

Atlas Pay uses Stripe Elements for secure card entry, with server-priced orders and authorization before capture.

Prepared by Kyle Campbell. Developer preview, September 9, 2026. These are implemented controls and launch requirements, not a fraud-free guarantee or PCI certification.

## Where card details go

The buyer signs in, reviews the merchant and total, and explicitly agrees to proceed. Stripe-hosted fields collect card details on the Atlas Pay page. Card numbers and CVCs go directly to Stripe, never through Atlas chat, application JSON, or D1. The backend creates a Checkout Session using `ui_mode: "elements"`; its private client secret is returned only to the authenticated buyer and retained in page memory, not URLs or browser storage. The browser uses `CheckoutElementsProvider` and `checkout.confirm`. Ordinary merchant reads and webhooks never disclose that secret. The separate [merchant Elements API](https://dashboard.atlaspay.sh/docs/payment-elements) returns its own session secret only on creation or an open creation replay. [Stripe Elements integration](https://docs.stripe.com/payments/quickstart).

Hosted checkout remains available for existing integrations. Changing UI mode after an order/setup is claimed returns a conflict instead of creating another payment. WordPress and Wix still use their existing hosted checkout contracts; this release does not silently replace them.

## Controls and their limits

| Signal or control | Implemented behavior | Important limitation |
| --- | --- | --- |
| 3D Secure | New card setups and catalog order sessions request `any`. Stripe handles the challenge. Failed/errored authentication prevents delivery capture. | The issuer chooses frictionless/challenge behavior; requesting 3DS does not guarantee authentication or liability shift. |
| IP and location | The Vercel server signs its trusted IP/country context. The Worker checks the signature, 60-second timestamp, method, path, original body and session cookie. | Raw browser-supplied IP/location headers are not trusted. Local testing does not manufacture a real buyer IP. |
| Browser and device | Coarse browser/device categories and a signed first-party browser cookie provide supplementary signals. | User agents can be spoofed and cookies can be reset. This is not hardware fingerprinting or identity verification. |
| Multiple cards | Atomic setup limits, a ten-card/pending-setup bound, and deduplication of saved Stripe card fingerprints within a wallet. | Card fingerprint availability and wallet tokenization can vary. It does not prove that two users are the same person. |
| Velocity | D1 atomically limits new setup/payment session issuance by buyer, keyed IP and browser identifier. | This does not count every card submission inside an existing Stripe form. Connected-account Radar rules must cover card-testing/attempt velocity. |
| Country mismatch | IP, card issuing, billing and shipping countries are compared where available and recorded as a supplementary mismatch signal. | Travel, gifts and cross-border commerce are legitimate. A mismatch alone does not decline payment. |
| VPN/proxy | Stripe Radar can evaluate known anonymous-IP signals. Atlas records VPN status as unknown, not a guessed boolean. | There is no comprehensive VPN detector configured. A proxy or VPN alone is not proof of fraud. |
| CVC/postal checks | Failed CVC or postal checks place an authorization in review before delivery capture. | Unsupported/unavailable checks are not treated as a pass or automatically as fraud. |
| Radar risk/review | Highest risk blocks capture. Elevated risk and open/unapproved reviews stay on hold. Review status is retrieved from Stripe again before capture. | Approving a review cannot override a highest-risk result, a failed check, or the other payment invariants. |

Manual 3DS requests override Stripe's dynamic 3DS-request rules for these sessions. Configure the remaining Radar block/review rules deliberately and assess conversion effects. Direct charges use the **connected account's** Radar configuration, not automatically the platform's rules. Known anonymous-IP support is not a promise to detect every VPN. [3DS behavior](https://docs.stripe.com/payments/3d-secure/authentication-flow), [Connect and Radar](https://docs.stripe.com/connect/radar), [Radar attributes](https://docs.stripe.com/radar/rules/supported-attributes).

### Current conservative issuance limits

| Operation | Window | Per buyer | Per IP | Per browser identifier |
| --- | --- | --- | --- | --- |
| New card setup | 1 hour | 5 | 20 | 10 |
| New order payment session | 15 minutes | 20 | 50 | 30 |

Identical operation retries reuse their slot. Limits are adjustable policy choices, not universal fraud thresholds. Shared networks may affect legitimate buyers; support must have a documented review process. HMAC identifiers, coarse location/categories and processor assessments are scheduled for bounded cleanup after 30 days. No precise GPS or canvas/font fingerprint is collected. Infrastructure/Stripe logs have separate retention policies. See [Privacy](https://dashboard.atlaspay.sh/privacy).

## Authorization is not payment capture

1. The merchant creates an immutable order from its catalog. The client cannot change the amount.
2. The buyer confirms in Stripe Elements. A verified `requires_capture` payment becomes an authorization hold, not paid revenue.
3. Atlas checks the original connected account, currency, amount, customer, metadata, shipping address, risk/review and card capture deadline.
4. A trusted delivery service with `orders:deliver` records actual delivery. Atlas rechecks the payment and captures the existing hold once.
5. An uncaptured cancellation uses `orders:cancel`. Paid/refunded/disputed/expired/uncertain states cannot start another charge automatically.

Ordinary single-capture intents must omit `final_capture`; Stripe reserves that option for multicapture-enabled payments. A processor idempotency conflict requires reconciliation, not a new key or new payment. A browser redirect or success callback never authorizes fulfillment. See [Delivery & payment holds](https://dashboard.atlaspay.sh/docs/delivery).

## Permanent platform payment links

Links under `/pay/link/{mode}/{id}` collect immediate payments into Atlas Pay's main Stripe account. Only the platform administrator can create or disable these links. The shareable link has no expiry; each buyer explicitly starts a separate expiring payment session. Guest checkout uses signed frontend context, the same IP/browser issuance limits, billing-address collection, and a 3D Secure request. Guest references are capabilities, not verified buyer identities. Session secrets stay in page memory, and status is reconciled against the original amount, currency, metadata, mode and current Stripe payment state.

The main account's Radar settings apply to these payments. Atlas records processor assessments and reports pending review, but this flow does not implement the catalog's delivery holds, capture gate, automatic refunds, or fulfillment. Platform operators manage reviews and refunds in Stripe. Disabling a link stops new sessions; an already-issued session may still complete.

## Browser and webhook defenses

Checkout documents use per-request script nonces, a restricted content security policy, no framing, no caching, no referrers, and disabled camera/microphone/geolocation permissions. Stripe's required iframe/connect origins remain permitted for bank authentication. General analytics are excluded from wallet and checkout routes. Nonce-bearing pages are dynamically rendered; don't cache their HTML. Third-party scripts should remain limited to the Stripe payment integration. [Stripe security guidance](https://docs.stripe.com/security/guide).

Buyer writes enforce exact origin, authentication, ownership, strict JSON schemas and an 8 KiB streamed-body limit. Production setup/payment issuance requires verified frontend context even for hosted mode; changing a JSON option cannot bypass that requirement. Webhooks verify the original body and signature, enforce mode, deduplicate IDs and bound payload size to 1 MiB. Order handlers reconcile current processor state instead of trusting stale event snapshots. Secrets, card details and Checkout URLs must not appear in logs.

## Deployment and acceptance gates

1. Apply D1 migrations through `0012_checkout_security.sql` after a backup and rehearsal. The production database was migrated on September 9, 2026; frontend builds do not apply migrations.
2. Configure the same random **server-only** `ATLAS_CHECKOUT_CONTEXT_SECRET` in Vercel and the Worker. Configure the matching Stripe publishable key as `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`. Never expose the signing key under a `NEXT_PUBLIC_` name.
3. Set a platform card-only wallet payment-method configuration and a **Connect parent** card-only delivery configuration. A configuration created for one test connected account is not a platform-wide configuration. Confirm child inheritance and merchant eligibility. [Connect configuration inheritance](https://docs.stripe.com/connect/multiple-payment-method-configurations).
4. Verify the connected-account Radar rules, particularly card-attempt velocity, anonymous-IP handling and elevated-risk review. No paid Radar plan or third-party IP-intelligence subscription has been enabled by this code change. Establish who reviews holds and alerts; API controls alone are not an operations team.
5. Add `review.opened`, `review.closed`, `charge.updated` and the existing payment/authorization events to the signed Connect webhook. Verify event delivery and cron recovery against the deployed Worker.
6. Configure checkout/dashboard DNS, HTTPS, OAuth callbacks and Worker origins. Deploy the Worker and frontend together before enabling `NEXT_PUBLIC_ATLAS_ELEMENTS_ENABLED=true` at build time. Enable live features after validating their mode-specific Stripe configuration. Selecting live mode does not bypass business verification or enable optional Elements and wallet features.
7. Complete actual browser tests for success, 3DS success/failure, insufficient funds, cancellation, expiry, network loss, card removal, capture/release, refunds and callback recovery. Repeat on real WordPress/Wix test stores and native Atlas clients. Verify geographic availability and your PCI obligations with Stripe. See [Testing & launch](https://dashboard.atlaspay.sh/docs/testing).

## Still outside this release

No claim is made of live-card certification, comprehensive VPN detection, approval for every country, completed marketplace acceptance, unattended chat charging, carrier proof-of-delivery validation, identity/KYC replacement, production load capacity, or an external penetration test. Do not enable live funds on the strength of unit tests alone.

---

Source: [Atlas Pay — Checkout & security](https://dashboard.atlaspay.sh/docs/payment-security)

Prepared by Kyle Campbell
