Cómo integrar 3DS con Checkout API - Checkout API - Mercado Pago Developers
¿Qué estás buscando?

¿No sabes cómo empezar la integración? 

Accede a los primeros pasos

Cómo integrar 3DS con Checkout API

En esta documentación encontrarás toda la información necesaria para realizar la integración con 3DS con Checkout API . Para obtener más información sobre cómo funciona este tipo de autenticación, consulte 3DS 2.0.

Importante
Para integrarse con 3DS, se deben cumplir ciertos requisitos. Antes de continuar con los siguientes pasos, revise la sección Requisitos previos y asegúrese de que se cumplan todos.

Integrar con 3DS

La autenticación 3DS se puede realizar a través de dos flujos distintos: con o sin Challenge, que son pasos adicionales que el comprador debe completar para garantizar su identidad. La decisión de incluir o no el Challenge depende del emisor de la tarjeta y del perfil de riesgo de la transacción que se realiza.

Obtenga también información sobre las integraciones a través de Checkout Bricks, un método de pago modular, seguro y personalizable que automatiza varios de los procesos que se describen a continuación.

Para transacciones de bajo riesgo, la información enviada en el momento del pago es suficiente y los pasos adicionales de Challenge no son necesarios. Sin embargo, para casos donde existe un alto riesgo de fraude, Challenge es requerido para verificar la identidad del comprador, lo que aumenta la conversión de las transacciones con tarjeta.

A continuación se presentan los pasos para realizar una integración con 3DS.

  1. Debes usar el SDK JS de Mercado Pago en el checkout para generar el token de la tarjeta de crédito.
  2. Después, postea los datos del checkout junto con el token de la tarjeta para su backend.
  3. Allí, haz una llamada para crear un nuevo pago con los datos recibidos. Es necesario que sea enviado el atributo three_d_secure_mode con uno de los siguientes valores:
    1. not_supported: no se debe usar 3DS (es el valor por default).
    2. optional: se puede requerir 3DS o no, dependiendo del perfil de riesgo de la transacción.
    3. mandatory: 3DS será requerido obligatoriamente.
Importante
Recomendamos usar el valor optional en la implementación del 3DS, ya que equilibra la seguridad y la aprobación de transacciones. El uso de mandatory debe limitarse a integraciones que requieran que todas las transacciones aprobadas pasen por 3DS.
          
<?php
  use MercadoPago\Client\Payment\PaymentClient;
  MercadoPagoConfig::setAccessToken("YOUR_ACCESS_TOKEN");

  $client = new PaymentClient();
  $request_options = new RequestOptions();
  $request_options->setCustomHeaders(["X-Idempotency-Key: <SOME_UNIQUE_VALUE>"]);

  $payment = $client->create([
    "transaction_amount" => <TRANSACTION_AMOUNT>,
    "token" => "CARD_TOKEN",
    "description" => "<DESCRIPTION>",
    "installments" => <INSTALLMENTS_NUMBER>,
    "payment_method_id" => "<PAYMENT_METHOD_ID>",
    "issuer_id" => "<ISSUER_ID>",
    "payer" => [
      "email" => $_POST['email']
    ],
    "three_d_secure_mode" => "optional"
  ], $request_options);
  echo implode($payment);
?>

        
          
MercadoPagoConfig.setAccessToken("<ENV_ACCESS_TOKEN>");
    PaymentClient client = new PaymentClient();
    PaymentCreateRequest createRequest =
        PaymentCreateRequest.builder()
            .transactionAmount(new BigDecimal(<TRANSACTION_AMOUNT>))
            .token("<CARD_TOKEN>")
            .description("<DESCRIPTION>")
            .installments(<INSTALLLMENTS_NUMBER>)
            .paymentMethodId("<PAYMENT_METHOD_ID>")
            .payer(
               PaymentPayerRequest.builder()
                 .email("<BUYER_EMAIL>")
                 .build()
            )
            .threeDSecureMode("optional")
            .build();
    client.create(createRequest);

        
          
using MercadoPago.Config;
using MercadoPago.Client.Payment;
using MercadoPago.Resource.Payment;
MercadoPagoConfig.AccessToken = "<ENV_ACCESS_TOKEN>";
var request = new PaymentCreateRequest
{
    TransactionAmount = <TRANSACTION_AMOUNT>,
    Token = "<CARD_TOKEN>",
    Description = "<DESCRIPTION>",
    Installments = <INSTALLLMENTS_NUMBER>,
    Payer = new PaymentPayerRequest
    {
        Email = "<BUYER_EMAIL>",
    },
    ThreeDSecureMode = "optional",
};
var client = new PaymentClient();
Payment payment = await client.CreateAsync(request);

        
          
import { MercadoPagoConfig, Payment } from 'mercadopago';

const client = new MercadoPagoConfig({ accessToken: '<ENV_ACCESS_TOKEN>' });
const payment = new Payment(client);

const body = {
  transaction_amount: <TRANSACTION_AMOUNT>,
  token: '<CARD_TOKEN>',
  description:  '<DESCRIPTION>',
  installments: <INSTALLMENTS_NUMBER>,
  payment_method_id: '<PAYMENT_METHOD_ID>',
  issuer_id: '<ISSUER_ID>',
  payer: {
    email: '<BUYER_EMAIL>',
  },
  three_d_secure_mode: 'optional'
}
payment.create({ body: body, requestOptions: { idempotencyKey: '<SOME_UNIQUE_VALUE>' } }).then(console.log).catch(console.log);

        
          
require 'mercadopago'
sdk = Mercadopago::SDK.new('<ENV_ACCESS_TOKEN>')
payment_request = {
  token: '<CARD_TOKEN>',
  installments: <INSTALLLMENTS_NUMBER>,
  transaction_amount: <TRANSACTION_AMOUNT>,
  description: '<DESCRIPTION>',
  payer: {
    email: '<BUYER_EMAIL>',
  },
  three_d_secure_mode: 'optional'
}
payment_response = sdk.payment.create(payment_request)
payment = payment_response[:response]

        
          
import mercadopago
sdk = mercadopago.SDK("<ENV_ACCESS_TOKEN>")
payment_data = {
    "transaction_amount": <TRANSACTION_AMOUNT>,
    "token": "<CARD_TOKEN>",
    "description": "<DESCRIPTION>",
    "installments": <INSTALLLMENTS_NUMBER>,
    "payer": {
        "email": "<BUYER_EMAIL>",
    },
    "three_d_secure_mode": "optional"
}
payment_response = sdk.payment().create(payment_data)
payment = payment_response["response"]

        
          
package main

import (
	"context"
	"fmt"

	"github.com/mercadopago/sdk-go/pkg/config"
	"github.com/mercadopago/sdk-go/pkg/payment"
)

func main() {
	accessToken := "<ENV_ACCESS_TOKEN>"

	cfg, err := config.New(accessToken)
	if err != nil {
		fmt.Println(err)
		return
	}

	client := payment.NewClient(cfg)

	request := payment.Request{
		TransactionAmount:<TRANSACTION_AMOUNT>,
		Payer: &payment.PayerRequest{
			Email: "<BUYER_EMAIL>",
		},
		Token:        "<CARD_TOKEN>",
		Installments: <INSTALLLMENTS_NUMBER>,
		Description: "<DESCRIPTION>",
		ThreeDSecureMode: "optional",
	}

	resource, err := client.Create(context.Background(), request)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(resource)
}

        
          
curl --location --request POST 'https://api.mercadopago.com/v1/payments' \
--header 'Authorization: <ENV_ACCESS_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "payer": {
        "email": "<BUYER_EMAIL>"
    },
    "additional_info": {
        "items": [
            {
                "quantity": <ITEM_QUANTITY>,
                "category_id": <CATEGORY_ID>,
                "title": <ITEM_TITLE>,
                "unit_price": <TRANSACTION_AMOUNT>
            }
        ]
    },
    "payment_method_id": <PAYMENT_METHOD_ID>,
    "marketplace": "NONE",
    "installments": <INSTALLLMENTS_NUMBER>,
    "transaction_amount": <TRANSACTION_AMOUNT>,
    "description": "<DESCRIPTION>",
    "token": "CARD_TOKEN",
    "three_d_secure_mode": "optional",
    "capture": true,
    "binary_mode": false
}'

        

En caso de que no sea necesario utilizar el flujo de Challenge, el campo del status del pago tendrá valor approved y no será necesario mostrarlo, por lo que puedes seguir con el flujo de tu aplicación.

Para casos en que el Challenge es necesario, el status mostrará el valor pending, y el status_detail será pending_challenge.

Importante
En este último caso, la respuesta mostrará un atributo del pago llamado three_ds_info con los campos external_resource_url, que contiene la URL del Challenge, y creq, un identificador del Challenge request. Será necesario mostrar el Challenge y tratar su resultado con los pasos siguientes.

Overview del response (se omitió información)

Cuando se inicia el Challenge, el usuario tiene aproximadamente 5 minutos para completarlo. Si no se completa, el banco rechazará la transacción y Mercado Pago considerará el pago cancelado. Mientras el usuario no complete el Challenge, el estado del pago permanecerá como pending_challenge.

          
{
    "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
}


        
  1. Para una mejor visualización del Challenge del 3DS de forma responsiva, debes agregar el CSS que se muestra a continuación.

css

  #myframe{
    width: 500px;
    height: 600px;
    border: none;
  }
  @media only screen and (width <= 980px) {
    #myframe{
      width: 100%;
      height: 440px;
    }
  }
  1. Para mostrar el Challenge, es necesario que generes un iframe que contenga un formulario con method post, action que contenga la URL obtenida en el campo external_resource_url, y un input oculto con el valor obtenido en creq. Después, debes hacer el post del form a continuación para empezar el challenge.
          
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.");
  }
}


        

Cuando el Challenge es finalizado, el status del pago será actualizado. Será approved si la autenticación fue exitosa, rejected si no lo fue y, en caso de que la autenticación no fuera hecha, el pago permanecerá pending. Esta actualización no es inmediata, puede tardar unos instantes.

Mira la sección a continuación para más detalles sobre cómo consultar el status de cada transacción.

Consultar status de la transacción

Para saber cuál es el resultado de la transacción, hay tres opciones:

  • Notificaciones: Recibirás la notificación del cambio del status del pago usando Webhooks y deberás redireccionar el buyer para una pantalla que indica que la transacción fue exitosa. Consulta la sección de Webhooks y aprende cómo configurarlos.
  • API de Payments: Deberás hacer un pooling en Payments y, si el status cambia, redireccionar el buyer para una pantalla de confirmación.
  • Tratar el evento del iframe (recomendado): debes recordar que el evento solo indica que finalizó el Challenge y no que el pago pasó a un status final, dado que la actualización no es inmediata y puede tardar unos instantes. Deberás hacer una consulta en Payments y, si el status cambia, redireccionar al buyer para una pantalla que indica que la transacción fue exitosa.

Para tratar el evento del iframe, sigue los pasos a continuación.

Realizar implementación

Utilice el código JavaScript a continuación para implementar y escuchar el evento que indica que el Challenge ha finalizado, de esta manera es posible redirigir al cliente a la pantalla de confirmación.

          
window.addEventListener("message", (e) => {
     if (e.data.status === "COMPLETE") {
         window.open("congrats.html");
     }
});


        

Buscar status del pago

El siguiente Javascript indica cómo se puede realizar la búsqueda del status de pago actualizado y mostrarlo en la pantalla de confirmación.

          
document.addEventListener("DOMContentLoaded", async function (e) {
 init();
});

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));
 }
}


        
Importante
Si el pago continúa pending después del timeout del Challenge, entonces deberás redireccionar al buyer para una pantalla que informe que el pago expiró y que es necesario crear uno nuevo (la actualización no es inmediata, puede tardar unos instantes).

Después de seguir estos pasos, tu integración está lista para autenticar transacciones con 3DS.

Posibles status del pago

Una transacción con 3DS puede devolver diferentes status según el tipo de autenticación realizada (con o sin Challenge). En un pago sin Challenge, el status de la transacción será directamente approved o rejected.

En un pago con Challenge, la transacción estará en status pending y se iniciará el proceso de autenticación con el banco. Solo después de esta etapa se mostrará el status final.

A continuación se muestra una tabla con los posibles status y sus descripciones correspondientes.

StatusStatus_detailDescripción
"approved""accredited"Transacción aprobada sin autenticación.
"rejected"-Transacción denegada sin autenticación. Para verificar los motivos, consulta la lista estándar de status detail.
"pending""pending_challenge"Transacción pendiente de autenticación o timeout del Challenge.
"rejected""cc_rejected_3ds_challenge"Transacción denegada debido a falla en el Challenge.
"rejected""cc_rejected_3ds_mandatory"Transacción rechazada por no cumplir validación de 3DS cuando esta es mandatory.
"cancelled""expired"Transacción con Challenge cancelada después de 24 horas en estado pendiente.

Prueba de integración

Para facilitar la validación de pagos con 3DS, hemos creado un entorno de pruebas tipo sandbox. Este entorno devuelve resultados ficticios que sólo se utilizan para simular y validar la implementación.

Importante
Para probar la integración, es necesario utilizar sus credenciales de prueba. Asegúrese también de incluir el atributo three_d_secure_mode, definiéndolo como optional o mandatory, para garantizar la correcta implementación del pago 3DS.

Para probar pagos en un entorno sandbox, se deben usar tarjetas específicas que permitan probar la implementación del desafío con flujos de éxito y fallo, según la tabla a continuación:

TarjetaFlujoNúmeroCódigo de SeguridadFecha de Vencimiento
MastercardChallenge exitoso5483 9281 6457 462312311/25
MastercardChallenge no autorizado5361 9568 0611 755712311/25
Mastercard3ds mandatory5031 7557 3453 060412311/25

Los pasos para crear el pago son los mismos. En caso de duda sobre cómo crear pagos con tarjeta, consulta la documentación sobre Tarjetas.

          
<?php
  use MercadoPago\Client\Payment\PaymentClient;
  use MercadoPago\MercadoPagoConfig;
  MercadoPagoConfig::setAccessToken("YOUR_ACCESS_TOKEN");

  $client = new PaymentClient();
  $request_options = new RequestOptions();
  $request_options->setCustomHeaders(["X-Idempotency-Key: <SOME_UNIQUE_VALUE>"]);

  $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);
?>

        
          
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: '<SOME_UNIQUE_VALUE>' } }).then(console.log).catch(console.log);

        
          
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);

        
          
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

        
          
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);

        
          
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)

        
          
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)
}

        
          
curl -X POST \
   -H 'accept: application/json' \
   -H 'content-type: application/json' \
   -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
   'https://api.mercadopago.com/v1/payments' \
   -d '{
         "transaction_amount": 100,
         "token": "CARD_TOKEN",
         "description": "Blue shirt",
         "installments": 1,
         "payment_method_id": "master",
         "issuer_id": 310,
         "payer": {
           "email": "PAYER_EMAIL"
         },
         "three_d_secure_mode": "optional"
   }'

        

Challenge

En ambos flujos (éxito y falla), el Challenge, que es una pantalla similar a la presentada a continuación, se mostrará dentro del iframe:

Challenge

El código de verificación proporcionado es meramente ilustrativo. Para completar el flujo de prueba, simplemente haz clic en el botón Confirmar. Una vez que hayas completado esta acción, sigue las instrucciones detalladas en la sección Consultar status de la transacción para determinar cuándo se ha finalizado el Challenge y cómo verificar la actualización del pago.