Skip to main content

Process 3DS Payments (Iframe)

This guide walks through authenticating a card payment with 3D Secure using the iframe transport: the 3DS session is created from the browser with the Elements SDK, and any challenge is rendered in an iframe directly on your checkout page. For a conceptual overview and the comparison with the redirect transport, see 3D Secure.

The flow has six steps: tokenize the card → create a Payment Intent → create a 3DS session → confirm → (if challenged) render the challenge → complete. The SDK runs in the browser with your Publishable Key; the Payment Intent confirm and complete calls run on your server with your Secret Key.

Before you start​

Credova account
Sign up or sign in to the Credova Portal.
Secret Key
Authenticates requests from your server. Never send it to the browser.

Prerequisites​

This guide assumes you have:

  • 3DS enabled on your account. 3DS is off by default — contact support to enable it.
  • The Elements SDK initialized with your Publishable Key. See the JavaScript or React SDK docs.

Process a 3DS Payment​

Complete these in order. The browser steps use the Elements SDK. The server steps call the Credova API with your Secret Key.

Tokenize the Card​

Collect the card with Elements and tokenize it. See Save Cards for the Card Elements setup. The response includes the card id (used to create the payment) and token (used to create the 3DS session).

Tokenize the card
const card = await publicsquare.cards.create({
cardholder_name: "John Smith",
card: cardElement,
});

Create a Payment Intent​

From your backend, call Create Payment Intent with the tokenized card. Return the intent id to the browser.

Create a Payment Intent
curl 'https://api.publicsquare.com/payment-intents' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"amount": 5000,
"currency": "USD",
"payment_method": { "card": "card_AjkCFKAYiTsjghXWMzoXFPMxj" },
"customer": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com"
},
"billing_details": {
"address_line_1": "111 Colorado Ave",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
}
}'

Create a 3DS Session​

In the browser, create the 3DS session from the card token and the Payment Intent id. The SDK collects the device fingerprint, registers the session with Credova, and returns:

  • id: the Credova session id. Pass this to confirm and complete.
  • bt_session_id: the session id used to render the challenge iframe in Render the Challenge.
Create a 3DS session
const session = await publicsquare.threeDs.createSession({
token_id: card.token,
payment_intent_id: intent.id,
});

Confirm the Payment Intent​

From your backend, call Confirm Payment Intent with transport: "iframe" and the session id from Create a 3DS Session.

Confirm with 3DS (iframe)
curl 'https://api.publicsquare.com/payment-intents/pi_2YKewBonG4tgk12MheY3PiHDy/confirm' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"three_d_secure": {
"transport": "iframe",
"session_id": "<PUBLICSQUARE_SESSION_ID>"
}
}'

The response is a PaymentIntent. There are two outcomes:

Frictionless — authentication succeeded with no challenge. The intent is succeeded and you are done.

Frictionless response
{
"id": "pi_2YKewBonG4tgk12MheY3PiHDy",
"status": "succeeded",
"payment_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
...
}

Challenge required — the intent is requires_action and next_action.three_d_secure carries the fields needed to render the challenge.

Challenge required response
{
"id": "pi_2YKewBonG4tgk12MheY3PiHDy",
"status": "requires_action",
"next_action": {
"type": "three_d_secure",
"three_d_secure": {
"session_id": "<PUBLICSQUARE_SESSION_ID>",
"transport": "iframe",
"acs_challenge_url": "https://...",
"acs_transaction_id": "f3e8a1b2-...",
"three_ds_version": "2.2.0"
}
},
...
}

Return next_action.three_d_secure to the browser to render the challenge.

Render the Challenge​

When the intent is requires_action, present the ACS challenge using the values from next_action.three_d_secure.

Use the session's bt_session_id (from Create a 3DS Session) as the challenge sessionId, not the Credova session id. Keep the Credova session_id for the complete call in Complete the Payment Intent.

Mount the ThreeDSChallenge element. It must be rendered inside your PublicSquareProvider. onComplete fires once the cardholder finishes the challenge (success, failure, or cancellation alike) — the authentication outcome is resolved server-side in Complete the Payment Intent.

Render the challenge
import { ThreeDSChallenge } from "@publicsquare/elements-react";

<ThreeDSChallenge
sessionId={session.bt_session_id}
acsChallengeUrl={nextAction.acs_challenge_url}
acsTransactionId={nextAction.acs_transaction_id}
threeDsVersion={nextAction.three_ds_version}
onComplete={(result) => completePayment(nextAction.session_id)}
onFailure={(err) => showError(err)}
/>
Your checkout page's Content Security Policy must allow the challenge iframe. Add frame-src https://*.basistheory.com to your CSP.

Complete the Payment Intent​

After the challenge resolves, call Complete Payment Intent 3DS Challenge from your backend with the Credova session_id (from next_action.three_d_secure.session_id). Credova retrieves the authentication result and submits the payment to the processor.

Complete the 3DS challenge
curl 'https://api.publicsquare.com/payment-intents/pi_2YKewBonG4tgk12MheY3PiHDy/three_d_secure/complete' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"three_d_secure": {
"session_id": "<PUBLICSQUARE_SESSION_ID>"
}
}'

The response is the final PaymentIntent, succeeded when authentication and authorization both pass, or failed with a last_payment_error otherwise.

Completed response
{
"id": "pi_2YKewBonG4tgk12MheY3PiHDy",
"status": "succeeded",
"payment_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
...
}

Testing​

Use the 3D Secure test cards to exercise frictionless, challenge, and failure scenarios in the test environment.

Conclusion​

You have authenticated a card payment with 3DS in-page. Next: