Reference Checkout TEST ONLY

The Payment Element, and the rules worth knowing before you wire production.

Integrator hard rules (do not skip)

Canonical list: 27-integration-hard-rules.md. View Source for what this page deliberately does not contain.

Your test keys not set

Same merchant, test mode. The secret key is POSTed only to /api/demo/intent (this page’s tiny server half) and never to the SDK. Allowed domains on the publishable key must include this page’s host (e.g. localhost or app.paygeyt.com).

Checkout

Card fields, wallets and the 3-D Secure challenge are all inside this frame. Test cards: 4111 1111 1111 1111 approves, and so do the other sandbox test numbers in the testing guide. A decline needs an amount under 1.00, which this fixed-amount demo cannot produce. The same card at the same amount within 20 minutes is refused as a duplicate, so switch cards between tries. Any future expiry, CVV 123.

The browser half, in full

// your server: POST /v1/payment_intents (sk_) -> { client_secret } const wp = WorthyPayments('pk_test_…'); wp.mountPaymentElement('#payment', { clientSecret, onSuccess: (r) => { /* succeeded | processing */ }, onError: (e) => { /* card_declined, element stays mounted */ }, });

What runs where

In the frame (not your page): card entry, encryption, wallets, device signals, the 3DS challenge.

On our servers, always: pre-screen, scoring, device intelligence, the 3DS decision, the block gate. On every charge, with or without a decision token.

Derived server-side (body ignored if you send it): charge IP, is_mit, is_new_card, whether the 3DS cryptogram is real.

In your page: the mount call and your own UI. That is all, and it is the point.

Before production

Allowed domains must be set on the publishable key (Settings → Developers → API keys), or the frame renders blank. Update the list on the key without rolling the secret. Parent pages must be https://. http://localhost and http://127.0.0.1 are the local exception.

Verify the charge.succeeded webhook signature and fulfil from it, not from onSuccess.

Console. The flow logs here.