Como integrar 3DS com Checkout API
Nesta documentaĆ§Ć£o vocĆŖ encontrarĆ” toda a informaĆ§Ć£o necessĆ”ria para realizar a integraĆ§Ć£o com 3DS com Checkout API. 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.