From 00d22c038db21effb0d3d52479774a54a7b478cd Mon Sep 17 00:00:00 2001 From: Breno Martinusso Date: Tue, 4 Aug 2026 13:55:08 -0300 Subject: [PATCH] PIX-514 create payment with network token --- .github/workflows/pr.yml | 69 ++++++ README.md | 188 ++++++++++++++-- api30.sdk/pom.xml | 61 +++++- .../sdk/ecommerce/ExternalBrandTokenIT.java | 200 ++++++++++++++++++ .../sdk/ecommerce/CreditCard.java | 28 +++ .../cieloecommerce/sdk/ecommerce/Payment.java | 22 ++ .../sdk/ecommerce/ExternalBrandTokenTest.java | 113 ++++++++++ 7 files changed, 668 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/pr.yml create mode 100644 api30.sdk/src/it/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenIT.java create mode 100644 api30.sdk/src/test/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenTest.java diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..7cbcf13 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,69 @@ +name: PR + +on: + pull_request: + branches: + - master + +concurrency: + group: pr-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: api30.sdk + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java 8 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 8 + cache: maven + cache-dependency-path: api30.sdk/pom.xml + + - name: Run unit tests + run: mvn test -B + + integration-tests: + name: Integration tests + runs-on: ubuntu-latest + needs: unit-tests + defaults: + run: + working-directory: api30.sdk + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java 8 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 8 + cache: maven + cache-dependency-path: api30.sdk/pom.xml + + - name: Validate sandbox credentials + env: + CIELO_MERCHANT_ID: ${{ secrets.CIELO_MERCHANT_ID }} + CIELO_MERCHANT_KEY: ${{ secrets.CIELO_MERCHANT_KEY }} + run: | + if [ -z "$CIELO_MERCHANT_ID" ] || [ -z "$CIELO_MERCHANT_KEY" ]; then + echo "::error::Configure os secrets CIELO_MERCHANT_ID e CIELO_MERCHANT_KEY no repositório GitHub." + exit 1 + fi + + - name: Run integration tests + env: + CIELO_MERCHANT_ID: ${{ secrets.CIELO_MERCHANT_ID }} + CIELO_MERCHANT_KEY: ${{ secrets.CIELO_MERCHANT_KEY }} + run: mvn verify -DskipITs=false -B diff --git a/README.md b/README.md index 4877b4a..e2f5523 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,147 @@ SDK API-3.0 Java ## Principais recursos -* [x] Pagamentos por cartão de crédito. -* [x] Pagamentos recorrentes. - * [x] Com autorização na primeira recorrência. - * [x] Com autorização a partir da primeira recorrência. -* [x] Pagamentos por cartão de débito. -* [x] Pagamentos por boleto. -* [x] Pagamentos por transferência eletrônica. -* [x] Cancelamento de autorização. -* [x] Consulta de pagamentos. -* [x] Geração de token para o cartão para armazenamento seguro - +- [x] Pagamentos por cartão de crédito. +- [x] Pagamentos recorrentes. + - [x] Com autorização na primeira recorrência. + - [x] Com autorização a partir da primeira recorrência. +- [x] Pagamentos por cartão de débito. +- [x] Pagamentos por boleto. +- [x] Pagamentos por transferência eletrônica. +- [x] Cancelamento de autorização. +- [x] Consulta de pagamentos. +- [x] Geração de token para o cartão para armazenamento seguro + ## Limitações Por envolver a interface de usuário da aplicação, o SDK funciona apenas como um framework para criação das transações. Nos casos onde a autorização é direta, não há limitação; mas nos casos onde é necessário a autenticação ou qualquer tipo de redirecionamento do usuário, o desenvolvedor deverá utilizar o SDK para gerar o pagamento e, com o link retornado pela Cielo, providenciar o redirecionamento do usuário. +## Setup (Ubuntu) + +Pré-requisitos: **Java JDK 8+** e **Maven 3**. + +```bash +sudo apt update +sudo apt install -y openjdk-8-jdk maven +``` + +Verifique a instalação: + +```bash +java -version +mvn -version +``` + +Clone o repositório e compile o SDK: + +```bash +git clone +cd API-3.0-Java/api30.sdk +mvn clean package +``` + +O artefato gerado ficará em `api30.sdk/target/api30.sdk-0.1.0.jar`. + +Para usar o SDK em outro projeto Maven, instale localmente: + +```bash +cd api30.sdk +mvn install +``` + +## Testes unitários + +Os testes ficam em `api30.sdk/src/test/java` e usam JUnit 3. + +Para executar todos os testes: + +```bash +cd api30.sdk +mvn test +``` + +Para executar apenas uma classe: + +```bash +cd api30.sdk +mvn -Dtest=ExternalBrandTokenTest test +``` + +Para executar um método específico: + +```bash +cd api30.sdk +mvn -Dtest=ExternalBrandTokenTest#testPanPayloadRemainsUnchanged test +``` + +Se não tiver Java/Maven instalados localmente, use Docker: + +```bash +docker run --rm -v "$PWD":/workspace -w /workspace/api30.sdk maven:3.9-eclipse-temurin-8 mvn test +``` + +## Testes de integração + +Testes que chamam o **sandbox da Cielo** ficam em `api30.sdk/src/it/java` e rodam +separados dos unitários via `maven-failsafe-plugin`. Por padrão, `mvn test` e +`mvn package` **não** executam integração. + +Fluxo coberto: [token de bandeira externo](https://docs.cielo.com.br/ecommerce-cielo/reference/token-bandeira-externo). + +Os DPANs de teste seguem a regra do [sandbox de cartão de crédito](https://docs.cielo.com.br/ecommerce-cielo/reference/credito-sandbox): o **último dígito** define o resultado simulado. A classe usa a base `402400715376319X` (cartão de exemplo da Cielo). + +| Último dígito | Cenário IT | Status esperado | ReturnCode | +| ------------- | --------------- | --------------- | ---------- | +| 1 | Autorizado | 1 | 4 ou 6 | +| 2 | Não autorizado | 3 | 05 | +| 3 | Cartão expirado | 3 | 57 | + +### Variáveis de ambiente + +| Variável | Obrigatória | Descrição | +| ----------------------------- | ----------- | ------------------------------------------------------------- | +| `CIELO_MERCHANT_ID` | Sim | Credencial sandbox | +| `CIELO_MERCHANT_KEY` | Sim | Credencial sandbox | +| `CIELO_TEST_CRYPTOGRAM` | Não | Criptograma de teste (padrão: valor da documentação Cielo) | +| `CIELO_TEST_BRAND` | Não | Bandeira (padrão: `Visa` na CIT, `Master` na MIT) | +| `CIELO_ISSUER_TRANSACTION_ID` | Não | Usado na MIT se a CIT autorizada não retornar o identificador | +| `CIELO_IT_VERBOSE` | Não | `true` imprime a resposta JSON completa no console | + +### Executar + +```bash +cd api30.sdk + +export CIELO_MERCHANT_ID="seu-merchant-id-sandbox" +export CIELO_MERCHANT_KEY="sua-merchant-key-sandbox" + +mvn verify -DskipITs=false +``` + +A classe `ExternalBrandTokenIT` executa em ordem: + +1. **CIT autorizada** — DPAN terminando em `1`, valida `Status=1` e `ReturnCode` 4 ou 6. +2. **CIT negada** — DPAN terminando em `2`, valida `Status=3` e `ReturnCode=05`. +3. **CIT cartão expirado** — DPAN terminando em `3`, valida `Status=3` e `ReturnCode=57`. +4. **MIT** — reutiliza o `IssuerTransactionId` da CIT autorizada (ou `CIELO_ISSUER_TRANSACTION_ID`) sem criptograma. + +Para rodar só a integração (após compilar): + +```bash +cd api30.sdk +mvn failsafe:integration-test -DskipITs=false +``` + +Se as credenciais não estiverem configuradas, os testes de integração são +**ignorados** automaticamente. + +Para ver a resposta completa da Cielo no console: + +```bash +export CIELO_IT_VERBOSE=true +mvn verify -DskipITs=false +``` + ## Utilizando o SDK Para criar um pagamento simples com cartão de crédito com o SDK, basta fazer: @@ -86,7 +212,7 @@ try { // // Configure o SDK com seu merchant e o ambiente apropriado para // gerar o token cardToken = new CieloEcommerce(merchant, Environment.SANDBOX).createCardToken(cardToken); - + String generatedToken = cardToken.getCardToken(); System.out.printf("CardToken: %s\n", generatedToken); @@ -97,6 +223,44 @@ try { } ``` +### Criando um pagamento com token de bandeira externo + +O token de bandeira criado fora da Cielo deve ser informado como DPAN em +`CardNumber`. Ele não deve ser enviado em `CardToken`, pois esse campo é +reservado ao token criado pela própria Cielo. + +```java +Sale sale = new Sale("ID do pagamento"); +sale.customer("Comprador Teste"); + +Payment payment = sale.payment(15700); +payment.creditCard(null, "Visa") + .setCardNumber("DPAN GERADO PELA BANDEIRA") + .setCardNumberType(CreditCard.CardNumberType.DPAN) + .setHolder("Comprador Teste") + .setCryptogram("CRIPTOGRAMA GERADO PARA A TRANSACAO") + .setExpirationDate("12/2030"); + +sale = new CieloEcommerce(merchant, Environment.SANDBOX).createSale(sale); + +// Armazene sempre os identificadores mais recentes para transações relacionadas. +String issuerTransactionId = sale.getPayment().getIssuerTransactionId(); +String transactionLinkId = sale.getPayment().getTransactionLinkId(); +``` + +Quem chama o SDK é responsável por decidir se deve informar o criptograma: + +- Em uma transação iniciada pelo portador (CIT), informe `Cryptogram`. +- Em uma transação Mastercard iniciada pelo estabelecimento (MIT), não informe + `Cryptogram` e envie o `IssuerTransactionId` mais recente por meio de + `Payment.setIssuerTransactionId`. +- `TransactionLinkId` é retornado para transações Mastercard e deve ser + armazenado. Consulte a documentação da Cielo antes de enviá-lo em uma MIT, + pois o contrato de envio será disponibilizado antes de sua obrigatoriedade. + +`SecurityCode` é opcional no pagamento com token de bandeira. Por isso, o +exemplo passa `null` ao método `creditCard`. + ## Manual Para mais informações sobre a integração com a API 3.0 da Cielo, vide o manual em: [Integração API 3.0](https://developercielo.github.io/Webservice-3.0/) diff --git a/api30.sdk/pom.xml b/api30.sdk/pom.xml index 9e1f66d..104e428 100644 --- a/api30.sdk/pom.xml +++ b/api30.sdk/pom.xml @@ -13,13 +13,18 @@ UTF-8 1.7.36 + 4.13.2 + 3.2.5 + 3.2.5 + 3.5.0 + true junit junit - 3.8.1 + ${junit.version} test @@ -39,6 +44,60 @@ provided + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.version} + + + **/*IT.java + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven.failsafe.version} + + ${skipITs} + + **/*IT.java + + + + + + integration-test + verify + + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper.version} + + + add-it-source + generate-test-sources + + add-test-source + + + + src/it/java + + + + + + + + nexus-snapshots diff --git a/api30.sdk/src/it/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenIT.java b/api30.sdk/src/it/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenIT.java new file mode 100644 index 0000000..fce278e --- /dev/null +++ b/api30.sdk/src/it/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenIT.java @@ -0,0 +1,200 @@ +package cieloecommerce.sdk.ecommerce; + +import cieloecommerce.sdk.Merchant; +import cieloecommerce.sdk.ecommerce.request.CieloRequestException; +import com.google.gson.GsonBuilder; +import org.junit.Assume; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import java.io.IOException; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Testes de integração contra o sandbox da Cielo para token de bandeira externo. + * + * Usa cartões simulados conforme a documentação de sandbox da Cielo: o último + * dígito do DPAN define autorização ou recusa. + * + * @see Cartão de crédito em sandbox + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class ExternalBrandTokenIT { + private static final int STATUS_AUTHORIZED = 1; + private static final int STATUS_DENIED = 3; + + /** + * Base do cartão de exemplo da Cielo ({@code 4024.0071.5376.3191}). + * O sandbox usa o último dígito para simular o resultado. + */ + private static final String SANDBOX_DPAN_PREFIX = "402400715376319"; + private static final String DEFAULT_CRYPTOGRAM = "abcdefghijklmnopqrstuvw=="; + private static final String DEFAULT_CIT_BRAND = "Visa"; + private static final String DEFAULT_MIT_BRAND = "Master"; + + private static String issuerTransactionIdFromCit; + + @Test + public void test01_citSandboxAuthorized() throws IOException, CieloRequestException { + assumeCredentialsPresent(); + + Sale sale = createCitSale(uniqueOrderId("cit-authorized"), sandboxDpan('1')); + assertAuthorized(sale); + + issuerTransactionIdFromCit = sale.getPayment().getIssuerTransactionId(); + } + + @Test + public void test02_citSandboxDeniedNotAuthorized() throws IOException, CieloRequestException { + assumeCredentialsPresent(); + + Sale sale = createCitSale(uniqueOrderId("cit-denied"), sandboxDpan('2')); + assertDenied(sale, "05", "Não autorizada"); + } + + @Test + public void test03_citSandboxDeniedExpiredCard() throws IOException, CieloRequestException { + assumeCredentialsPresent(); + + Sale sale = createCitSale(uniqueOrderId("cit-expired"), sandboxDpan('3')); + assertDenied(sale, "57", "Cartão expirado"); + } + + @Test + public void test04_mitExternalBrandTokenPayment() throws IOException, CieloRequestException { + assumeCredentialsPresent(); + + String issuerTransactionId = issuerTransactionIdFromCit != null + ? issuerTransactionIdFromCit + : env("CIELO_ISSUER_TRANSACTION_ID"); + Assume.assumeTrue( + "Requer IssuerTransactionId retornado na CIT ou CIELO_ISSUER_TRANSACTION_ID", + issuerTransactionId != null && !issuerTransactionId.isEmpty()); + + Sale sale = buildMitSale(uniqueOrderId("mit"), issuerTransactionId, sandboxDpan('1')); + sale = createSale(sale); + + assertNotNull(sale.getPayment()); + assertNotNull(sale.getPayment().getPaymentId()); + assertNotNull(sale.getPayment().getStatus()); + logResponse("MIT", sale); + } + + private static Sale createCitSale(String merchantOrderId, String dpan) + throws IOException, CieloRequestException { + Sale sale = buildCitSale(merchantOrderId, dpan); + sale = createSale(sale); + logResponse("CIT", sale); + return sale; + } + + private static Sale buildCitSale(String merchantOrderId, String dpan) { + Sale sale = new Sale(merchantOrderId); + sale.customer("Comprador Teste"); + + sale.payment(15700) + .creditCard(null, testBrand(DEFAULT_CIT_BRAND)) + .setCardNumber(dpan) + .setCardNumberType(CreditCard.CardNumberType.DPAN) + .setHolder("Comprador Teste") + .setCryptogram(testCryptogram()) + .setExpirationDate("12/2030"); + + return sale; + } + + private static Sale buildMitSale(String merchantOrderId, String issuerTransactionId, String dpan) { + Sale sale = new Sale(merchantOrderId); + sale.customer("Comprador Teste"); + + Payment payment = sale.payment(15700); + payment.setIssuerTransactionId(issuerTransactionId); + payment.setInitiatedTransactionIndicator(new InitiatedTransactionIndicator() + .setCategory("M1") + .setSubcategory("Subscription")); + payment.creditCard(null, testBrand(DEFAULT_MIT_BRAND)) + .setCardNumber(dpan) + .setCardNumberType(CreditCard.CardNumberType.DPAN) + .setHolder("Comprador Teste") + .setExpirationDate("12/2030"); + + assertNull(payment.getCreditCard().getCryptogram()); + + return sale; + } + + private static void assertAuthorized(Sale sale) { + Payment payment = sale.getPayment(); + assertNotNull(payment); + assertNotNull(payment.getPaymentId()); + assertEquals(STATUS_AUTHORIZED, payment.getStatus().intValue()); + assertTrue( + "ReturnCode esperado: 4 ou 6. Recebido: " + payment.getReturnCode(), + "4".equals(payment.getReturnCode()) || "6".equals(payment.getReturnCode())); + } + + private static void assertDenied(Sale sale, String expectedReturnCode, String scenario) { + Payment payment = sale.getPayment(); + assertNotNull(payment); + assertNotNull(payment.getPaymentId()); + assertEquals("Cenário: " + scenario, STATUS_DENIED, payment.getStatus().intValue()); + assertEquals("Cenário: " + scenario, expectedReturnCode, payment.getReturnCode()); + } + + private static String sandboxDpan(char lastDigit) { + return SANDBOX_DPAN_PREFIX + lastDigit; + } + + private static Sale createSale(Sale sale) throws IOException, CieloRequestException { + Merchant merchant = new Merchant(env("CIELO_MERCHANT_ID"), env("CIELO_MERCHANT_KEY")); + return new CieloEcommerce(merchant, Environment.SANDBOX).createSale(sale); + } + + private static void assumeCredentialsPresent() { + String merchantId = env("CIELO_MERCHANT_ID"); + String merchantKey = env("CIELO_MERCHANT_KEY"); + Assume.assumeTrue( + "CIELO_MERCHANT_ID e CIELO_MERCHANT_KEY são obrigatórios para testes de integração", + merchantId != null && !merchantId.isEmpty() + && merchantKey != null && !merchantKey.isEmpty()); + } + + private static String testCryptogram() { + return envOrDefault("CIELO_TEST_CRYPTOGRAM", DEFAULT_CRYPTOGRAM); + } + + private static String testBrand(String defaultBrand) { + return envOrDefault("CIELO_TEST_BRAND", defaultBrand); + } + + private static String uniqueOrderId(String prefix) { + return "it-" + prefix + "-" + UUID.randomUUID().toString().substring(0, 8); + } + + private static void logResponse(String label, Sale sale) { + if (!"true".equalsIgnoreCase(env("CIELO_IT_VERBOSE"))) { + return; + } + + System.out.println(label + " response:"); + System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(sale)); + } + + private static String env(String name) { + return System.getenv(name); + } + + private static String envOrDefault(String name, String defaultValue) { + String value = env(name); + if (value == null || value.isEmpty()) { + return defaultValue; + } + return value; + } +} diff --git a/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/CreditCard.java b/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/CreditCard.java index 713cbfa..f304e5e 100644 --- a/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/CreditCard.java +++ b/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/CreditCard.java @@ -6,9 +6,15 @@ public class CreditCard { @SerializedName("CardNumber") private String cardNumber; + @SerializedName("CardNumberType") + private CardNumberType cardNumberType; + @SerializedName("Holder") private String holder; + @SerializedName("Cryptogram") + private String cryptogram; + @SerializedName("ExpirationDate") private String expirationDate; @@ -47,6 +53,24 @@ public CreditCard setCardNumber(String cardNumber) { return this; } + public CardNumberType getCardNumberType() { + return cardNumberType; + } + + public CreditCard setCardNumberType(CardNumberType cardNumberType) { + this.cardNumberType = cardNumberType; + return this; + } + + public String getCryptogram() { + return cryptogram; + } + + public CreditCard setCryptogram(String cryptogram) { + this.cryptogram = cryptogram; + return this; + } + public String getCardToken() { return cardToken; } @@ -91,4 +115,8 @@ public CreditCard setSecurityCode(String securityCode) { this.securityCode = securityCode; return this; } + + public enum CardNumberType { + PAN, DPAN + } } diff --git a/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/Payment.java b/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/Payment.java index dd54028..d8b899b 100644 --- a/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/Payment.java +++ b/api30.sdk/src/main/java/cieloecommerce/sdk/ecommerce/Payment.java @@ -37,6 +37,10 @@ public class Payment { private Provider provider; @SerializedName("PaymentId") private String paymentId; + @SerializedName("IssuerTransactionId") + private String issuerTransactionId; + @SerializedName("TransactionLinkId") + private String transactionLinkId; @SerializedName("Type") private Type type; @SerializedName("Amount") @@ -239,6 +243,24 @@ public Payment setPaymentId(String paymentId) { return this; } + public String getIssuerTransactionId() { + return issuerTransactionId; + } + + public Payment setIssuerTransactionId(String issuerTransactionId) { + this.issuerTransactionId = issuerTransactionId; + return this; + } + + public String getTransactionLinkId() { + return transactionLinkId; + } + + public Payment setTransactionLinkId(String transactionLinkId) { + this.transactionLinkId = transactionLinkId; + return this; + } + public String getProofOfSale() { return proofOfSale; } diff --git a/api30.sdk/src/test/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenTest.java b/api30.sdk/src/test/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenTest.java new file mode 100644 index 0000000..5ebbc5c --- /dev/null +++ b/api30.sdk/src/test/java/cieloecommerce/sdk/ecommerce/ExternalBrandTokenTest.java @@ -0,0 +1,113 @@ +package cieloecommerce.sdk.ecommerce; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import junit.framework.TestCase; + +public class ExternalBrandTokenTest extends TestCase { + private final Gson gson = new Gson(); + + public void testPanPayloadRemainsUnchanged() { + Sale sale = new Sale("order-pan"); + sale.payment(15700) + .creditCard("123", "Visa") + .setCardNumber("0000000000000001") + .setHolder("Comprador Teste") + .setExpirationDate("12/2030"); + + assertEquals(json("{" + + "\"MerchantOrderId\":\"order-pan\"," + + "\"Payment\":{" + + "\"Installments\":1," + + "\"Capture\":false," + + "\"Authenticate\":false," + + "\"Recurrent\":false," + + "\"CreditCard\":{" + + "\"CardNumber\":\"0000000000000001\"," + + "\"Holder\":\"Comprador Teste\"," + + "\"ExpirationDate\":\"12/2030\"," + + "\"SecurityCode\":\"123\"," + + "\"SaveCard\":false," + + "\"Brand\":\"Visa\"}," + + "\"SoftDescriptor\":\"\"," + + "\"Type\":\"CreditCard\"," + + "\"Amount\":15700}}"), json(gson.toJson(sale))); + } + + public void testCieloCardTokenPayloadRemainsUnchanged() { + Sale sale = new Sale("order-card-token"); + sale.payment(15700) + .creditCard("123", "Visa") + .setCardToken("d37bf475-307d-47be-b50a-8dcc38c5056c"); + + JsonElement payload = json(gson.toJson(sale)); + JsonElement creditCard = payload.getAsJsonObject() + .getAsJsonObject("Payment") + .getAsJsonObject("CreditCard"); + + assertEquals("d37bf475-307d-47be-b50a-8dcc38c5056c", + creditCard.getAsJsonObject().get("CardToken").getAsString()); + assertFalse(creditCard.getAsJsonObject().has("CardNumberType")); + assertFalse(creditCard.getAsJsonObject().has("Cryptogram")); + } + + public void testCitExternalTokenPayloadContainsDpanAndCryptogram() { + Sale sale = new Sale("order-cit"); + sale.payment(15700) + .creditCard(null, "Visa") + .setCardNumber("1234123412341231") + .setCardNumberType(CreditCard.CardNumberType.DPAN) + .setHolder("Comprador Teste") + .setCryptogram("abcdefghijklmnopqrstuvw==") + .setExpirationDate("12/2030"); + + JsonElement creditCard = json(gson.toJson(sale)).getAsJsonObject() + .getAsJsonObject("Payment") + .getAsJsonObject("CreditCard"); + + assertEquals("1234123412341231", creditCard.getAsJsonObject().get("CardNumber").getAsString()); + assertEquals("DPAN", creditCard.getAsJsonObject().get("CardNumberType").getAsString()); + assertEquals("abcdefghijklmnopqrstuvw==", + creditCard.getAsJsonObject().get("Cryptogram").getAsString()); + assertFalse(creditCard.getAsJsonObject().has("SecurityCode")); + assertFalse(creditCard.getAsJsonObject().has("CardToken")); + } + + public void testMastercardMitPayloadOmitsCryptogramAndSendsIssuerTransactionId() { + Sale sale = new Sale("order-mit"); + Payment payment = sale.payment(15700); + payment.setIssuerTransactionId("580027442382078"); + payment.setInitiatedTransactionIndicator(new InitiatedTransactionIndicator() + .setCategory("M1") + .setSubcategory("Subscription")); + payment.creditCard(null, "Master") + .setCardNumber("1234123412341231") + .setCardNumberType(CreditCard.CardNumberType.DPAN) + .setHolder("Comprador Teste") + .setExpirationDate("12/2030"); + + JsonElement paymentPayload = json(gson.toJson(sale)).getAsJsonObject() + .getAsJsonObject("Payment"); + JsonElement creditCard = paymentPayload.getAsJsonObject().getAsJsonObject("CreditCard"); + + assertEquals("580027442382078", + paymentPayload.getAsJsonObject().get("IssuerTransactionId").getAsString()); + assertFalse(creditCard.getAsJsonObject().has("Cryptogram")); + assertFalse(paymentPayload.getAsJsonObject().has("TransactionLinkId")); + } + + public void testBrandIdentifiersAreDeserializedFromResponse() { + Sale sale = gson.fromJson("{\"Payment\":{" + + "\"IssuerTransactionId\":\"580027442382078\"," + + "\"TransactionLinkId\":\"mK8vT2qA-9LpX7dWc3Rf_HsD1Z\"}}", Sale.class); + + assertEquals("580027442382078", sale.getPayment().getIssuerTransactionId()); + assertEquals("mK8vT2qA-9LpX7dWc3Rf_HsD1Z", sale.getPayment().getTransactionLinkId()); + } + + private JsonElement json(String value) { + return new JsonParser().parse(value); + } +}