> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.paymentkit.com/guides/integration/sdk-reference/payment-kit-js/one-click-saved-cards/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server. # One-click saved cards One-click lets a returning customer pay with a card they saved on an earlier visit — a single confirmation, no retyping the card, all inside your PaymentKit.js checkout. It builds on the [Card payments](/guides/integration/sdk-reference/payment-kit-js/card-payments) flow, so start there if you don't have a card checkout working yet. PaymentKit.js gives you three things: the customer's saved-card list, an optional CVC re-entry field, and the confirm call. **You build the card picker UI yourself** — there is no prebuilt picker element. # Before you begin * Your account routes to **Stripe** (the only processor that supports one-click saved-card charges today — see [Limitations](#limitations)). * The checkout session is created with a `customer_id` — one-click is never anonymous. # Create the checkout session Create the session server-side with your secret key. Use `mode: "payment"` and a `customer_id`, then hand the returned `secure_token` to PaymentKit.js in the browser. ```bash curl -X POST https://app.paymentkit.com/api/{account_id}/checkout-sessions \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "mode": "payment", "customer_id": "cus_abc123", "line_items": [ { "price_id": "price_xyz", "quantity": 1 } ], "default_payment_method_id": "pm_abc123", "saved_card_cvc_reprompt": true, "success_url": "https://yoursite.com/success", "return_url": "https://yoursite.com/cancel" }' ``` * `default_payment_method_id` — optional. Suggests which saved card to preselect. It's a prefill hint, not a lock; the customer can still pick another card, and the confirm call decides what is charged. * `saved_card_cvc_reprompt` — optional, set **per session at create time** (there is no account-level setting). `true` asks the customer to re-enter their security code, which generally improves approval rates. Omitted, it defaults to `false` (pure one-click, no CVC). > **Info** > > If you run your own backend, you can also list a customer's saved cards server-side with your secret key. In the browser, use `listSavedCards()` (below) instead. # Charge the saved card in the browser Initialize PaymentKit.js with the card payment method, exactly as in [Card payments](/guides/integration/sdk-reference/payment-kit-js/card-payments): ```typescript import PaymentKit from '@payment-kit-js/vanilla'; import CardPaymentMethod from '@payment-kit-js/vanilla/payment-methods/card'; const paymentKit = PaymentKit({ environment: 'sandbox', secureToken: 'aBcDeFgHiJkLmNoPqRsTuVwXyZ123456', // secure_token from your backend paymentMethods: [CardPaymentMethod] }); ``` ## List the saved cards `listSavedCards()` returns the session customer's saved cards. Preselect the one flagged `isDefault` (this reflects `default_payment_method_id`), or the first card. Render the picker with your own UI. ```typescript const result = await paymentKit.card.listSavedCards(); if ('errors' in result) { // Couldn't load the cards — fall back to the normal card form. showFreshCardForm(); } else { const cards = result.data; // [{ id, brand, last4, expMonth, expYear, isDefault, cmpCardId }] const selected = cards.find((c) => c.isDefault) ?? cards[0]; renderPicker(cards, selected); // your UI } ``` If a customer has no saved cards, show your normal card form instead. ## Re-prompt for the CVC when required Read the session's setting with `getSavedCardCvcReprompt()`. When it's on, mount a CVC-only field for the selected card and **disable the pay button until `onLoaded` fires** — this stops a fast click from firing a CVC-less charge before the field is ready. ```typescript const repromptResult = await paymentKit.card.getSavedCardCvcReprompt(); // Fail safe: on an error, treat re-prompt as ON — never silently charge without a CVC. const cvcRequired = 'errors' in repromptResult ? true : repromptResult.data; if (cvcRequired) { const payButton = document.querySelector('#pay-button'); payButton.disabled = true; // gate until the field is ready const cvcElement = paymentKit.card.createSavedCardCvcElement({ cmpCardId: selected.cmpCardId, // re-collect the CVC against the chosen card placeholder: 'CVC', onLoaded: () => { payButton.disabled = false; } }); const mountedCvc = cvcElement.mount('#saved-card-cvc'); // When the customer picks a different card, re-gate: mountedCvc.unmount(), // then create and mount a new element with the new card's cmpCardId. } ``` | Option | Type | Description | | --------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `cmpCardId` | `string` | The selected card's `cmpCardId`, so the CVC is re-collected against the right card. Re-mount when the selected card changes. | | `placeholder` | `string` | Placeholder text for the input | | `style` | `Record` | CSS properties applied to the input inside the iframe | | `onLoaded` | `() => void` | Called when the field is ready to accept input | | `onFocusChange` | `(isFocused: boolean) => void` | Called when the input gains or loses focus | > **Warning** > > **PaymentKit does not enforce CVC re-prompt on saved cards.** If you don't gate the charge on the CVC field (or collect a CVC another way), the charge is captured **silently without a CVC**, which lowers approval rates. Always disable the pay button until `onLoaded` fires, and if `getSavedCardCvcReprompt()` returns an error, treat re-prompt as **on** rather than firing a CVC-less charge. ## Confirm the charge Call `confirmSavedCard()` with the selected card's `id` and `cmpCardId`, so a re-prompted CVC is re-collected against the right card. > **Info** > > `confirmSavedCard()` returns a **Promise** — it does **not** take the `onSuccess` / `onError` callbacks that `submit()` uses. Branch on the resolved result instead. ```typescript const result = await paymentKit.card.confirmSavedCard(selected.id, { cmpCardId: selected.cmpCardId }); if ('errors' in result) { // errors.root — a customer-facing message. // errors.card_cvc === 'invalid' — the CVC re-collection failed. // errors.root === 'No saved card was selected' — no card id was passed. showError(result.errors.root ?? 'Payment failed'); } else { // result.data is the checkout response — the charge succeeded. window.location.href = '/success'; } ``` 3D Secure is handled inline, identically to a fresh card — no extra code. For decline details (`errors.checkout_response`) and 3DS requirements, see [Card payments](/guides/integration/sdk-reference/payment-kit-js/card-payments#3d-secure). ## Let the customer use a different card There's no swap API — this is just your own UI state. To let the customer enter a fresh card, unmount the CVC element and render the ordinary card elements, then charge with `submit()`: ```typescript mountedCvc.unmount(); const cardNumber = paymentKit.card.createElement('card_pan').mount('#card-number'); const cardExpiry = paymentKit.card.createElement('card_exp').mount('#card-expiry'); const cardCvc = paymentKit.card.createElement('card_cvc').mount('#card-cvc'); // ...then paymentKit.submit({ ... }) — see the Card payments guide. ``` # Limitations * **Stripe only today.** A one-click saved-card charge routed to any other processor is rejected before any charge is attempted. Enable it on a Stripe route. * **You build the picker.** PaymentKit.js gives you the card list, the CVC field, and the confirm call — not a prebuilt picker component. Render the list yourself. * **PaymentKit.js version.** Requires `@payment-kit-js/vanilla >= ` — the release that adds `listSavedCards`, `getSavedCardCvcReprompt`, `createSavedCardCvcElement`, and `confirmSavedCard`. > **Note** > > Replace `` with the published version before this page ships — check `npm view @payment-kit-js/vanilla dist-tags`. These methods are not in a released build yet. > Let a returning customer pay in one click with a card they saved earlier.