Skip to content

added 3ds#574

Merged
aqudsilva merged 1 commit intomasterfrom
feature/orders3ds
Nov 28, 2025
Merged

added 3ds#574
aqudsilva merged 1 commit intomasterfrom
feature/orders3ds

Conversation

@aqudsilva
Copy link
Contributor

@aqudsilva aqudsilva commented Nov 3, 2025

Implementação de Suporte a 3DS (3D Secure) em Orders

🔍 Processo de Descoberta e Implementação

Esta seção documenta o passo a passo completo para implementar novas features no SDK PHP, servindo como guia para futuras implementações.

1. Monitoramento de Mudanças

  • Acompanhar anúncios de mudanças na API através do canal
  • Identificar novas features ou campos adicionados
  • Verificar se há breaking changes ou apenas adições
  • Documentar as mudanças para referência futura

Neste caso: Foi anunciada a feature de 3DS (3D Secure) para Orders.

2. Verificação no Fury Spec Hub

O que fazer:

  1. Acessar o Fury Spec Hub para verificar se há uma nova versão stable do swagger
  2. Localizar os endpoints e exemplos correspondentes à mudança anunciada
  3. Analisar a especificação completa para entender a estrutura dos dados

Neste caso:

  • Exemplo identificado: "POST /v1/orders - Online with 3DS"

  • Nós do JSON identificados:

    {
      "config": {
        "online": {
          "transaction_security": {
            "validation": "on_fraud_risk",
            "liability_shift": "required"
          }
        }
      }
    }

3. Análise da Spec e Exemplos

O que analisar no swagger:

  • Request Schema: Quais campos novos foram adicionados
  • Response Schema: O que a API retorna de novo
  • Exemplos: Casos de uso práticos (ex: "Online - Payment with 3DS")
  • Campos obrigatórios vs opcionais
  • Valores possíveis para enums

Neste caso:

  • Request: Campos adicionados em config.online.transaction_security

    transaction_security:
      type: object
      properties:
        validation:
          type: string
          enum: [always, on_fraud_risk, never]
        liability_shift:
          type: string
          enum: [required, preferred]
  • Response: Campo transaction_security adicionado em payment_method

    payment_method:
      transaction_security:
        url: "https://..." # URL do Challenge 3DS
        validation: "on_fraud_risk"
        liability_shift: "required"

4. Verificação na SDK

Passos:

  1. Verificar se já existe suporte para a feature nas classes de Orders
  2. Consultar exemplos existentes para verificar se já há implementação
  3. Identificar quais arquivos precisam ser criados ou modificados

Neste caso:

  • Verificamos src/MercadoPago/Resources/Order/OnlineConfig.php - não tinha transaction_security
  • Verificamos src/MercadoPago/Resources/Order/PaymentMethod.php - não tinha transaction_security
  • Verificamos examples/Order/ - não havia exemplo de 3DS
  • Conclusão: Era necessário criar nova classe e atualizar as existentes

5. Mapeamento para Estrutura do SDK PHP

5.1 Entendendo a Estrutura de Pastas

src/MercadoPago/
├── Client/              # Classes para fazer REQUESTS (chamadas HTTP)
│   └── Order/
│       └── OrderClient.php  # Métodos create(), get(), etc.
│
└── Resources/           # Classes para RESPONSES (dados recebidos)
    └── Order/
        ├── Order.php                      # Entidade principal
        ├── Config.php                     # Configuração da Order
        ├── OnlineConfig.php               # Config online
        ├── TransactionSecurity.php        [NOVO]
        ├── PaymentMethod.php              # Método de pagamento
        ├── Payment.php                    # Pagamento
        └── Transactions.php               # Transações

Diferença importante do SDK PHP:

  • Requests: Não usamos classes de request separadas. Usamos arrays associativos diretamente
  • Responses: Usamos classes Resource com propriedades públicas e o trait Mapper para deserialização

5.2 Mapeamento JSON → Classes PHP

Regra geral:

  • Cada objeto no JSON vira uma classe em Resources/
  • Cada propriedade no JSON vira uma property pública na classe
  • snake_case (JSON) → snake_case (PHP) - mantém o mesmo nome
  • Classes que contêm objetos aninhados usam o trait Mapper para mapeamento automático

Exemplo deste caso:

JSON Request:
{
  "config": {
    "online": {
      "transaction_security": {  ← Novo objeto
        "validation": "on_fraud_risk",
        "liability_shift": "required"
      }
    }
  }
}

Estrutura PHP:
OnlineConfig.php (já existia)
  └─ transaction_security: array|object|null
       └─ TransactionSecurity.php  [NOVA CLASSE]
            ├─ validation: ?string
            └─ liability_shift: ?string

---

JSON Response:
{
  "transactions": {
    "payments": [{
      "payment_method": {
        "transaction_security": {  ← Novo objeto
          "url": "https://...",
          "validation": "on_fraud_risk",
          "liability_shift": "required"
        }
      }
    }]
  }
}

Estrutura PHP:
PaymentMethod.php (já existia)
  └─ transaction_security: array|object|null
       └─ TransactionSecurity.php  [MESMA CLASSE]
            ├─ url: ?string
            ├─ validation: ?string
            └─ liability_shift: ?string

6. Como Funciona o Sistema de Serialização

6.1 O Trait Mapper

O SDK PHP usa o trait Mapper para deserialização automática de JSON para objetos PHP:

trait Mapper
{
    public function map(string $field)
    {
        $map = $this->getMap();
        return isset($map[$field]) ? $map[$field] : null;
    }

    abstract public function getMap(): array;
}

Como funciona:

  1. Quando a API retorna JSON, o Serializer percorre os dados
  2. Para cada campo, verifica se existe no $map da classe
  3. Se existe, cria uma instância da classe mapeada e deserializa recursivamente
  4. Se não existe, atribui o valor diretamente à propriedade

Exemplo prático:

// OnlineConfig.php
class OnlineConfig
{
    use Mapper;

    public array|object|null $transaction_security;

    private $map = [
        "transaction_security" => "MercadoPago\Resources\Order\TransactionSecurity",
    ];

    public function getMap(): array
    {
        return $this->map;
    }
}

Quando o JSON contém:

{
  "transaction_security": {
    "validation": "on_fraud_risk",
    "liability_shift": "required"
  }
}

O Serializer automaticamente:

  1. Detecta que transaction_security está no $map
  2. Cria instância de TransactionSecurity
  3. Preenche $validation e $liability_shift
  4. Atribui o objeto à propriedade $transaction_security

6.2 Tipos de Propriedades

Tipos permitidos:

  • ?string - Strings opcionais
  • ?int - Inteiros opcionais
  • ?array - Arrays simples (lista)
  • array|object|null - Objetos aninhados (requer mapeamento)
  • ?bool - Booleanos

Quando usar array|object|null:

  • Quando o campo pode ser um objeto aninhado que precisa ser mapeado
  • Permite que o Mapper transforme o array JSON em objeto PHP

7. Implementação das Classes

7.1 Classe TransactionSecurity

Arquivo: src/MercadoPago/Resources/Order/TransactionSecurity.php

<?php

namespace MercadoPago\Resources\Order;

use MercadoPago\Serialization\Mapper;

/** TransactionSecurity class. */
class TransactionSecurity
{
    /** Class mapper. */
    use Mapper;

    /** Validation. */
    public ?string $validation;

    /** Liability shift. */
    public ?string $liability_shift;

    /** URL. */
    public ?string $url;

    /**
     * Method responsible for getting map of entities.
     */
    public function getMap(): array
    {
        return [];
    }
}

Pontos importantes:

  • Comentário com versão da API no topo
  • Namespace correto: MercadoPago\Resources\Order
  • Usa use Mapper para permitir deserialização
  • Propriedades públicas com tipos nullable
  • Campo url apenas na resposta (não no request)
  • getMap() retorna array vazio porque não tem objetos aninhados

7.2 Atualização de OnlineConfig

Arquivo: src/MercadoPago/Resources/Order/OnlineConfig.php

// ... código existente ...

/** Transaction security. */
public array|object|null $transaction_security;

private $map = [
    "differential_pricing" => "MercadoPago\Resources\Common\DifferentialPricing",
    "transaction_security" => "MercadoPago\Resources\Order\TransactionSecurity",  // ADICIONADO
];

O que foi feito:

  • Adicionada propriedade $transaction_security
  • Adicionado mapeamento no array $map
  • O Mapper agora sabe como deserializar quando vier no JSON

7.3 Atualização de PaymentMethod

Arquivo: src/MercadoPago/Resources/Order/PaymentMethod.php

// ... código existente ...

/** Transaction security. */
public array|object|null $transaction_security;

private $map = [
    "transaction_security" => "MercadoPago\Resources\Order\TransactionSecurity",
];

/**
 * Method responsible for getting map of entities.
 */
public function getMap(): array
{
    return $this->map;
}

O que foi feito:

  • Adicionado use Mapper (não tinha antes)
  • Adicionada propriedade $transaction_security
  • Adicionado array $map com mapeamento
  • Adicionado método getMap() (obrigatório quando usa Mapper)

Importante: A mesma classe TransactionSecurity é usada tanto no request (via OnlineConfig) quanto na response (via PaymentMethod). A diferença é que na response também vem o campo url.

8. Criação de Exemplos

Arquivo: examples/Order/CreateOrderWith3DS.php

O que incluir:

  1. Imports necessários
  2. Configuração de credenciais (usar placeholders)
  3. Criação do request como array associativo
  4. Chamada ao cliente
  5. Processamento da resposta
  6. Tratamento de casos especiais (Challenge, etc.)
  7. Output com informações relevantes
  8. Tratamento de exceções

Estrutura padrão dos exemplos:

<?php

namespace Examples\Order;

require_once '../../vendor/autoload.php';

use MercadoPago\Client\Common\RequestOptions;
use MercadoPago\Client\Order\OrderClient;
use MercadoPago\Exceptions\MPApiException;
use MercadoPago\MercadoPagoConfig;

// Step 1: Configurar credenciais
MercadoPagoConfig::setAccessToken("<ACCESS_TOKEN>");
MercadoPagoConfig::setRuntimeEnviroment(MercadoPagoConfig::LOCAL);

// Step 2: Inicializar cliente
$client = new OrderClient();

try {
    // Step 3: Criar request como array
    $request = [
        // ... campos do request
    ];

    // Step 4: Criar request options
    $request_options = new RequestOptions();
    $request_options->setCustomHeaders(["X-Idempotency-Key: <IDEMPOTENCY_KEY>"]);

    // Step 5: Executar request
    $order = $client->create($request, $request_options);

    // Step 6: Processar resposta
    // ...

} catch (MPApiException $e) {
    // Tratamento de exceções da API
} catch (\Exception $e) {
    // Tratamento de outras exceções
}

Exemplo completo com 3DS:

O exemplo criado mostra:

  1. Como configurar uma Order com 3DS no request
  2. Como verificar se o Challenge é necessário na response
  3. Como acessar a URL do Challenge quando necessário
  4. Como processar diferentes status de pagamento

9. Estrutura Completa do SDK (Resumo)

src/MercadoPago/
├── Client/
│   └── [Recurso]/
│       └── [Recurso]Client.php    # Métodos HTTP (create, get, etc.)
│
├── Resources/
│   └── [Recurso]/
│       ├── [Recurso].php          # Entidade principal
│       └── Sub-objetos/           # Classes auxiliares
│
├── Serialization/
│   ├── Mapper.php                 # Trait para mapeamento
│   └── Serializer.php            # Serialização/deserialização
│
└── Net/
    └── MPResource.php             # Base para Resources

examples/
└── [Recurso]/
    ├── Create.php
    ├── Get.php
    └── [FeatureEspecial].php      # Ex: CreateOrderWith3DS.php

Fluxo de uma chamada:

  1. Usuário cria array associativo com dados do request
  2. Chama OrderClient->create($request)
  3. SDK serializa array para JSON
  4. Envia POST para API
  5. Recebe JSON de resposta
  6. SDK deserializa JSON para objetos PHP usando Mapper
  7. Usuário acessa $order->transactions->payments[0]->payment_method->transaction_security->url

10. Diferenças Importantes: Request vs Response

Request (Array Associativo):

$request = [
    "config" => [
        "online" => [
            "transaction_security" => [
                "validation" => "on_fraud_risk",
                "liability_shift" => "required"
            ]
        ]
    ]
];
  • Não usamos classes para requests
  • Usamos arrays associativos diretamente
  • Mais simples e direto
  • JSON é gerado automaticamente pelo SDK

Response (Objetos PHP):

$order = $client->create($request);

// Acessar propriedades
$validation = $order->config->online->transaction_security->validation;
$challengeUrl = $order->transactions->payments[0]->payment_method->transaction_security->url;
  • Usamos classes Resource com propriedades públicas
  • Mapper faz conversão automática de JSON para objetos
  • Tipagem forte com PHP 8.2+
  • Acesso direto às propriedades

🎯 Objetivo

Permitir que integradores implementem autenticação 3DS em suas transações através do SDK PHP, com:

  • Configuração flexível de quando executar o 3DS (validation)
  • Gestão de transferência de responsabilidade (liability_shift)
  • Tratamento adequado do fluxo de Challenge
  • Documentação completa e exemplos práticos

🔧 Alterações Técnicas

Arquivos Modificados

1. src/MercadoPago/Resources/Order/OnlineConfig.php

  • Adicionada propriedade $transaction_security do tipo array|object|null
  • Adicionado mapeamento no array $map para TransactionSecurity
  • Permite configurar 3DS no nó config.online.transaction_security do request

2. src/MercadoPago/Resources/Order/PaymentMethod.php

  • Adicionado use Mapper (não tinha antes)
  • Adicionada propriedade $transaction_security do tipo array|object|null
  • Adicionado array $map com mapeamento para TransactionSecurity
  • Adicionado método getMap() (obrigatório quando usa Mapper)
  • Permite receber dados de 3DS na resposta, incluindo a URL do Challenge

Novos Arquivos Criados

3. src/MercadoPago/Resources/Order/TransactionSecurity.php

class TransactionSecurity
{
    use Mapper;

    public ?string $validation;        // "on_fraud_risk", "always", "never"
    public ?string $liability_shift;  // "required", "preferred"
    public ?string $url;               // URL do Challenge (apenas na response)
}

Classe para representar transaction_security tanto no request quanto na response.

4. examples/Order/CreateOrderWith3DS.php

Exemplo completo mostrando:

  • Como configurar uma Order com 3DS no request (array associativo)
  • Como verificar se o Challenge é necessário na response
  • Como acessar a URL do Challenge quando status = "action_required"
  • Como processar diferentes status de pagamento

🔄 Fluxo de Integração

1. Request com 3DS

$request = [
    "type" => "online",
    "total_amount" => "150.00",
    "config" => [
        "online" => [
            "transaction_security" => [
                "validation" => "on_fraud_risk",
                "liability_shift" => "required"
            ]
        ]
    ],
    "payer" => [
        "email" => "<PAYER_EMAIL>",
        "identification" => [
            "type" => "<IDENTIFICATION_TYPE>",
            "number" => "<IDENTIFICATION_NUMBER>"
        ]
    ],
    "transactions" => [
        "payments" => [
            [
                "amount" => "150.00",
                "payment_method" => [
                    "id" => "master",
                    "type" => "credit_card",
                    "token" => "<CARD_TOKEN>",
                    "installments" => 1
                ]
            ]
        ]
    ]
];

$order = $client->create($request);

2. Processamento da Resposta

Sem Challenge:

if ($payment->status === "processed") {
    // Pagamento aprovado diretamente
    echo "Pagamento aprovado sem Challenge";
}

Com Challenge:

if ($payment->status === "action_required" && 
    $payment->status_detail === "pending_challenge") {
    
    // Obter URL do Challenge
    $challengeUrl = $payment->payment_method->transaction_security->url;
    
    // Exibir iframe com a URL
    echo "<iframe src='$challengeUrl'></iframe>";
    
    // Escutar evento de conclusão (JavaScript)
    // window.addEventListener("message", (e) => {
    //     if (e.data.status === "COMPLETE") {
    //         // Verificar status atualizado
    //     }
    // });
}

3. Valores Disponíveis

Campo validation:

  • "on_fraud_risk": 3DS será exigido dependendo do risco da transação (recomendado)
  • "always": Sempre executar 3DS
  • "never": Nunca executar 3DS (padrão)

Campo liability_shift:

  • "required": A responsabilidade financeira em caso de chargeback é da bandeira
  • "preferred": Preferencial mas não obrigatório

4. Status Possíveis

status status_detail Descrição
processed accredited Transação aprovada sem autenticação
failed failed Transação rejeitada sem autenticação
action_required pending_challenge Transação pendente de autenticação
failed cc_rejected_3ds_challenge* Transação rejeitada devido a falha no Challenge
cancelled expired Transação com Challenge cancelada após 5min

Para que seja possivel criar uma nova versão com os ajustes que fizemos, devemos fazer um bump da versão após mergearmos as nossas mudanças. Segue pr de exemplo #577

@aqudsilva aqudsilva merged commit f90d7cb into master Nov 28, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants