Como integrar 3DS com Checkout Transparente
Nesta documentação vocĂȘ encontrarĂĄ toda a informação necessĂĄria para realizar a integração com 3DS com Checkout Transparente. Para mais informaçÔes sobre como esse tipo de autenticação funciona, veja 3DS 2.0.
Integrar com 3DS
A autenticação 3DS pode ser feita através de dois fluxos diferentes: com e sem Challenge, sendo estas etapas adicionais que o comprador deve cumprir para garantir sua identidade. A decisão de incluir ou não o Challenge depende do emissor do cartão e do perfil de risco da transação que estå sendo realizada.
Para transaçÔes de baixo risco, as informaçÔes enviadas na finalização da compra são suficientes e as etapas adicionais do Challenge não são necessårias. Porém, para casos de alto risco de fraude, o Challenge é necessårio para verificar a identidade do comprador, o que aumenta a aprovação das transaçÔes com cartão.
Abaixo estão as etapas para realizar uma integração com 3DS.
- Utilize o Mercado Pago SDK JS no checkout para gerar o token do cartão de crédito.
- Em seguida, envie os dados do checkout junto com o token do cartĂŁo para o backend.
- Feito isso, faça uma chamada para criar um novo pagamento com os dados recebidos. O atributo
three_d_secure_mode
precisa ser enviado com um dos seguintes valores:not_supported
: 3DS nĂŁo deve ser usado (Ă© o valor padrĂŁo).optional
: 3DS pode ou não ser exigido, dependendo do perfil de risco da operação.
<?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
}'
Caso nĂŁo seja necessĂĄrio utilizar o fluxo do Challenge, o campo de status do pagamento terĂĄ valor approved
e não serå necessårio exibi-lo, dessa forma, siga normalmente com o fluxo de sua aplicação.
Para os casos em que o Challenge Ă© necessĂĄrio, o status mostrarĂĄ o valor pending
, e o status_detail
serĂĄ pending_challenge
.
Visão geral da resposta (informação omitida)
Quando o Challenge Ă© iniciado, o usuĂĄrio tem cerca de 5 minutos para completĂĄ-lo. Se nĂŁo for concluĂdo, o banco recusarĂĄ a transação e o Mercado Pago considerarĂĄ o pagamento cancelado. Enquanto o usuĂĄrio nĂŁo completar o Challenge, o pagamento ficarĂĄ 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 uma melhor visualização do Challenge do 3DS de forma responsiva, vocĂȘ deve adicionar o CSS abaixo.
css
#myframe{
width: 500px;
height: 600px;
border: none;
}
@media only screen and (width <= 980px) {
#myframe{
width: 100%;
height: 440px;
}
}
- Para exibir o Challenge, Ă© necessĂĄrio gerar um iframe que contenha um formulĂĄrio com
method post
,action
contendo a URL obtida no campoexternal_resource_url
, e um input oculto com o valor obtido emcreq
. Em seguida, faça o post do formulårio abaixo para iniciar o 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.");
}
}
Quando o Challenge for concluĂdo, o status do pagamento serĂĄ atualizado para approved
se a autenticação for bem-sucedida, e rejected
se não for. Em situaçÔes nas quais a autenticação não é realizada, o pagamento permanece pending
. Esta atualização não é imediata e pode levar alguns instantes.
Consulte a seção abaixo para obter mais detalhes sobre como verificar o status de cada transação.
Verificar status da transação
Para saber qual Ă© o resultado de cada transação, existem trĂȘs opçÔes:
- NotificaçÔes: Uma notificação da alteração do status do pagamento serå recebida por meio de Webhooks e o comprador deverå ser redirecionado para uma tela indicando que a transação foi bem-sucedida. Consulte a seção Webhooks e saiba como realizar sua configuração..
- API de pagamentos: Serå necessårio fazer um pooling em Payments e, se o status mudar, redirecionar o comprador para uma tela de confirmação.
- Tratar o evento iframe (recomendado): Tenha em mente que o evento apenas indica que o Challenge terminou e não que o pagamento chegou a um status final, pois a atualização não é imediata e pode demorar alguns instantes. Faça uma consulta em Payments e, caso o status mude, redirecione o comprador para uma tela indicando que a transação foi realizada com sucesso.
Para tratar o evento iframe, siga as etapas abaixo.
Realizar implantação
Utilize o cĂłdigo Javascript a seguir para implementar e escutar o evento que indica que o Challenge foi encerrado, assim Ă© possĂvel redirecionar o cliente para a tela de confirmação.
window.addEventListener("message", (e) => {
if (e.data.status === "COMPLETE") {
window.open("congrats.html");
}
});
Buscar status de pagamento
O Javascript a seguir indica como buscar o status do pagamento atualizado e exibi-lo na tela de confirmação.
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));
}
}
Após seguir estes passos, sua integração estå pronta para autenticar transaçÔes com 3DS.
PossĂveis status de pagamento
Uma transação com 3DS pode retornar diferentes status dependendo do tipo de autenticação realizada (com ou sem Challenge).
Em um pagamento sem Challenge, o status da transação serå diretamente approved
ou rejected
. Enquanto que em um pagamento com Challenge, a transação ficarå com status pending
e o processo de autenticação junto ao banco serå iniciado. Somente após esta etapa o status final serå exibido.
Veja abaixo a tabela com os possĂveis status e suas respectivas descriçÔes.
Status | Status_detail | Descrição |
"approved" | "accredited" | Transação aprovada sem autenticação. |
"rejected" | - | Transação rejeitada sem autenticação. Para conferir os motivos, consulte a lista padrão de status detail. |
"pending" | "pending_challenge" | Transação pendente de autenticação ou timeout do Challenge. |
"rejected" | "cc_rejected_3ds_challenge" | Transação rejeitada devido a falha no Challenge. |
"cancelled" | "expired" | Transação com Challenge cancelada após 24h no status pending . |
Teste de integração
Para que seja possĂvel validar pagamentos com 3DS, disponibilizamos um ambiente de testes do tipo sandbox que retorna resultados falsos apenas para simulação e validação da implementação.
Para realizar testes de pagamento em um ambiente sandbox, Ă© necessĂĄrio utilizar cartĂ”es especĂficos que permitem testar a implementação do Challenge com os fluxos de sucesso e falha. A tabela a seguir apresenta os detalhes desses cartĂ”es:
CartĂŁo | Fluxo | NĂșmero | CĂłdigo de segurança | Data de vencimento |
Mastercard | Challenge com sucesso | 5483 9281 6457 4623 | 123 | 11/25 |
Mastercard | Challenge nĂŁo autorizado | 5361 9568 0611 7557 | 123 | 11/25 |
Os passos para criar o pagamento sĂŁo os mesmos. Em caso de dĂșvida sobre como criar pagamentos com cartĂŁo, consulte a documentação sobre CartĂ”es.
<?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
Em ambos os fluxos (sucesso e falha), o Challenge, que Ă© uma tela semelhante Ă mostrada abaixo, deve ser exibido dentro do iframe:
O cĂłdigo de verificação fornecido Ă© apenas ilustrativo. Para concluir o fluxo de teste, basta clicar no botĂŁo Confirmar. ApĂłs concluir essa ação, siga as instruçÔes detalhadas na seção Verificar status da transação para identificar quando o Challenge foi concluĂdo e como verificar a atualização do pagamento.