3DS with Hosted Fields

When 3D Secure is active on the seller's account and a sale meets its selection rules (a foreign card, an amount above the account's threshold, or 3 or more installments), pay the Hosted Fields token with pay-sale and the buyer's browser data. PayMe then runs 3D Secure: either frictionless, or with a challenge screen the buyer completes. No services parameter is needed to turn it on.

Always send the browser data (meta_data_jwt). Without it, pay-sale charges the token without 3D Secure.

The challenge screen opens at the redirect_url PayMe returns. If you restrict hosts with allow-lists or a Content Security Policy, allow the host of that URL. See Environments.

Step 1: Tokenize

Tokenize the card as in the Hosted Fields guide's minimal integration. The result's token is the buyer_key. The 3D Secure part needs no extra HTML.

Step 2: Collect the browser data

In the same page, call PayMe.clientData(testMode) and send the hash it resolves to your server, together with the token:

<script src="https://cdn.payme.io/hf/v1/hostedfields.js"></script>
<script>
  const testMode = true; // the same value you pass to PayMe.create

  PayMe.clientData(testMode)
    .then(({ hash }) => {
      // Send hash and the token to your server.
    })
    .catch((error) => {
      // Without the hash, the payment would run without 3D Secure: show an error and stop.
    });
</script>

Pass the same testMode you use for PayMe.create: PayMe.clientData(true) in the sandbox, PayMe.clientData(false) (or no argument) in production. The data is encrypted for that environment. If your site sets a Content Security Policy, allow connect-src https://hf.payme.io.

Step 3: Create the sale

On your server, call generate-sale without a buyer_key, and keep the payme_sale_id it returns. Set the two URLs here: pay-sale doesn't take them.

{
  "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567",
  "sale_price": 1000,
  "currency": "ILS",
  "product_name": "Order 1234",
  "sale_payment_method": "credit-card",
  "sale_callback_url": "https://www.example.com/payment/callback",
  "sale_return_url": "https://www.example.com/payment/success"
}

The response has other fields as well; you need payme_sale_id:

{
  "status_code": 0,
  "payme_sale_id": "SALE1655-3843870J-NTPYLJNW-SOWRLK9X"
}

Step 4: Pay the sale

On your server, call pay-sale with the sale, the token and the hash:

{
  "payme_sale_id": "SALE1655-3843870J-NTPYLJNW-SOWRLK9X",
  "buyer_key": "BUYERDEMO-BUYERDEMO-BUYERDEMO-1234567",
  "meta_data_jwt": "<hash from PayMe.clientData>"
}

With a buyer_key, the buyer's name, email and ID come from the token, so don't send them. installments is read only when the sale lets the buyer choose the number of installments.

POST it to https://sandbox.payme.io/api/pay-sale, or https://live.payme.io/api/pay-sale in production.

Step 5: Handle the result

status_code Meaning What to do
5 A challenge is needed Send the buyer's browser (the top window) to redirect_url. The result is posted to sale_callback_url, and the buyer lands on sale_return_url.
0 Paid without a challenge: sale_status is completed Confirm, then fulfil the order
1 Declined: sale_status is failed status_error_code and status_error_details say why

Don't read payme_status: it is success on a declined payment too. status_message and status_error_details are in Hebrew unless you add "language": "en" to the pay-sale request.

A challenge:

{
  "status_code": 5,
  "status_message": "Generating validation page, please wait",
  "redirect_url": "<challenge page URL>"
}

Paid without a challenge (other fields omitted):

{
  "status_code": 0,
  "payme_status": "success",
  "status_error_code": 0,
  "payme_sale_id": "SALE1655-3843870J-NTPYLJNW-SOWRLK9X",
  "payme_sale_code": 20511311,
  "sale_status": "completed",
  "currency": "ILS",
  "price": 1000,
  "payme_transaction_auth_number": "1172408",
  "buyer_card_mask": "490763******9022",
  "sale_3ds": true
}

Declined (other fields omitted):

{
  "status_code": 1,
  "payme_status": "success",
  "status_error_code": 20004,
  "status_error_details": "Refusal by credit company. Please try a different card",
  "payme_sale_id": "SALE1655-3843870J-NTPYLJNW-SOWRLK9X",
  "sale_status": "failed",
  "currency": "ILS",
  "price": 1000
}

Before you fulfil, confirm the payment with the callback or with get-transactions. sale_is_3ds there tells you whether the payment was authenticated with 3D Secure.

Test in the sandbox

The sandbox applies the same selection rules as production, so use a card and amount that trigger 3D Secure. See 3D Secure in the sandbox for the test cards.

Move to production

In production, call PayMe.create(publicKey, { testMode: false }) with the production public key and PayMe.clientData(false), and send the server calls to https://live.payme.io/api.