# MD for: https://www.mercadopago.com.pe/developers/es/docs/checkout-api-payments/how-tos/integrate-3ds.md \# How to integrate 3DS with Checkout API In this documentation you will find all the necessary information to carry out the integration with 3DS with Checkout API. For more information on how this type of authentication works, see \[3DS 2.0\](https://www.mercadopago.com.pe/developers/en/docs/checkout-api-payments/how-tos/improve-payment-approval/3ds). > WARNING > > Important > > To integrate with 3DS, certain requirements must be met. Before moving on to the next steps, review the \[Prerequisites\](https://www.mercadopago.com.pe/developers/en/docs/checkout-api-payments/prerequisites) section and make sure that all are met. Also, keep in mind that to test this integration, you will need to use \*\*test credentials\*\*, as indicated in the \[Integration test\](https://www.mercadopago.com.pe/developers/en/docs/checkout-api-payments/how-tos/integrate-3ds#bookmark\_integration\_test) section. ## Integrate with 3DS 3DS authentication can be done through two different flows: \*\*with or without Challenge\*\*, which are additional steps that the buyer must complete to ensure their identity. The decision to include or exclude the Challenge depends on the card issuer and the risk profile of the transaction being performed. > Also learn about the integrations via \[Checkout Bricks,\](https://www.mercadopago.com.pe/developers/en/docs/checkout-bricks/how-tos/integrate-3ds) a modular, secure and customizable payment method that automates several of the processes described below. For \*\*low-risk transactions\*\*, the information sent at checkout is sufficient and the additional Challenge steps are not necessary. However, \*\*for cases of high fraud risk\*\*, the Challenge is necessary to \*\*verify the buyer's identity\*\*, which increases card transaction conversion. Below are the steps to integrate with 3DS. 1\. Use the Mercado Pago \[SDK JS\](https://www.mercadopago.com.br/developers/en/docs/sdks-library/client-side/mp-js-v2) at checkout to generate the \[credit card token\](https://www.mercadopago.com.pe/developers/en/docs/checkout-api-payments/integration-configuration/card/web-integration). 2\. Next, send the \*\*checkout data\*\* along with the \*\*card token\*\* to the backend. 3\. After that, make a request to create a new payment with the received data. The \`three\_d\_secure\_mode\` attribute needs to be sent with one of the following values: 1\. \`not\_supported\`: 3DS must not be used (this is the default value). 2\. \`optional\`: 3DS may or may not be required, depending on the risk profile of the transaction. > WARNING > > Important > > We recommend using the \`optional\` value in the implementation of 3DS, as it balances security and transaction approval. > \> The payment capture must be automatic (\`capture=true\`), and the transaction should be created with binary mode deactivated (\`binary mode=false\`), as it might remain pending while waiting for the buyer to complete the Challenge. * [csharp ](#editor%5F3) * [curl ](#editor%5F8) * [go ](#editor%5F7) * [java ](#editor%5F2) * [node ](#editor%5F4) * [php ](#editor%5F1) * [python ](#editor%5F6) * [ruby ](#editor%5F5) php java csharp node ruby python go curl ``` setCustomHeaders(["X-Idempotency-Key: "]); $payment = $client->create([ "transaction_amount" => , "token" => "CARD_TOKEN", "description" => "", "installments" => , "payment_method_id" => "", "issuer_id" => "", "payer" => [ "email" => $_POST['email'] ], "three_d_secure_mode" => "optional" ], $request_options); echo implode($payment); ?> ``` Copiar ``` MercadoPagoConfig.setAccessToken(""); PaymentClient client = new PaymentClient(); PaymentCreateRequest createRequest = PaymentCreateRequest.builder() .transactionAmount(new BigDecimal()) .token("") .description("") .installments() .paymentMethodId("") .payer( PaymentPayerRequest.builder() .email("") .build() ) .threeDSecureMode("optional") .build(); client.create(createRequest); ``` Copiar ``` using MercadoPago.Config; using MercadoPago.Client.Payment; using MercadoPago.Resource.Payment; MercadoPagoConfig.AccessToken = ""; var request = new PaymentCreateRequest { TransactionAmount = , Token = "", Description = "", Installments = , Payer = new PaymentPayerRequest { Email = "", }, ThreeDSecureMode = "optional", }; var client = new PaymentClient(); Payment payment = await client.CreateAsync(request); ``` Copiar ``` import { MercadoPagoConfig, Payment } from 'mercadopago'; const client = new MercadoPagoConfig({ accessToken: '' }); const payment = new Payment(client); const body = { transaction_amount: , token: '', description: '', installments: , payment_method_id: '', issuer_id: '', payer: { email: '', }, three_d_secure_mode: 'optional' } payment.create({ body: body, requestOptions: { idempotencyKey: '' } }).then(console.log).catch(console.log); ``` Copiar ``` require 'mercadopago' sdk = Mercadopago::SDK.new('') payment_request = { token: '', installments: , transaction_amount: , description: '', payer: { email: '', }, three_d_secure_mode: 'optional' } payment_response = sdk.payment.create(payment_request) payment = payment_response[:response] ``` Copiar ``` import mercadopago sdk = mercadopago.SDK("") payment_data = { "transaction_amount": , "token": "", "description": "", "installments": , "payer": { "email": "", }, "three_d_secure_mode": "optional" } payment_response = sdk.payment().create(payment_data) payment = payment_response["response"] ``` Copiar ``` package main import ( "context" "fmt" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/payment" ) func main() { accessToken := "" cfg, err := config.New(accessToken) if err != nil { fmt.Println(err) return } client := payment.NewClient(cfg) request := payment.Request{ TransactionAmount:, Payer: &payment.PayerRequest{ Email: "", }, Token: "", Installments: , Description: "", ThreeDSecureMode: "optional", } resource, err := client.Create(context.Background(), request) if err != nil { fmt.Println(err) return } fmt.Println(resource) } ``` Copiar ``` curl --location --request POST 'https://api.mercadopago.com/v1/payments' \ --header 'Authorization: ' \ --header 'Content-Type: application/json' \ --data-raw '{ "payer": { "email": "" }, "additional_info": { "items": [ { "quantity": , "category_id": , "title": , "unit_price": } ] }, "payment_method_id": , "marketplace": "NONE", "installments": , "transaction_amount": , "description": "", "token": "CARD_TOKEN", "three_d_secure_mode": "optional", "capture": true, "binary_mode": false }' ``` Copiar If the Challenge flow is not required, the payment \`status\` field will have a value of \`approved\` and it will not be necessary to display it, so it is possible to proceed with the application flow. For cases where the Challenge is necessary, the status will show the value \`pending\`, and the \`status\_detail\` will be \`pending\_challenge\`. > WARNING > > Important > > In the latter case, the response will show a payment attribute called \`three\_ds\_info\` with the fields \`external\_resource\_url\`, which contains the Challenge URL, and \`creq\`, a Challenge request identifier. It will be necessary to display the Challenge and treat its result with the following steps. ### Response overview (information omitted) When the Challenge is initiated, the user has about 5 minutes to complete it. If it is not completed, the bank will decline the transaction and Mercado Pago will consider the payment canceled. While the user doesn't complete the Challenge, the payment will remain as \`pending\_challenge\`. * [json ](#editor%5F9) json ``` { "id": 52044997115, ... "status": "pending", "status_detail": "pending_challenge", ... "three_ds_info": { "external_resource_url": "https://acs-public.tp.mastercard.com/api/v1/browser_Challenges", "creq": "eyJ0aHJlZURTU2VydmVyVHJhbnNJRCI6ImJmYTVhZjI0LTliMzAtNGY1Yi05MzQwLWJkZTc1ZjExMGM1MCIsImFjc1RyYW5zSUQiOiI3MDAwYTI2YS1jYWQ1LTQ2NjQtOTM0OC01YmRlZjUwM2JlOWYiLCJjaGFsbGVuZ2VXaW5kb3dTaXplIjoiMDQiLCJtZXNzYWdlVHlwZSI6IkNSZXEiLCJtZXNzYWdlVmVyc2lvbiI6IjIuMS4wIn0" }, "owner": null } ``` Copiar 4\. For a better view of the 3DS Challenge in a responsive way, you should add the CSS below. \`\`\`css #myframe{ width: 500px; height: 600px; border: none; } @media only screen and (width <= 980px) { #myframe{ width: 100%; height: 440px; } } \`\`\` 5\. To \*\*display the Challenge\*\*, you need to generate an iframe containing a form with \`method post\`, \`action\` containing the URL obtained in the field \`external\_resource\_url\`, and a hidden input with the value returned in \`creq\`. Then, you must post the form below to start the Challenge. * [javascript ](#editor%5F10) javascript ``` function doChallenge(payment) { try { const { status, status_detail, three_ds_info: { creq, external_resource_url }, } = payment; if (status === "pending" && status_detail === "pending_challenge") { var iframe = document.createElement("iframe"); iframe.name = "myframe"; iframe.id = "myframe"; document.body.appendChild(iframe); var idocument = iframe.contentWindow.document; var myform = idocument.createElement("form"); myform.name = "myform"; myform.setAttribute("target", "myframe"); myform.setAttribute("method", "post"); myform.setAttribute("action", external_resource_url); var hiddenField = idocument.createElement("input"); hiddenField.setAttribute("type", "hidden"); hiddenField.setAttribute("name", "creq"); hiddenField.setAttribute("value", creq); myform.appendChild(hiddenField); iframe.appendChild(myform); myform.submit(); } } catch (error) { console.log(error); alert("Error doing challenge, try again later."); } } ``` Copiar When the Challenge is completed, the payment status will be updated to \`approved\` if the authentication is successful, and \`rejected\` if it is not. In situations where authentication is not performed, the payment remains \`pending\`. This update is not immediate and may take a few moments. See the section below for more details on how to check the status of each transaction. ## Check the status of the transaction To find out the result of each transaction, there are three options: \* \*\*Notifications\*\*: A notification of the payment status change will be received through Webhooks and the buyer must be redirected to a screen indicating that the transaction was successful. Check the \[Webhooks\](https://www.mercadopago.com.pe/developers/en/docs/checkout-api-payments/additional-content/your-integrations/notifications/webhooks) section and learn how to set it up. \* \*\*Payments API\*\*: It will be necessary to make a \[Payments\](https://www.mercadopago.com.pe/developers/en/reference/online-payments/checkout-api-payments/create-payment/post) pooling and if the status changes, redirect the buyer to a confirmation screen. \* \*\*Treat the iframe event (recommended)\*\*: Keep in mind that the event only indicates that the Challenge has ended and not that the payment has reached a final status, as the update is not immediate and may take a few moments. Make a request to \[Payments\](https://www.mercadopago.com.pe/developers/en/reference/online-payments/checkout-api-payments/create-payment/post) and if the status changes, redirect the buyer to a screen indicating that the transaction was successful. To \*\*treat the iframe event\*\*, follow the steps below. ### Perform implementation Use the following JavaScript code to implement and request the event that indicates that the Challenge has ended, so it is possible to redirect the client to the confirmation screen. * [javascript ](#editor%5F11) javascript ``` window.addEventListener("message", (e) => { if (e.data.status === "COMPLETE") { window.open("congrats.html"); } }); ``` Copiar \### Search payment status The following Javascript indicates how to search for the updated payment status and display it on the confirmation screen. * [javascript ](#editor%5F12) javascript ``` document.addEventListener("DOMContentLoaded", async function (e) { heat(); }); async function init() { const id = localStorage.getItem("paymentId"); try { const response = await fetch("/get_payment/" + id, { method: "GET", }); const result = await response.json(); if (result.status != 200) throw new Error("error getting payment"); document.getElementById("congrats-div").innerHTML = "Pagamento " + result.data.id + " -> Status: " + result.data.status; } catch (error) { alert("Unexpected error\n" + JSON.stringify(error)); } } ``` Copiar \> WARNING > > Important > > If the payment is still \`pending\` after the Challenge timeout, it will be necessary to redirect the buyer to a screen informing that the payment has expired and that a new one needs to be created (the update is not immediate, it may take some moments). After following these steps, your integration is ready to authenticate transactions with 3DS. ## Possible payment statuses A transaction with 3DS can return different statuses depending on the type of authentication performed (with or without Challenge). In a payment \*\*without Challenge\*\*, the transaction status will be directly \`approved\` or \`rejected\`. In a payment \*\*with Challenge\*\*, the transaction will have a \`pending\` status and the authentication process with the bank will be initiated. Only after this step, the final status will be displayed. See below the table with the possible statuses and their respective descriptions. | Status | Status\_detail | Description | |------------|-------------------------------|------------------------------------------------------------------| | "approved" | "accredited" | Transaction approved without authentication. | | "rejected" | - | Transaction rejected without authentication. To check the reasons, please refer to the standard \[list of status details\](https://mercadopago.com.br/developers/en/docs/checkout-api-payments/response-handling/collection-results). | | "pending" | "pending\_challenge" | Transaction pending authentication or Challenge timeout. | | "rejected" | "cc\_rejected\_3ds\_challenge" | Transaction rejected due to Challenge failure. | | "canceled" | "expired" | Transaction with Challenge canceled after 24 hours in pending status. | ## Integration test To facilitate the validation of 3DS payments, we have created a sandbox testing environment. This environment returns fictional results that are only used for simulating and validating the implementation. > WARNING > > Important > > To test the 3DS integration, it is \*\*necessary to use the sandbox environment\*\* with your respective :toolTipComponent\[test credentials\]{link="/developers/en/docs/checkout-api-legacy/additional-content/your-integrations/credentials" linkText="Credentials" content="Unique access keys linked to your application that allow you to perform transactions in a test environment. For more information, see the link below."}. The 3DS flow \*\*cannot be tested in the production environment\*\*, even using test users. Also, make sure to include the \`three\_d\_secure\_mode\` attribute, setting it as \`optional\`, to ensure the correct implementation of the 3DS payment. To test payments in a sandbox environment, specific cards should be used to test the implementation of the Challenge with both success and failure flows, as shown in the table below: | Card | Flow | Number | Security Code | Expiration Date | |-------------|-------------------------|---------------------|----------------|-----------------| | Mastercard | Successful Challenge | 5483 9281 6457 4623 | 123 | 11/30 | | Mastercard | Unauthorized Challenge | 5361 9568 0611 7557 | 123 | 11/30 | The steps to create the payment remain the same. If you have any doubts about how to create card payments, please refer to the \[documentation on Cards\](https://www.mercadopago.com.br/developers/en/docs/checkout-api-payments/integration-configuration/card/web-integration). * [csharp ](#editor%5F17) * [curl ](#editor%5F20) * [go ](#editor%5F19) * [java ](#editor%5F15) * [node ](#editor%5F14) * [php ](#editor%5F13) * [python ](#editor%5F18) * [ruby ](#editor%5F16) php node java ruby csharp python go curl ``` setCustomHeaders(["X-Idempotency-Key: "]); $payment = $client->create([ "transaction_amount" => (float) $_POST['transactionAmount'], "token" => $_POST['token'], "description" => $_POST['description'], "installments" => $_POST['installments'], "payment_method_id" => $_POST['paymentMethodId'], "issuer_id" => $_POST['issuer'], "payer" => [ "email" => $_POST['email'], "identification" => [ "type" => $_POST['identificationType'], "number" => $_POST['number'] ] ], "three_d_secure_mode" => "optional" ], $request_options); echo implode($payment); ?> ``` Copiar ``` import { MercadoPagoConfig, Payment } from 'mercadopago'; const client = new MercadoPagoConfig({ accessToken: 'YOUR_ACCESS_TOKEN' }); const payment = new Payment(client); const body = { transaction_amount: req.transaction_amount, token: req.token, description: req.description, installments: req.installments, payment_method_id: req.paymentMethodId, issuer_id: req.issuer, payer: { email: req.email, identification: { type: req.identificationType, number: req.number } }, three_d_secure_mode: 'optional' }; payment.create({ body: body, requestOptions: { idempotencyKey: '' } }).then(console.log).catch(console.log); ``` Copiar ``` PaymentClient client = new PaymentClient(); PaymentCreateRequest paymentCreateRequest = PaymentCreateRequest.builder() .transactionAmount(request.getTransactionAmount()) .token(request.getToken()) .description(request.getDescription()) .installments(request.getInstallments()) .paymentMethodId(request.getPaymentMethodId()) .payer( PaymentPayerRequest.builder() .email(request.getPayer().getEmail()) .firstName(request.getPayer().getFirstName()) .identification( IdentificationRequest.builder() .type(request.getPayer().getIdentification().getType()) .number(request.getPayer().getIdentification().getNumber()) .build()) .build()) .threeDSecureMode("optional") .build(); client.create(paymentCreateRequest); ``` Copiar ``` require 'mercadopago' sdk = Mercadopago::SDK.new('YOUR_ACCESS_TOKEN') payment_data = { transaction_amount: params[:transactionAmount].to_f, token: params[:token], description: params[:description], installments: params[:installments].to_i, payment_method_id: params[:paymentMethodId], payer: { email: params[:email], identification: { type: params[:identificationType], number: params[:identificationNumber] } three_d_secure_mode: "optional", } } payment_response = sdk.payment.create(payment_data) payment = payment_response[:response] puts payment ``` Copiar ``` using System; using MercadoPago.Client.Common; using MercadoPago.Client.Payment; using MercadoPago.Config; using MercadoPago.Resource.Payment; MercadoPagoConfig.AccessToken = "YOUR_ACCESS_TOKEN"; var paymentRequest = new PaymentCreateRequest { TransactionAmount = decimal.Parse(Request["transactionAmount"]), Token = Request["token"], Description = Request["description"], Installments = int.Parse(Request["installments"]), PaymentMethodId = Request["paymentMethodId"], Payer = new PaymentPayerRequest { Email = Request["email"], Identification = new IdentificationRequest { Type = Request["identificationType"], Number = Request["identificationNumber"], }, }, ThreeDSecureMode = "optional", }; var client = new PaymentClient(); Payment payment = await client.CreateAsync(paymentRequest); Console.WriteLine(payment.Status); ``` Copiar ``` import mercadopago sdk = mercadopago.SDK("ACCESS_TOKEN") payment_data = { "transaction_amount": float(request.POST.get("transaction_amount")), "token": request.POST.get("token"), "description": request.POST.get("description"), "installments": int(request.POST.get("installments")), "payment_method_id": request.POST.get("payment_method_id"), "payer": { "email": request.POST.get("email"), "identification": { "type": request.POST.get("type"), "number": request.POST.get("number") } } "three_d_secure_mode": "optional" } payment_response = sdk.payment().create(payment_data) payment = payment_response["response"] print(payment) ``` Copiar ``` package main import ( "context" "fmt" "github.com/mercadopago/sdk-go/pkg/config" "github.com/mercadopago/sdk-go/pkg/payment" ) func processPayment(r *http.Request) { accessToken := "{{ACCESS_TOKEN}}" cfg, err := config.New(accessToken) if err != nil { fmt.Println(err) return } client := payment.NewClient(cfg) request := payment.Request{ TransactionAmount: r.FormValue("transactionAmount"), Token: r.FormValue("token"), Description: r.FormValue("description"), PaymentMethodID: r.FormValue("paymentMethodId"), Payer: &payment.PayerRequest{ Email: r.FormValue("email"), Identification: &payment.IdentificationRequest{ Type: r.FormValue("type"), Number: r.FormValue("number"), }, }, } resource, err := client.Create(context.Background(), request) if err != nil { fmt.Println(err) } fmt.Println(resource) } ``` Copiar ``` curl --location --request POST 'https://api.mercadopago.com/v1/payments' \ --header 'Authorization: ' \ --header 'Content-Type: application/json' \ --data-raw '{ "payer": { "email": "" }, "additional_info": { "items": [ { "quantity": , "category_id": , "title": , "unit_price": } ] }, "payment_method_id": , "marketplace": "NONE", "installments": , "transaction_amount": , "description": "", "token": "CARD_TOKEN", "three_d_secure_mode": "optional", "capture": true, "binary_mode": false }' ``` Copiar \### Challenge In both the success and failure flows, the Challenge, which is a screen similar to the one shown below, should be displayed within the iframe: !\[Challenge\](https://www.mercadopago.com.pe/images/api/sandbox-v1-en.png) The provided verification code is for illustrative purposes only. To complete the test flow, simply click the \*\*Confirm\*\* button. After completing this action, follow the detailed instructions in the \[Check the status of the transaction\](https://www.mercadopago.com.pe/developers/en/docs/checkout-api-payments/how-tos/integrate-3ds#bookmark\_check\_transaction\_status) section to determine when the Challenge has been completed and how to check for payment updates.