CĂłmo integrar 3DS con Checkout Transparente
En esta documentaciĂłn encontrarĂĄs toda la informaciĂłn necesaria para realizar la integraciĂłn con 3DS con Checkout Transparente. Para obtener mĂĄs informaciĂłn sobre cĂłmo funciona este tipo de autenticaciĂłn, consulte 3DS 2.0.
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.
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.
- Debes usar el SDK JS de Mercado Pago en el checkout para generar el token de la tarjeta de crédito.
- Después, postea los datos del checkout junto con el token de la tarjeta para su backend.
- 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:not_supported
: no se debe usar 3DS (es el valor por default).optional
: se puede requerir 3DS o no, dependiendo del perfil de riesgo de la transacciĂłn.
<?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
.
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
}
- 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;
}
}
- 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 campoexternal_resource_url
, y un input oculto con el valor obtenido encreq
. 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));
}
}
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.
Status | Status_detail | DescripciĂł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. |
"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.
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:
Tarjeta | Flujo | NĂșmero | CĂłdigo de Seguridad | Fecha de Vencimiento |
Mastercard | Challenge exitoso | 5483 9281 6457 4623 | 123 | 11/25 |
Mastercard | Challenge no autorizado | 5361 9568 0611 7557 | 123 | 11/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:
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.