# Save a card and charge it later Building with an AI agent? Start at https://docs.payme.io/agents.md ## Prerequisites - Seller's seller_payme_id (MPL), kept on your server - A public HTTPS URL for callbacks (not localhost or a private IP) ## Related - [Save a card (token sale)](/payments/tokenization/post-generate-sale-tokenization) - [Save a card as a token](/payments/tokenization/post-capture-buyer-token) - [Get the buyer key of a sale or subscription](/payments/tokenization/post-get-buyer-key) - [Create a sale (hosted payment page)](/payments/hosted-payment-page/post-generate-sale) - [Hosted Fields JSAPI guide](/accepting-payments/checkout-and-in-person/hosted-fields-jsapi-guide) - [Build recurring billing with subscriptions](/accepting-payments/capture-and-saved-cards/build-recurring-billing) - [Test Cards and Payment Methods](/payments/getting-started/test-cards-and-payment-methods) Save a buyer's card once as a token, the `buyer_key`, and charge it from your server whenever you need to: a repeat order, a top-up, or a charge after delivery. The buyer doesn't have to be present, and the card number never reaches your servers. This guide covers the ways to save a card, the two settings that decide what a token can do, how to charge it, and how to keep tokens safe. To charge a saved card on a fixed schedule, see [Build recurring billing with subscriptions](/accepting-payments/capture-and-saved-cards/build-recurring-billing). ## Before you start - **Credentials.** The seller's `seller_payme_id` (MPL), in the JSON body. Partners also send `payme_client_key`. Keep both on your server: together with a `buyer_key`, the MPL is all it takes to charge the card. See [Authentication](/payments/getting-started/authentication). - **Base URL.** `https://sandbox.payme.io/api/` in the sandbox, `https://live.payme.io/api/` in production. A token works only in the environment where it was saved. - **A callback URL.** A public address for `sale_callback_url`. `localhost` and private IP addresses are refused with error `21`. See [Callbacks](/payments/getting-started/callbacks). ## 1. Save the card | Way | Charges the buyer? | Who handles the card | Use it when | |---|---|---|---| | `capture_buyer: 1` on [`generate-sale`](/payments/hosted-payment-page/post-generate-sale) | Yes, the sale amount | PayMe's payment page (`sale_url`) | The first order should also save the card. | | `sale_type: token` on [`generate-sale`](/payments/tokenization/post-generate-sale-tokenization) | No | PayMe's payment page (`sale_url`) | You want a card on file before any charge, for example at sign-up. | | Hosted Fields `tokenize()` | No | PayMe's card fields inside your own checkout | Your checkout must look native to your site. | | [`capture-buyer-token`](/payments/tokenization/post-capture-buyer-token) | No | Your server sends the card number | You are PCI DSS Level 1 certified. | Saving works with the payment methods `credit-card`, `apple-pay`, `il-direct-debit` and `multi`. Other methods return error `4`. ### Save while charging (`capture_buyer`) Add `capture_buyer: 1` to a regular `generate-sale` and show the buyer the `sale_url`. When the buyer pays, PayMe charges the card, saves it, and sends the `buyer_key` in the `sale-complete` callback. ```json { "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_price": 5000, "currency": "ILS", "product_name": "First order", "transaction_id": "order-1001", "capture_buyer": 1, "buyer_is_private": true, "sale_callback_url": "https://your-server.example.com/payme/callback", "language": "en" } ``` ### Save without charging (`sale_type: token`) Send `sale_type: token` on `generate-sale` and show the buyer the `sale_url`. Nothing is charged: PayMe sets the price to `100` and doesn't charge it, but `product_name` and a numeric `sale_price` are still required. The `buyer_key` arrives in the `sale-complete` callback. For a page that can be opened only once, add `layout: token-capture` and `sale_should_be_opened_once: true`. ```json { "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_type": "token", "sale_price": 100, "currency": "ILS", "product_name": "Save card", "buyer_is_private": true, "sale_callback_url": "https://your-server.example.com/payme/callback", "language": "en" } ``` ### Save in your own checkout (Hosted Fields) In the browser, `instance.tokenize()` returns a `token`: that is the `buyer_key`. Send it to your server and charge it there. A Hosted Fields token is permanent unless you create the instance with `tokenIsPermanent: false`, and account-level unless you pass `buyerIsPrivate: true` (both explained below). Setup and code: [Hosted Fields JSAPI guide](/accepting-payments/checkout-and-in-person/hosted-fields-jsapi-guide). ### Save from your server (`capture-buyer-token`) `capture-buyer-token` takes the card number, expiry and CVV, so use it only if you are PCI DSS Level 1 certified. It returns the `buyer_key` in the response. **The token is single-use unless you send `buyer_is_permanent: true`.** ## 2. Decide what the token can do Two settings, sent when you save the card, decide how often the token can be charged and who can charge it. ### Single-use or permanent | Saved with | Default | |---|---| | `capture_buyer` or `sale_type: token` | Permanent. | | Hosted Fields | Permanent. `tokenIsPermanent: false` makes it single-use. | | `capture-buyer-token` | **Single-use.** `buyer_is_permanent: true` makes it permanent. | A permanent token can be charged any number of times and doesn't expire in PayMe. A single-use token is deactivated by the first charge, even when that charge is declined. Later charges return error `604` ("Buyer inactive"), and a subscription refuses it with error `376`. Save permanent tokens unless you want exactly one charge. ### Seller-level or account-level (`buyer_is_private`) | | `buyer_is_private: true` (seller-level) | Not sent (account-level, the default) | |---|---|---| | Who can charge it | Only the seller (MPL) that saved it | Any seller of the same partner | | Read it back with [`get-buyer-key`](/payments/tokenization/post-get-buyer-key) | `seller_payme_id`, or a partner's `payme_client_key` | `payme_client_key` only. Sending `seller_payme_id` returns error `603`. | In Hosted Fields the option is `buyerIsPrivate` (default `false`). - **Direct sellers:** send `buyer_is_private: true`. Without a partner key you can't read an account-level token back. - **Partners:** an account-level token lets several of your sellers charge the same buyer's card. Use seller-level tokens unless you need that. - Error `603` comes only from `get-buyer-key`. A seller charges its own account-level token with its MPL as usual. A token that belongs to another seller, or to another partner, returns error `605` when you charge it. ## 3. Charge the saved card Send the `buyer_key` to [`generate-sale`](/payments/hosted-payment-page/post-generate-sale) from your server. The call charges the card at once: there is no payment page, no `sale_url`, no CVV and no 3D Secure. ```json { "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "buyer_key": "BUYERDEMO-BUYERDEMO-BUYERDEMO-1234567", "sale_price": 5000, "currency": "ILS", "product_name": "Repeat order", "transaction_id": "order-1002", "sale_callback_url": "https://your-server.example.com/payme/callback", "language": "en" } ``` The response holds the result (abridged): ```json { "status_code": 0, "payme_status": "success", "status_error_code": 0, "payme_sale_id": "SALE1700-000000AA-EXAMPLE1-SALE0001", "payme_sale_code": 12345678, "sale_status": "completed", "sale_payment_method": "credit-card", "price": 5000, "currency": "ILS", "transaction_id": "order-1002", "payme_transaction_id": "TRAN1700-000000AA-EXAMPLE1-TXN00001", "buyer_card_mask": "532610******5846", "sale_3ds": false } ``` A declined charge also returns **HTTP 200**, with `status_code: 1` (abridged): ```json { "status_code": 1, "status_error_code": 20004, "status_error_details": "Refusal by credit company. Please try a different card", "payme_sale_id": "SALE1700-000000AA-EXAMPLE1-SALE0002", "sale_status": "failed" } ``` - **Check `status_code`, not the HTTP status.** Request errors, such as an unusable token or a field that can't be combined with `buyer_key`, also return `status_code: 1`, with an HTTP error status. - **Every call creates a new sale**, even when the charge is declined. PayMe doesn't de-duplicate `transaction_id`, so a repeated call charges again. After a timeout, look the sale up with [`get-sales`](/payments/query-apis/post-get-sales) before you retry. See [Idempotency and Retries](/payments/getting-started/idempotency-and-retries). - **Callbacks** follow as for any sale: `sale-complete` for a charge, `sale-failure` for a decline. - **The token's payment method** is used, whatever `sale_payment_method` you send. - **To hold the amount instead of charging it**, add `sale_type: authorize`. The sale becomes `authorized`, and you capture it later: see [Authorize now, capture later](/accepting-payments/capture-and-saved-cards/capture-sale). Because the buyer isn't there, nothing is checked with them when you charge: no CVV and no 3D Secure. Charge a saved card only for payments the buyer has agreed to. ## 4. Get a token back - **From the save itself.** The `sale-complete` callback of a `capture_buyer` or `token` sale carries `buyer_key`. `capture-buyer-token` returns it in the response, and Hosted Fields returns it from `tokenize()`. - **If you missed it.** Call [`get-buyer-key`](/payments/tokenization/post-get-buyer-key) with the `payme_sale_id` of the paid sale that saved the card. For a subscription whose card was entered on `sub_url`, send `sub_payme_id`: subscription calls and callbacks never return the `buyer_key`. - **Same card, same key.** Saving the same card again for the same buyer and seller can return the `buyer_key` you already have. Store one record per `buyer_key`. ## 5. Store tokens safely - **Treat `buyer_key` like a card number.** With the seller's MPL it charges the card. Store it encrypted, on your server only. - **Keep it out of the browser.** Never send `buyer_key` or the MPL to a web page or an app, and keep both out of URLs and logs. - **Store what you show, not the card.** Keep `buyer_card_mask` and the expiry next to the token, so the buyer can see which card is on file. Never store the card number or the CVV: you don't need them. - **Record how it was saved:** which seller saved it, and whether it is permanent and seller-level. - **When a buyer removes a card,** delete the `buyer_key` on your side and stop charging it. ## 6. Keep saved cards up to date (Card Updater) Cards expire, and card companies replace lost or stolen ones. A charge on the old card then fails, and you have to save the new card and replace the stored `buyer_key`. With the Card Updater service active on the seller's account, PayMe updates saved cards when an Israeli card company reports a replacement card. The `buyer_key` stays the same, so stored tokens and subscriptions keep working. The new masked number and expiry show the next time you read the token with `get-buyer-key`. PayMe sends no callback when it updates a card. To activate Card Updater, contact PayMe. ## Errors | Code | When | What to do | |---|---|---| | `601` | `buyer_key` sent with `capture_buyer` | Send one or the other. | | `602` | `buyer_key` sent with `sale_type` `token` or `seller-token` | Charge with a regular sale type. | | `606` | `buyer_key` sent with `sale_type: template` | A template can't be charged with a token. | | `357` | `buyer_key` sent with `sale_type: multi-capture` | Capture the original authorization instead. | | `604` | The token is inactive, or a single-use token was already charged | Save the card again, as a permanent token. | | `605` | The token belongs to another seller or another partner | Charge with the seller that saved it. | | `603` | `get-buyer-key` with `seller_payme_id` for an account-level token | Send `payme_client_key` instead. | | `600` | `get-buyer-key` found no saved card on the sale or subscription, or the sale isn't paid | Check the ID, and that the sale was paid. | | `130` | The charge is over the seller's processing limit | Contact PayMe. | | `4` | Saving isn't supported for the sale's payment method | Use one of the methods listed in step 1. | | `608` | `buyer_perform_validation` sent without `capture_buyer` or a token sale | Remove it, or save a card in the same request. | | A decline code, for example `20004` | The card issuer declined the charge (HTTP 200, `status_code: 1`) | Ask the buyer for another card. | All codes: [Errors](/payments/getting-started/errors). ## Test it in the sandbox - Save and charge with `5326105300985846`, expiry `12/30`, CVV `658`, social ID `008336174`. It's a local Israeli card that accepts ILS only. - `4000000000000036` saves through Hosted Fields or `capture-buyer-token`, and a later charge with its token is declined with `20004`. On a payment page with `capture_buyer` or `sale_type: token`, it's declined at once and no token is saved. - To see the single-use rule, save a card with `capture-buyer-token` without `buyer_is_permanent` and charge it twice: the second charge returns `604`. - Save and charge sandbox tokens with sandbox credentials on `https://sandbox.payme.io`. More cards: [Test Cards and Payment Methods](/payments/getting-started/test-cards-and-payment-methods).