Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/main/java/com/checkout/payments/request/PaymentRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.checkout.payments.ShippingDetails;
import com.checkout.payments.ThreeDSRequest;
import com.checkout.payments.request.source.AbstractRequestSource;
import com.checkout.payments.request.source.RequestCardSource;
import com.checkout.payments.PaymentPlan;
import com.checkout.payments.PaymentRouting;
import com.checkout.payments.PaymentSubscription;
Expand Down Expand Up @@ -43,11 +44,17 @@ public final class PaymentRequest {
*/
private AbstractRequestSource source;

/**
* An alternative card source to attempt if the payment using the primary source fails.
* [Optional]
*/
private RequestCardSource fallbackSource;

/**
* The payment amount in minor currency units. To perform a card verification, do not provide
* the amount or provide a value of 0.
* [Optional]
* min 0
* min 0, max 9999999999
*/
private Long amount;

Expand Down Expand Up @@ -197,6 +204,11 @@ public final class PaymentRequest {
private String failureUrl;

/**
* The IP address used to make the payment. Used by Checkout.com's risk engine to check the customer's
* IP address. Only accepts IPv4 and IPv6 addresses. Required if source.type is sequra.
* [Optional]
* Format: ipv4
* max 45 characters
* @deprecated Deprecated in the API on 2025-03-11. Use {@code risk.device.network.ipv4} or {@code risk.device.network.ipv6} instead.
*/
@Deprecated
Expand All @@ -215,6 +227,8 @@ public final class PaymentRequest {
private PaymentRecipient recipient;

/**
* The marketplace data for the payment. No longer in the API specification.
* [Optional]
* @deprecated Use {@link PaymentRequest#amountAllocations} instead.
*/
@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,77 @@
public final class RequestCardSource extends AbstractRequestSource {

/**
* The card number (without separators). Write-only.
* [Optional]
* The card number (without separators).
* [Required]
*/
private String number;

/**
* The expiry month of the card.
* [Optional]
* [Required]
* min 1, max 12
*/
private Integer expiryMonth;

/**
* The expiry year of the card.
* [Optional]
* 4-digit expiry year of the card.
* [Required]
*/
private Integer expiryYear;

/**
* The cardholder's name. Write-only.
* The name of the cardholder.
* [Optional]
*/
private String name;

/**
* The card verification value/code. 3 digits, except for American Express (4 digits). Write-only.
* The card verification value/code. 3 digits, except for American Express (4 digits).
* [Optional]
* min 3 characters, max 4 characters
*/
private String cvv;

/**
* Set to true for payments that use stored card details. Write-only.
* Indicates whether this card is being submitted from your own stored card-on-file system, rather than
* being entered by the customer at the time of payment. Set to true when you provide card details that
* you already have on file for the customer.
* [Optional]
*/
private Boolean stored;

/**
* Set to true if you intend to reuse the payment credentials in subsequent payments. Write-only.
* This must be set to true if you intend to reuse the payment credentials in subsequent payments.
* Setting the field as false will mean that a payment instrument will not be included in the payment
* response.
* [Optional]
*/
private Boolean storeForFutureUse;

/**
* The payment source owner's billing address.
* The billing address of the cardholder.
* [Optional]
*/
private Address billingAddress;

/**
* The payment source owner's phone number.
* The phone number of the cardholder.
* [Optional]
*/
private Phone phone;

/**
* The card account holder's details.
* Information about the account holder of the card.
* [Optional]
*/
private AccountHolder accountHolder;

/**
* Specifies whether to use the Real-Time Account Updater to update the card information.
* [Optional]
*/
private Boolean allowUpdate;

@Builder
private RequestCardSource(final String number,
final Integer expiryMonth,
Comment thread
armando-rodriguez-cko marked this conversation as resolved.
Expand All @@ -86,7 +98,8 @@ private RequestCardSource(final String number,
final Boolean storeForFutureUse,
final Address billingAddress,
final Phone phone,
final AccountHolder accountHolder) {
final AccountHolder accountHolder,
final Boolean allowUpdate) {
super(PaymentSourceType.CARD);
this.number = number;
this.expiryMonth = expiryMonth;
Expand All @@ -98,6 +111,7 @@ private RequestCardSource(final String number,
this.billingAddress = billingAddress;
this.phone = phone;
this.accountHolder = accountHolder;
this.allowUpdate = allowUpdate;
}

public RequestCardSource() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package com.checkout.payments.request;

import com.checkout.GsonSerializer;
import com.checkout.common.AccountHolder;
import com.checkout.common.AccountHolderType;
import com.checkout.common.Address;
import com.checkout.common.CountryCode;
import com.checkout.common.Currency;
import com.checkout.common.PaymentSourceType;
import com.checkout.common.Phone;
import com.checkout.payments.AuthorizationType;
import com.checkout.payments.PaymentType;
import com.checkout.payments.request.source.RequestCardSource;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.junit.jupiter.api.Test;

import java.time.Instant;
Expand Down Expand Up @@ -253,4 +262,140 @@ void shouldHandleAbsentOptionalFields() {
assertNull(request.getMerchantInitiated());
assertNull(request.getPreviousPaymentId());
}

// ------------------------------------------------------------------------
// fallback_source
// Covers all 12 properties of the PaymentRequestFallbackSource schema.
// ------------------------------------------------------------------------

@Test
void shouldSerializeFallbackSourceWithAllProperties() {
final PaymentRequest request = PaymentRequest.builder()
.currency(Currency.USD)
.fallbackSource(buildFallbackSource())
.build();

final JsonObject json = JsonParser.parseString(serializer.toJson(request)).getAsJsonObject();

assertTrue(json.has("fallback_source"));
final JsonObject fallbackSource = json.getAsJsonObject("fallback_source");
assertEquals(12, fallbackSource.size());
assertEquals("card", fallbackSource.get("type").getAsString());
assertEquals("4543474002249996", fallbackSource.get("number").getAsString());
assertEquals(6, fallbackSource.get("expiry_month").getAsInt());
assertEquals(2030, fallbackSource.get("expiry_year").getAsInt());
assertEquals("Bruce Wayne", fallbackSource.get("name").getAsString());
assertEquals("956", fallbackSource.get("cvv").getAsString());
assertTrue(fallbackSource.get("stored").getAsBoolean());
assertFalse(fallbackSource.get("store_for_future_use").getAsBoolean());
assertFalse(fallbackSource.get("allow_update").getAsBoolean());
assertEquals("London", fallbackSource.getAsJsonObject("billing_address").get("city").getAsString());
assertEquals("44", fallbackSource.getAsJsonObject("phone").get("country_code").getAsString());
assertEquals("individual", fallbackSource.getAsJsonObject("account_holder").get("type").getAsString());
assertEquals("Bruce", fallbackSource.getAsJsonObject("account_holder").get("first_name").getAsString());
}

@Test
void shouldRoundTripFallbackSource() {
final PaymentRequest original = PaymentRequest.builder()
.currency(Currency.USD)
.fallbackSource(buildFallbackSource())
.build();

final String json = serializer.toJson(original);
final PaymentRequest deserialized = serializer.fromJson(json, PaymentRequest.class);

assertNotNull(deserialized.getFallbackSource());
assertEquals(PaymentSourceType.CARD, deserialized.getFallbackSource().getType());
assertEquals(original.getFallbackSource(), deserialized.getFallbackSource());
}

@Test
void shouldDeserializeFallbackSourceSwaggerExample() {
final String json = "{\"currency\":\"USD\",\"fallback_source\":{"
+ "\"type\":\"card\",\"number\":\"4543474002249996\",\"expiry_month\":6,"
+ "\"expiry_year\":2030,\"name\":\"Bruce Wayne\",\"cvv\":\"956\",\"stored\":true,"
+ "\"store_for_future_use\":false,\"allow_update\":false,"
+ "\"billing_address\":{\"address_line1\":\"123 High St.\",\"city\":\"London\",\"zip\":\"SW1A 1AA\",\"country\":\"GB\"},"
+ "\"phone\":{\"country_code\":\"44\",\"number\":\"207 946 0000\"},"
+ "\"account_holder\":{\"type\":\"individual\",\"first_name\":\"Bruce\",\"last_name\":\"Wayne\"}}}";

final PaymentRequest request = serializer.fromJson(json, PaymentRequest.class);

final RequestCardSource fallbackSource = request.getFallbackSource();
assertNotNull(fallbackSource);
assertEquals(PaymentSourceType.CARD, fallbackSource.getType());
assertEquals("4543474002249996", fallbackSource.getNumber());
assertEquals(6, fallbackSource.getExpiryMonth());
assertEquals(2030, fallbackSource.getExpiryYear());
assertEquals("Bruce Wayne", fallbackSource.getName());
assertEquals("956", fallbackSource.getCvv());
assertEquals(Boolean.TRUE, fallbackSource.getStored());
assertEquals(Boolean.FALSE, fallbackSource.getStoreForFutureUse());
assertEquals(Boolean.FALSE, fallbackSource.getAllowUpdate());
assertEquals("London", fallbackSource.getBillingAddress().getCity());
assertEquals(CountryCode.GB, fallbackSource.getBillingAddress().getCountry());
assertEquals("44", fallbackSource.getPhone().getCountryCode());
assertEquals("207 946 0000", fallbackSource.getPhone().getNumber());
assertEquals(AccountHolderType.INDIVIDUAL, fallbackSource.getAccountHolder().getType());
assertEquals("Bruce", fallbackSource.getAccountHolder().getFirstName());
}

@Test
void shouldNotSerializeNullFallbackSource() {
final PaymentRequest request = PaymentRequest.builder()
.currency(Currency.USD)
.build();

final String json = serializer.toJson(request);

assertFalse(json.contains("\"fallback_source\""));
}

@Test
void shouldSerializeAllowUpdateOnPrimaryCardSource() {
final PaymentRequest request = PaymentRequest.builder()
.currency(Currency.USD)
.source(RequestCardSource.builder()
.number("4543474002249996")
.expiryMonth(6)
.expiryYear(2030)
.allowUpdate(true)
.build())
.build();

final JsonObject json = JsonParser.parseString(serializer.toJson(request)).getAsJsonObject();

assertTrue(json.getAsJsonObject("source").get("allow_update").getAsBoolean());
}

private static RequestCardSource buildFallbackSource() {
final Address address = Address.builder()
.addressLine1("123 High St.")
.city("London")
.zip("SW1A 1AA")
.country(CountryCode.GB)
.build();
final Phone phone = Phone.builder()
.countryCode("44")
.number("207 946 0000")
.build();
return RequestCardSource.builder()
.number("4543474002249996")
.expiryMonth(6)
.expiryYear(2030)
.name("Bruce Wayne")
.cvv("956")
.stored(true)
.storeForFutureUse(false)
.billingAddress(address)
.phone(phone)
.accountHolder(AccountHolder.builder()
.type(AccountHolderType.INDIVIDUAL)
.firstName("Bruce")
.lastName("Wayne")
.build())
.allowUpdate(false)
.build();
}
}
Loading