diff --git a/src/main/java/com/mercadopago/client/order/OrderOnlineConfig.java b/src/main/java/com/mercadopago/client/order/OrderOnlineConfig.java index 83f71d68..e5361182 100644 --- a/src/main/java/com/mercadopago/client/order/OrderOnlineConfig.java +++ b/src/main/java/com/mercadopago/client/order/OrderOnlineConfig.java @@ -26,4 +26,7 @@ public class OrderOnlineConfig { /** Differential pricing. */ private OrderDifferentialPricing differentialPricing; + + /** Transaction security configuration for 3DS authentication. */ + private OrderTransactionSecurity transactionSecurity; } diff --git a/src/main/java/com/mercadopago/client/order/OrderTransactionSecurity.java b/src/main/java/com/mercadopago/client/order/OrderTransactionSecurity.java new file mode 100644 index 00000000..ea11bc74 --- /dev/null +++ b/src/main/java/com/mercadopago/client/order/OrderTransactionSecurity.java @@ -0,0 +1,30 @@ +package com.mercadopago.client.order; + +import lombok.Builder; +import lombok.Getter; + +/** + * Transaction Security configuration for 3DS authentication. + * Used in Order requests to configure 3D Secure validation. + */ +@Getter +@Builder +public class OrderTransactionSecurity { + + /** + * Validation mode for 3DS authentication. + * Possible values: + * - "always": Always require 3DS authentication + * - "on_fraud_risk": Require 3DS based on fraud risk assessment (recommended) + * - "never": Never require 3DS authentication (default) + */ + private String validation; + + /** + * Liability shift configuration. + * Possible values: + * - "required": Financial liability in case of chargeback is with the card brand + * - "preferred": Preferred liability shift + */ + private String liabilityShift; +} diff --git a/src/main/java/com/mercadopago/example/apis/order/CreateOrderWith3DS.java b/src/main/java/com/mercadopago/example/apis/order/CreateOrderWith3DS.java new file mode 100644 index 00000000..2329aa20 --- /dev/null +++ b/src/main/java/com/mercadopago/example/apis/order/CreateOrderWith3DS.java @@ -0,0 +1,164 @@ +package com.mercadopago.example.apis.order; + +import com.mercadopago.MercadoPagoConfig; +import com.mercadopago.client.common.IdentificationRequest; +import com.mercadopago.client.order.*; +import com.mercadopago.exceptions.MPApiException; +import com.mercadopago.exceptions.MPException; +import com.mercadopago.resources.order.Order; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Example of creating an Order with 3DS (3D Secure) authentication. + * This example demonstrates how to configure transaction security for enhanced payment security. + */ +public class CreateOrderWith3DS { + + /** + * Creates an order with 3DS authentication enabled. + * + * @param accessToken Your MercadoPago access token + * @param cardToken Token representing the credit card + * @param payerEmail Payer's email address + * @param identificationType Payer's identification type (e.g., "CPF", "DNI") + * @param identificationNumber Payer's identification number + * @return Created Order with 3DS configuration + * @throws MPException if there's an error in the request + * @throws MPApiException if the API returns an error + */ + public static Order createOrderWith3DS( + String accessToken, + String cardToken, + String payerEmail, + String identificationType, + String identificationNumber) throws MPException, MPApiException { + + // Set your access token + MercadoPagoConfig.setAccessToken(accessToken); + + // Create the order client + OrderClient client = new OrderClient(); + + // Configure 3DS transaction security + OrderTransactionSecurity transactionSecurity = OrderTransactionSecurity.builder() + .validation("on_fraud_risk") // Recommended: validate based on fraud risk + .liabilityShift("required") // Required for liability shift to card brand + .build(); + + // Configure online settings with 3DS + OrderOnlineConfig onlineConfig = OrderOnlineConfig.builder() + .transactionSecurity(transactionSecurity) + .build(); + + // Configure order settings + OrderConfigRequest config = OrderConfigRequest.builder() + .online(onlineConfig) + .build(); + + // Configure payment method + OrderPaymentMethodRequest paymentMethod = OrderPaymentMethodRequest.builder() + .id("master") // Card brand (master, visa, etc.) + .type("credit_card") // Payment type + .token(cardToken) // Card token from MercadoPago SDK JS + .installments(1) // Number of installments + .build(); + + // Configure payment + List payments = new ArrayList<>(); + payments.add(OrderPaymentRequest.builder() + .amount("150.00") + .paymentMethod(paymentMethod) + .build()); + + // Configure transactions + OrderTransactionRequest transactions = OrderTransactionRequest.builder() + .payments(payments) + .build(); + + // Configure payer information + IdentificationRequest identification = IdentificationRequest.builder() + .type(identificationType) + .number(identificationNumber) + .build(); + + OrderPayerRequest payer = OrderPayerRequest.builder() + .email(payerEmail) + .identification(identification) + .build(); + + // Create the order request + OrderCreateRequest request = OrderCreateRequest.builder() + .type("online") + .externalReference("3ds_test_" + System.currentTimeMillis()) + .processingMode("automatic") + .totalAmount("150.00") + .config(config) + .payer(payer) + .transactions(transactions) + .build(); + + // Create the order + Order order = client.create(request); + + return order; + } + + /** + * Main method to demonstrate the usage. + */ + public static void main(String[] args) { + try { + // Replace with your actual values + String accessToken = "YOUR_ACCESS_TOKEN"; + String cardToken = "YOUR_CARD_TOKEN"; // Generated using MercadoPago SDK JS + String payerEmail = "test_user@example.com"; + String identificationType = "CPF"; + String identificationNumber = "12345678901"; + + Order order = createOrderWith3DS( + accessToken, + cardToken, + payerEmail, + identificationType, + identificationNumber + ); + + System.out.println("Order created successfully!"); + System.out.println("Order ID: " + order.getId()); + System.out.println("Status: " + order.getStatus()); + System.out.println("Status Detail: " + order.getStatusDetail()); + + // Check if 3DS challenge is required + if ("action_required".equals(order.getStatus()) && + "pending_challenge".equals(order.getStatusDetail())) { + + // Get the challenge URL from the payment method + if (order.getTransactions() != null && + order.getTransactions().getPayments() != null && + !order.getTransactions().getPayments().isEmpty()) { + + var payment = order.getTransactions().getPayments().get(0); + if (payment.getPaymentMethod() != null && + payment.getPaymentMethod().getTransactionSecurity() != null) { + + String challengeUrl = payment.getPaymentMethod() + .getTransactionSecurity().getUrl(); + + System.out.println("3DS Challenge required!"); + System.out.println("Challenge URL: " + challengeUrl); + System.out.println("Display this URL in an iframe for the user to complete the challenge."); + } + } + } else if ("processed".equals(order.getStatus())) { + System.out.println("Payment processed successfully without 3DS challenge."); + } + + } catch (MPException | MPApiException e) { + System.err.println("Error creating order: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/mercadopago/resources/order/OrderDifferentialPricing.java b/src/main/java/com/mercadopago/resources/order/OrderDifferentialPricing.java new file mode 100644 index 00000000..88e4482e --- /dev/null +++ b/src/main/java/com/mercadopago/resources/order/OrderDifferentialPricing.java @@ -0,0 +1,14 @@ +package com.mercadopago.resources.order; + +import lombok.Getter; + +/** + * Order Differential Pricing response. + * Contains differential pricing configuration information. + */ +@Getter +public class OrderDifferentialPricing { + + /** Differential pricing ID. */ + private Integer id; +} diff --git a/src/main/java/com/mercadopago/resources/order/OrderOnlineConfig.java b/src/main/java/com/mercadopago/resources/order/OrderOnlineConfig.java new file mode 100644 index 00000000..1582d34b --- /dev/null +++ b/src/main/java/com/mercadopago/resources/order/OrderOnlineConfig.java @@ -0,0 +1,32 @@ +package com.mercadopago.resources.order; + +import lombok.Getter; + +/** + * Order Online Configuration response. + * Contains configuration for online orders including 3DS transaction security. + */ +@Getter +public class OrderOnlineConfig { + + /** Callback URL. */ + private String callbackUrl; + + /** Success URL. */ + private String successUrl; + + /** Pending URL. */ + private String pendingUrl; + + /** Failure URL. */ + private String failureUrl; + + /** Auto return URL. */ + private String autoReturnUrl; + + /** Differential pricing. */ + private OrderDifferentialPricing differentialPricing; + + /** Transaction security configuration for 3DS authentication. */ + private OrderTransactionSecurity transactionSecurity; +} diff --git a/src/main/java/com/mercadopago/resources/order/OrderPaymentMethod.java b/src/main/java/com/mercadopago/resources/order/OrderPaymentMethod.java index ef0acf9b..430d3fe0 100644 --- a/src/main/java/com/mercadopago/resources/order/OrderPaymentMethod.java +++ b/src/main/java/com/mercadopago/resources/order/OrderPaymentMethod.java @@ -49,4 +49,7 @@ public class OrderPaymentMethod { /** Ticket Url. */ private String ticketUrl; + /** Transaction security information for 3DS authentication. */ + private OrderTransactionSecurity transactionSecurity; + } diff --git a/src/main/java/com/mercadopago/resources/order/OrderTransactionSecurity.java b/src/main/java/com/mercadopago/resources/order/OrderTransactionSecurity.java new file mode 100644 index 00000000..12948282 --- /dev/null +++ b/src/main/java/com/mercadopago/resources/order/OrderTransactionSecurity.java @@ -0,0 +1,30 @@ +package com.mercadopago.resources.order; + +import lombok.Getter; + +/** + * Transaction Security response for 3DS authentication. + * Contains information about 3DS validation and challenge URL when required. + */ +@Getter +public class OrderTransactionSecurity { + + /** + * Challenge URL for 3DS authentication. + * This URL should be displayed in an iframe when challenge is required. + * Present only when status is "action_required" and status_detail is "pending_challenge". + */ + private String url; + + /** + * Validation mode used for 3DS authentication. + * Values: "always", "on_fraud_risk", "never" + */ + private String validation; + + /** + * Liability shift configuration. + * Values: "required", "preferred" + */ + private String liabilityShift; +} diff --git a/src/test/java/com/mercadopago/client/order/OrderClientWith3DSTest.java b/src/test/java/com/mercadopago/client/order/OrderClientWith3DSTest.java new file mode 100644 index 00000000..4001810c --- /dev/null +++ b/src/test/java/com/mercadopago/client/order/OrderClientWith3DSTest.java @@ -0,0 +1,139 @@ +package com.mercadopago.client.order; + +import com.mercadopago.BaseClientIT; +import com.mercadopago.client.common.IdentificationRequest; +import com.mercadopago.exceptions.MPApiException; +import com.mercadopago.exceptions.MPException; +import com.mercadopago.resources.order.Order; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for Order creation with 3DS authentication. + */ +public class OrderClientWith3DSTest extends BaseClientIT { + + private final OrderClient client = new OrderClient(); + + @Test + public void testCreateOrderWith3DS_Success() throws MPException, MPApiException { + // Configure 3DS transaction security + OrderTransactionSecurity transactionSecurity = OrderTransactionSecurity.builder() + .validation("on_fraud_risk") + .liabilityShift("required") + .build(); + + // Configure online settings with 3DS + OrderOnlineConfig onlineConfig = OrderOnlineConfig.builder() + .transactionSecurity(transactionSecurity) + .build(); + + // Configure order settings + OrderConfigRequest config = OrderConfigRequest.builder() + .online(onlineConfig) + .build(); + + // Configure payment method + OrderPaymentMethodRequest paymentMethod = OrderPaymentMethodRequest.builder() + .id("master") + .type("credit_card") + .token("test_card_token") + .installments(1) + .build(); + + // Configure payment + List payments = new ArrayList<>(); + payments.add(OrderPaymentRequest.builder() + .amount("150.00") + .paymentMethod(paymentMethod) + .build()); + + // Configure transactions + OrderTransactionRequest transactions = OrderTransactionRequest.builder() + .payments(payments) + .build(); + + // Configure payer information + IdentificationRequest identification = IdentificationRequest.builder() + .type("CPF") + .number("12345678901") + .build(); + + OrderPayerRequest payer = OrderPayerRequest.builder() + .email("test_user@example.com") + .identification(identification) + .build(); + + // Create the order request + OrderCreateRequest request = OrderCreateRequest.builder() + .type("online") + .externalReference("3ds_test_" + System.currentTimeMillis()) + .processingMode("automatic") + .totalAmount("150.00") + .config(config) + .payer(payer) + .transactions(transactions) + .build(); + + // Verify that the request is properly configured + assertNotNull(request.getConfig()); + assertNotNull(request.getConfig().getOnline()); + assertNotNull(request.getConfig().getOnline().getTransactionSecurity()); + assertEquals("on_fraud_risk", request.getConfig().getOnline().getTransactionSecurity().getValidation()); + assertEquals("required", request.getConfig().getOnline().getTransactionSecurity().getLiabilityShift()); + } + + @Test + public void testTransactionSecurityBuilder() { + // Test building transaction security with all validation options + OrderTransactionSecurity alwaysValidation = OrderTransactionSecurity.builder() + .validation("always") + .liabilityShift("preferred") + .build(); + + assertEquals("always", alwaysValidation.getValidation()); + assertEquals("preferred", alwaysValidation.getLiabilityShift()); + + OrderTransactionSecurity neverValidation = OrderTransactionSecurity.builder() + .validation("never") + .liabilityShift("required") + .build(); + + assertEquals("never", neverValidation.getValidation()); + assertEquals("required", neverValidation.getLiabilityShift()); + + OrderTransactionSecurity fraudRiskValidation = OrderTransactionSecurity.builder() + .validation("on_fraud_risk") + .liabilityShift("required") + .build(); + + assertEquals("on_fraud_risk", fraudRiskValidation.getValidation()); + assertEquals("required", fraudRiskValidation.getLiabilityShift()); + } + + @Test + public void testOrderOnlineConfigWith3DS() { + OrderTransactionSecurity transactionSecurity = OrderTransactionSecurity.builder() + .validation("on_fraud_risk") + .liabilityShift("required") + .build(); + + OrderOnlineConfig onlineConfig = OrderOnlineConfig.builder() + .callbackUrl("https://example.com/callback") + .successUrl("https://example.com/success") + .failureUrl("https://example.com/failure") + .transactionSecurity(transactionSecurity) + .build(); + + assertNotNull(onlineConfig.getTransactionSecurity()); + assertEquals("on_fraud_risk", onlineConfig.getTransactionSecurity().getValidation()); + assertEquals("required", onlineConfig.getTransactionSecurity().getLiabilityShift()); + assertEquals("https://example.com/callback", onlineConfig.getCallbackUrl()); + assertEquals("https://example.com/success", onlineConfig.getSuccessUrl()); + assertEquals("https://example.com/failure", onlineConfig.getFailureUrl()); + } +}