From 6b327d4c4ae483943b723a3558c6bf3e86ab1aad Mon Sep 17 00:00:00 2001 From: rodrigojara-cko Date: Wed, 14 Oct 2026 18:55:00 +0200 Subject: [PATCH 1/5] fix(setups): align the payment setup request shape with the spec POST /payments/setups modelled several fields in a shape the API no longer accepts, so the values were dropped without an error. - card: the card data moves under card.details and token is an object. The flat accessors are deprecated and now write inside details, so existing code keeps compiling and starts sending the right keys. - order.sub_merchants: number_of_trades is not an API field; the field is number_of_sales. The numberOfTrades accessors are deprecated and delegate. - card, applepay, googlepay: add schemes and preferred_scheme. - settings: add capture and excluded_payment_methods. - payment_methods: add auto_confirm. Add metadata to the request and both responses. Add the read-only instrument.type. - PaymentMethodStatus: deprecate PENDING and AVAILABLE, which the spec does not list. --- .../entities/order/OrderSubMerchant.java | 43 ++- .../paymentMethods/PaymentMethods.java | 6 + .../paymentMethods/applepay/ApplePay.java | 22 +- .../entities/paymentMethods/card/Card.java | 246 +++++++++++++++--- .../paymentMethods/card/CardDetails.java | 87 +++++++ .../paymentMethods/card/CardToken.java | 22 ++ .../common/PaymentMethodStatus.java | 10 + .../common/PaymentSetupScheme.java | 62 +++++ .../paymentMethods/googlepay/GooglePay.java | 23 +- .../paymentMethods/instrument/Instrument.java | 8 +- .../setups/entities/settings/Settings.java | 29 ++- .../setups/requests/PaymentSetupsRequest.java | 13 +- .../PaymentSetupsConfirmResponse.java | 10 + .../responses/PaymentSetupsResponse.java | 10 + .../PaymentMethodsSerializationTest.java | 216 ++++++++++++++- ...ymentSetupsNewFieldsSerializationTest.java | 158 +++++++++++ .../schema/LocalDateFieldsRegressionTest.java | 9 +- 17 files changed, 901 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardDetails.java create mode 100644 src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardToken.java create mode 100644 src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentSetupScheme.java diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java index 7a19421e1..72bba25a3 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java @@ -17,19 +17,22 @@ public final class OrderSubMerchant { /** - * The unique identifier of the sub-merchant + * The unique identifier for the sub-merchant. + * [Optional] */ private String id; /** - * The category of products or services offered by the sub-merchant + * The product category for the sub-merchant. + * [Optional] */ private String productCategory; /** - * The number of trades or transactions the sub-merchant has conducted + * The number of orders the sub-merchant has processed. + * [Optional] */ - private Integer numberOfTrades; + private Integer numberOfSales; /** * The date the sub-merchant was registered. @@ -37,4 +40,34 @@ public final class OrderSubMerchant { * Format: yyyy-MM-dd */ private LocalDate registrationDate; -} \ No newline at end of file + + /** + * @deprecated Use {@link #getNumberOfSales()}. The API field is {@code number_of_sales}; + * {@code number_of_trades} is not part of the API and was dropped. + */ + @Deprecated + public Integer getNumberOfTrades() { + return numberOfSales; + } + + /** + * @deprecated Use {@link #setNumberOfSales(Integer)}. The API field is {@code number_of_sales}; + * {@code number_of_trades} is not part of the API and was dropped. + */ + @Deprecated + public void setNumberOfTrades(final Integer numberOfTrades) { + this.numberOfSales = numberOfTrades; + } + + public static class OrderSubMerchantBuilder { + + /** + * @deprecated Use {@link #numberOfSales(Integer)}. The API field is {@code number_of_sales}; + * {@code number_of_trades} is not part of the API and was dropped. + */ + @Deprecated + public OrderSubMerchantBuilder numberOfTrades(final Integer numberOfTrades) { + return numberOfSales(numberOfTrades); + } + } +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/PaymentMethods.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/PaymentMethods.java index 51466d726..a7eb5b070 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/PaymentMethods.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/PaymentMethods.java @@ -58,6 +58,12 @@ @AllArgsConstructor public final class PaymentMethods { + /** + * The payment method that automatically confirms the payment when it is ready, for example "card". + * [Optional] + */ + private String autoConfirm; + /** * Instrument (saved payment source) configuration. * [Optional] diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java index 82b2195bd..d4dc14bee 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java @@ -3,10 +3,13 @@ import com.checkout.common.Phone; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.CardAccountHolder; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; +import com.checkout.payments.PreferredSchema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.Instant; +import java.util.List; /** * The Apple Pay payment method's details and configuration. @@ -17,7 +20,7 @@ public final class ApplePay extends PaymentMethodBase { /** * The Apple Pay encrypted payment token data. - * [Optional] + * [Optional] writeOnly */ private ApplePayTokenData tokenData; @@ -35,8 +38,9 @@ public final class ApplePay extends PaymentMethodBase { private Instant expiresOn; /** - * Set to true if you intend to reuse the payment credentials in subsequent payments. - * [Optional] + * Set to true if you intend to reuse the payment credentials in subsequent payments. If set to + * false, a payment instrument will not be included in the payment response. + * [Optional] writeOnly */ private Boolean storeForFutureUse; @@ -51,4 +55,16 @@ public final class ApplePay extends PaymentMethodBase { * [Optional] */ private CardAccountHolder accountHolder; + + /** + * The list of supported card scheme names. + * [Optional] readOnly + */ + private List schemes; + + /** + * The preferred card scheme to process the payment. + * [Optional] + */ + private PreferredSchema preferredScheme; } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java index d2956f63a..bf309558e 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java @@ -3,100 +3,270 @@ import com.checkout.common.Phone; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.CardAccountHolder; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; +import com.checkout.payments.PreferredSchema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.Instant; +import java.util.List; /** * The Card payment method's details and configuration. + * + *

The card data lives under {@link #getDetails()}, which maps {@code card.details}. The flat + * accessors such as {@link #setNumber(String)} are deprecated: the API ignores those keys at the card + * level, so they now read and write the same values inside {@code details}.

*/ @Data @EqualsAndHashCode(callSuper = true) public final class Card extends PaymentMethodBase { /** - * The card number (without separators). Write-only. + * The Checkout.com card token to use instead of the raw card details. * [Optional] */ - private String number; + private CardToken token; /** - * The last four digits of the card number. Read-only. + * The card details. * [Optional] */ - private String last4; + private CardDetails details; /** - * The card issuer's Bank Identification Number (BIN). Read-only. - * [Optional] + * Set to true if you intend to reuse the payment credentials in subsequent payments. If set to + * false, a payment instrument will not be included in the payment response. + * [Optional] writeOnly */ - private String bin; + private Boolean storeForFutureUse; /** - * The card scheme. Read-only. + * The customer's phone number. * [Optional] */ - private String scheme; + private Phone phone; /** - * The expiry month of the card. + * The card account holder details. * [Optional] */ - private Integer expiryMonth; + private CardAccountHolder accountHolder; /** - * The expiry year of the card. - * [Optional] + * The list of supported card scheme names. + * [Optional] readOnly */ - private Integer expiryYear; + private List schemes; /** - * The cardholder's name. Write-only. + * The preferred card scheme to process the payment. * [Optional] */ - private String name; + private PreferredSchema preferredScheme; + + private CardDetails detailsOrCreate() { + if (details == null) { + details = new CardDetails(); + } + return details; + } /** - * The card verification value/code. 3 digits, except for American Express (4 digits). Write-only. - * [Optional] + * @deprecated Use {@link CardDetails#getNumber()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. */ - private String cvv; + @Deprecated + public String getNumber() { + return details == null ? null : details.getNumber(); + } /** - * Set to true for payments that use stored card details. Write-only. - * [Optional] + * @deprecated Use {@link CardDetails#setNumber(String)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. */ - private Boolean stored; + @Deprecated + public void setNumber(final String number) { + detailsOrCreate().setNumber(number); + } /** - * The time by which the card details must be confirmed. Read-only. - * [Optional] - * Format: date-time (ISO 8601) + * @deprecated Use {@link CardDetails#getLast4()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. */ - private Instant expiresOn; + @Deprecated + public String getLast4() { + return details == null ? null : details.getLast4(); + } /** - * Set to true if you intend to reuse the payment credentials in subsequent payments. Write-only. - * [Optional] + * @deprecated Use {@link CardDetails#setLast4(String)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. */ - private Boolean storeForFutureUse; + @Deprecated + public void setLast4(final String last4) { + detailsOrCreate().setLast4(last4); + } /** - * The customer's phone number. - * [Optional] + * @deprecated Use {@link CardDetails#getBin()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. */ - private Phone phone; + @Deprecated + public String getBin() { + return details == null ? null : details.getBin(); + } /** - * The card account holder details. - * [Optional] + * @deprecated Use {@link CardDetails#setBin(String)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. */ - private CardAccountHolder accountHolder; + @Deprecated + public void setBin(final String bin) { + detailsOrCreate().setBin(bin); + } /** - * Indicates whether to use the Real-Time Account Updater to update the card information. - * [Optional] + * @deprecated Use {@link CardDetails#getScheme()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public String getScheme() { + return details == null ? null : details.getScheme(); + } + + /** + * @deprecated Use {@link CardDetails#setScheme(String)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. + */ + @Deprecated + public void setScheme(final String scheme) { + detailsOrCreate().setScheme(scheme); + } + + /** + * @deprecated Use {@link CardDetails#getExpiryMonth()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public Integer getExpiryMonth() { + return details == null ? null : details.getExpiryMonth(); + } + + /** + * @deprecated Use {@link CardDetails#setExpiryMonth(Integer)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. + */ + @Deprecated + public void setExpiryMonth(final Integer expiryMonth) { + detailsOrCreate().setExpiryMonth(expiryMonth); + } + + /** + * @deprecated Use {@link CardDetails#getExpiryYear()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public Integer getExpiryYear() { + return details == null ? null : details.getExpiryYear(); + } + + /** + * @deprecated Use {@link CardDetails#setExpiryYear(Integer)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. + */ + @Deprecated + public void setExpiryYear(final Integer expiryYear) { + detailsOrCreate().setExpiryYear(expiryYear); + } + + /** + * @deprecated Use {@link CardDetails#getName()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public String getName() { + return details == null ? null : details.getName(); + } + + /** + * @deprecated Use {@link CardDetails#setName(String)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. + */ + @Deprecated + public void setName(final String name) { + detailsOrCreate().setName(name); + } + + /** + * @deprecated Use {@link CardDetails#getCvv()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public String getCvv() { + return details == null ? null : details.getCvv(); + } + + /** + * @deprecated Use {@link CardDetails#setCvv(String)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. + */ + @Deprecated + public void setCvv(final String cvv) { + detailsOrCreate().setCvv(cvv); + } + + /** + * @deprecated Use {@link CardDetails#getStored()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public Boolean getStored() { + return details == null ? null : details.getStored(); + } + + /** + * @deprecated Use {@link CardDetails#setStored(Boolean)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. + */ + @Deprecated + public void setStored(final Boolean stored) { + detailsOrCreate().setStored(stored); + } + + /** + * @deprecated Use {@link CardDetails#getExpiresOn()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public Instant getExpiresOn() { + return details == null ? null : details.getExpiresOn(); + } + + /** + * @deprecated Use {@link CardDetails#setExpiresOn(Instant)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. + */ + @Deprecated + public void setExpiresOn(final Instant expiresOn) { + detailsOrCreate().setExpiresOn(expiresOn); + } + + /** + * @deprecated Use {@link CardDetails#getAllowUpdate()} through {@link #getDetails()}. The API + * expects this value under {@code card.details} and drops it at the card level. + */ + @Deprecated + public Boolean getAllowUpdate() { + return details == null ? null : details.getAllowUpdate(); + } + + /** + * @deprecated Use {@link CardDetails#setAllowUpdate(Boolean)} through {@link #setDetails(CardDetails)}. + * The API expects this value under {@code card.details}; this setter now writes it there. */ - private Boolean allowUpdate; + @Deprecated + public void setAllowUpdate(final Boolean allowUpdate) { + detailsOrCreate().setAllowUpdate(allowUpdate); + } } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardDetails.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardDetails.java new file mode 100644 index 000000000..74ab53f80 --- /dev/null +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardDetails.java @@ -0,0 +1,87 @@ +package com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +/** + * The card details of the card payment method. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class CardDetails { + + /** + * The card number (without separators). + * [Optional] writeOnly + */ + private String number; + + /** + * The last four digits of the card number. + * [Optional] readOnly + */ + private String last4; + + /** + * The card issuer's Bank Identification Number (BIN). + * [Optional] readOnly + */ + private String bin; + + /** + * The card scheme. + * [Optional] readOnly + */ + private String scheme; + + /** + * The expiry month of the card. + * [Optional] + */ + private Integer expiryMonth; + + /** + * The expiry year of the card. + * [Optional] + */ + private Integer expiryYear; + + /** + * The cardholder's name. + * [Optional] writeOnly + */ + private String name; + + /** + * The card verification value/code. 3 digits, except for American Express (4 digits). + * [Optional] writeOnly + */ + private String cvv; + + /** + * 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] writeOnly + */ + private Boolean stored; + + /** + * The time by which the card details must be confirmed. + * [Optional] readOnly + * Format: date-time (ISO 8601) + */ + private Instant expiresOn; + + /** + * Indicates whether to use the Real-Time Account Updater to update the card information. + * [Optional] + */ + private Boolean allowUpdate; +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardToken.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardToken.java new file mode 100644 index 000000000..248bc0775 --- /dev/null +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/CardToken.java @@ -0,0 +1,22 @@ +package com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * The Checkout.com card token to use for the card payment method. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class CardToken { + + /** + * The Checkout.com card token identifier. + * [Optional] + */ + private String id; +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentMethodStatus.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentMethodStatus.java index f4d597f81..e4dd4ac61 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentMethodStatus.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentMethodStatus.java @@ -22,7 +22,12 @@ public enum PaymentMethodStatus { /** * Not defined by the current API specification. Kept for backward compatibility. + * + * @deprecated not part of the PaymentMethodStatus enum in the API specification, which lists + * unavailable, action_required, ready, initialization_required and invalid. It will be + * removed in the next major version. */ + @Deprecated @SerializedName("pending") PENDING, @@ -34,7 +39,12 @@ public enum PaymentMethodStatus { /** * Not defined by the current API specification. Kept for backward compatibility. + * + * @deprecated not part of the PaymentMethodStatus enum in the API specification, which lists + * unavailable, action_required, ready, initialization_required and invalid. It will be + * removed in the next major version. */ + @Deprecated @SerializedName("available") AVAILABLE, diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentSetupScheme.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentSetupScheme.java new file mode 100644 index 000000000..d759ccc00 --- /dev/null +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/PaymentSetupScheme.java @@ -0,0 +1,62 @@ +package com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common; + +import com.google.gson.annotations.SerializedName; + +/** + * A card scheme name, as listed in the schemes of a Payment Setup card, Apple Pay or Google Pay + * payment method. + */ +public enum PaymentSetupScheme { + + @SerializedName("accel") + ACCEL, + + @SerializedName("amex") + AMEX, + + @SerializedName("cartes_bancaires") + CARTES_BANCAIRES, + + @SerializedName("china_union_pay") + CHINA_UNION_PAY, + + @SerializedName("diners") + DINERS, + + @SerializedName("discover") + DISCOVER, + + @SerializedName("jcb") + JCB, + + @SerializedName("mada") + MADA, + + @SerializedName("maestro") + MAESTRO, + + @SerializedName("mastercard") + MASTERCARD, + + @SerializedName("nyce") + NYCE, + + @SerializedName("omannet") + OMANNET, + + @SerializedName("pulse") + PULSE, + + @SerializedName("shazam") + SHAZAM, + + @SerializedName("star") + STAR, + + @SerializedName("upi") + UPI, + + @SerializedName("visa") + VISA + +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java index abdc97375..08bf3ab90 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java @@ -3,10 +3,13 @@ import com.checkout.common.Phone; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.CardAccountHolder; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; +import com.checkout.payments.PreferredSchema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.Instant; +import java.util.List; /** * The Google Pay payment method's details and configuration. @@ -17,7 +20,7 @@ public final class GooglePay extends PaymentMethodBase { /** * The Google Pay encrypted payment token data. - * [Optional] + * [Optional] writeOnly */ private GooglePayTokenData tokenData; @@ -35,8 +38,9 @@ public final class GooglePay extends PaymentMethodBase { private Instant expiresOn; /** - * Set to true if you intend to reuse the payment credentials in subsequent payments. - * [Optional] + * Set to true if you intend to reuse the payment credentials in subsequent payments. If set to + * false, a payment instrument will not be included in the payment response. + * [Optional] writeOnly */ private Boolean storeForFutureUse; @@ -51,4 +55,17 @@ public final class GooglePay extends PaymentMethodBase { * [Optional] */ private CardAccountHolder accountHolder; + + /** + * The list of supported card scheme names. + * [Optional] readOnly + */ + private List schemes; + + /** + * The preferred card scheme to process the payment. + * Google Pay accepts only VISA and MASTERCARD. + * [Optional] + */ + private PreferredSchema preferredScheme; } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/instrument/Instrument.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/instrument/Instrument.java index d04e0c15e..332a3ab69 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/instrument/Instrument.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/instrument/Instrument.java @@ -18,9 +18,15 @@ public final class Instrument extends PaymentMethodBase { */ private String id; + /** + * The type of the instrument, for example "bacs". + * [Optional] readOnly + */ + private String type; + /** * The next available action for the payment method. - * [Optional] + * [Optional] readOnly */ private PaymentMethodAction action; } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/settings/Settings.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/settings/Settings.java index d367c16e2..3a31ea0dd 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/settings/Settings.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/settings/Settings.java @@ -5,8 +5,10 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + /** - * Payment setup configuration settings + * Settings for the Payment Setup. */ @Data @Builder @@ -15,16 +17,33 @@ public final class Settings { /** - * <= 255 characters * The URL to redirect the customer to, if the payment is successful. * For payment methods with a redirect, this value overrides the default success redirect URL configured on your account. + * [Optional] + * Format: uri + * max 255 characters */ private String successUrl; /** - * <= 255 characters * The URL to redirect the customer to, if the payment is unsuccessful. - * For payment methods with a redirect, this value overrides the default failure redirect URL configured on your account. + * For payment methods with a redirect, this value overrides the default failure redirect URL configured on your account. + * [Optional] + * Format: uri + * max 255 characters */ private String failureUrl; -} \ No newline at end of file + + /** + * Indicates whether to capture the payment immediately. + * [Optional] + * Default: true + */ + private Boolean capture; + + /** + * The list of payment methods excluded from the Payment Setup, for example "card", "klarna" or "stcpay". + * [Optional] + */ + private List excludedPaymentMethods; +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java index d9c73db1b..28106da0f 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java @@ -18,6 +18,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.Map; + /** * Payment setup request */ @@ -133,4 +135,13 @@ public final class PaymentSetupsRequest { * [Optional] */ private PaymentSetupRisk risk; -} \ No newline at end of file + + /** + * A set of key/value pairs you can attach to a payment. It can be useful for storing additional + * information about the payment. + * Each value is a string (max 255 characters), a number or a boolean. + * [Optional] + * max 20 properties + */ + private Map metadata; +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java index 1b6726380..36d90a363 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java @@ -15,6 +15,7 @@ import com.checkout.payments.PaymentType; import java.util.List; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -149,4 +150,13 @@ public final class PaymentSetupsConfirmResponse extends Resource { * [Optional] */ private PaymentSetupTerminal terminal; + + /** + * A set of key/value pairs you can attach to a payment. It can be useful for storing additional + * information about the payment. + * Each value is a string (max 255 characters), a number or a boolean. + * [Optional] + * max 20 properties + */ + private Map metadata; } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java index fd8e4cf85..655ab5c62 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java @@ -16,6 +16,7 @@ import com.checkout.payments.PaymentType; import java.util.List; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -156,4 +157,13 @@ public final class PaymentSetupsResponse extends Resource { * [Optional] */ private PaymentSetupRisk risk; + + /** + * A set of key/value pairs you can attach to a payment. It can be useful for storing additional + * information about the payment. + * Each value is a string (max 255 characters), a number or a boolean. + * [Optional] + * max 20 properties + */ + private Map metadata; } diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java index 0951d5827..e50520c1a 100644 --- a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java @@ -3,19 +3,28 @@ import com.checkout.GsonSerializer; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.PaymentMethods; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.bizum.Bizum; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.Card; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.CardDetails; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.CardToken; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodAction; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodInitialization; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodStatus; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.instrument.Instrument; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.klarna.Klarna; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.paypal.PayPal; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.paypal.PayPalShippingPreference; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.paypal.PayPalUserAction; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.stcpay.Stcpay; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.tabby.Tabby; +import com.checkout.payments.PreferredSchema; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.junit.jupiter.api.Test; import java.lang.reflect.Field; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; @@ -89,7 +98,7 @@ void shouldRoundTripKlarnaWithAllProperties() { @Test void shouldDeserializeStcpayWithOtpAction() { final String json = "{" - + "\"status\":\"pending\"," + + "\"status\":\"action_required\"," + "\"flags\":[\"stc_flag\"]," + "\"initialization\":\"enabled\"," + "\"otp\":\"123456\"," @@ -99,7 +108,7 @@ void shouldDeserializeStcpayWithOtpAction() { final Stcpay stcpay = serializer.fromJson(json, Stcpay.class); assertNotNull(stcpay); - assertEquals(PaymentMethodStatus.PENDING, stcpay.getStatus()); + assertEquals(PaymentMethodStatus.ACTION_REQUIRED, stcpay.getStatus()); assertEquals(Collections.singletonList("stc_flag"), stcpay.getFlags()); assertEquals(PaymentMethodInitialization.ENABLED, stcpay.getInitialization()); assertEquals("123456", stcpay.getOtp()); @@ -113,7 +122,7 @@ void shouldDeserializeStcpayWithOtpAction() { @Test void shouldSerializeStcpayOtp() { final Stcpay stcpay = new Stcpay(); - stcpay.setStatus(PaymentMethodStatus.AVAILABLE); + stcpay.setStatus(PaymentMethodStatus.READY); stcpay.setFlags(Collections.emptyList()); stcpay.setInitialization(PaymentMethodInitialization.DISABLED); stcpay.setOtp("654321"); @@ -127,7 +136,7 @@ void shouldSerializeStcpayOtp() { @Test void shouldRoundTripStcpayWithAllProperties() { final Stcpay original = new Stcpay(); - original.setStatus(PaymentMethodStatus.AVAILABLE); + original.setStatus(PaymentMethodStatus.INITIALIZATION_REQUIRED); original.setFlags(Collections.singletonList("f")); original.setInitialization(PaymentMethodInitialization.DISABLED); original.setOtp("111222"); @@ -184,7 +193,7 @@ void shouldRoundTripTabbyWithAllProperties() { @Test void shouldDeserializePayPalWithAction() { final String json = "{" - + "\"status\":\"available\"," + + "\"status\":\"ready\"," + "\"flags\":[\"paypal_flag\"]," + "\"initialization\":\"enabled\"," + "\"user_action\":\"pay_now\"," @@ -199,7 +208,7 @@ void shouldDeserializePayPalWithAction() { final PayPal paypal = serializer.fromJson(json, PayPal.class); assertNotNull(paypal); - assertEquals(PaymentMethodStatus.AVAILABLE, paypal.getStatus()); + assertEquals(PaymentMethodStatus.READY, paypal.getStatus()); assertEquals(Collections.singletonList("paypal_flag"), paypal.getFlags()); assertEquals(PaymentMethodInitialization.ENABLED, paypal.getInitialization()); assertEquals(PayPalUserAction.PAY_NOW, paypal.getUserAction()); @@ -215,7 +224,7 @@ void shouldDeserializePayPalWithAction() { @Test void shouldRoundTripPayPalWithAllProperties() { final PayPal original = new PayPal(); - original.setStatus(PaymentMethodStatus.PENDING); + original.setStatus(PaymentMethodStatus.INVALID); original.setFlags(Collections.singletonList("p")); original.setInitialization(PaymentMethodInitialization.ENABLED); original.setUserAction(PayPalUserAction.CONTINUE); @@ -281,7 +290,7 @@ void shouldSerializePaymentMethodsContainer() { klarna.setInitialization(PaymentMethodInitialization.ENABLED); final PayPal paypal = new PayPal(); - paypal.setStatus(PaymentMethodStatus.AVAILABLE); + paypal.setStatus(PaymentMethodStatus.READY); paypal.setFlags(Collections.emptyList()); paypal.setInitialization(PaymentMethodInitialization.DISABLED); paypal.setBrandName("Shop"); @@ -296,7 +305,6 @@ void shouldSerializePaymentMethodsContainer() { assertTrue(json.contains("\"klarna\"")); assertTrue(json.contains("\"paypal\"")); assertTrue(json.contains("\"ready\"")); - assertTrue(json.contains("\"available\"")); assertTrue(json.contains("Shop")); } @@ -345,7 +353,7 @@ void shouldDeserializeFullPaymentMethodsFromSwaggerExample() { + "}" + "}," + "\"stcpay\":{" - + "\"status\":\"pending\"," + + "\"status\":\"action_required\"," + "\"flags\":[]," + "\"initialization\":\"disabled\"," + "\"action\":{\"type\":\"otp\"}" @@ -357,7 +365,7 @@ void shouldDeserializeFullPaymentMethodsFromSwaggerExample() { + "\"payment_types\":[\"pay_later\"]" + "}," + "\"paypal\":{" - + "\"status\":\"available\"," + + "\"status\":\"ready\"," + "\"flags\":[]," + "\"initialization\":\"enabled\"," + "\"user_action\":\"pay_now\"," @@ -443,16 +451,34 @@ void shouldRoundTripPaymentMethodActionWithAllProperties() { assertTrue(json.contains("\"order_id\"")); } + /** + * PENDING and AVAILABLE are deprecated (not in the PaymentMethodStatus enum of the spec) but still + * deserialize, so a response carrying them does not break existing integrations. + */ @Test + @SuppressWarnings("deprecation") void shouldDeserializeAllPaymentMethodStatusWireValues() { assertEquals(PaymentMethodStatus.UNAVAILABLE, serializer.fromJson("{\"status\":\"unavailable\"}", Klarna.class).getStatus()); assertEquals(PaymentMethodStatus.ACTION_REQUIRED, serializer.fromJson("{\"status\":\"action_required\"}", Klarna.class).getStatus()); assertEquals(PaymentMethodStatus.PENDING, serializer.fromJson("{\"status\":\"pending\"}", Klarna.class).getStatus()); assertEquals(PaymentMethodStatus.READY, serializer.fromJson("{\"status\":\"ready\"}", Klarna.class).getStatus()); assertEquals(PaymentMethodStatus.AVAILABLE, serializer.fromJson("{\"status\":\"available\"}", Klarna.class).getStatus()); + assertEquals(PaymentMethodStatus.INITIALIZATION_REQUIRED, serializer.fromJson("{\"status\":\"initialization_required\"}", Klarna.class).getStatus()); + assertEquals(PaymentMethodStatus.INVALID, serializer.fromJson("{\"status\":\"invalid\"}", Klarna.class).getStatus()); + } + + @Test + @SuppressWarnings("deprecation") + void shouldMarkOnlyTheStatusValuesOutsideTheSpecAsDeprecated() throws NoSuchFieldException { + for (final PaymentMethodStatus status : PaymentMethodStatus.values()) { + final boolean deprecated = PaymentMethodStatus.class.getField(status.name()).isAnnotationPresent(Deprecated.class); + final boolean inSpec = status != PaymentMethodStatus.PENDING && status != PaymentMethodStatus.AVAILABLE; + assertEquals(!inSpec, deprecated, status.name()); + } } @Test + @SuppressWarnings("deprecation") void shouldSerializeAllPaymentMethodStatusValues() { for (final PaymentMethodStatus status : PaymentMethodStatus.values()) { final Klarna k = new Klarna(); @@ -496,6 +522,170 @@ void shouldRoundTripPaymentMethodInitialization() { } } + // ------------------------------------------------------------------------ + // Card + // PaymentSetup.payment_methods.card: the card data lives under details, + // token is an object, plus schemes and preferred_scheme. The flat + // accessors are deprecated and now write inside details. + // ------------------------------------------------------------------------ + + @Test + void shouldRoundTripCardWithEveryProperty() { + final Card original = buildSampleCard(); + + final String json = serializer.toJson(original); + final JsonObject card = JsonParser.parseString(json).getAsJsonObject(); + final JsonObject details = card.getAsJsonObject("details"); + + assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", card.getAsJsonObject("token").get("id").getAsString()); + assertEquals(11, details.size()); + assertEquals("4242424242424242", details.get("number").getAsString()); + assertEquals("4242", details.get("last4").getAsString()); + assertEquals("424242", details.get("bin").getAsString()); + assertEquals("visa", details.get("scheme").getAsString()); + assertEquals(12, details.get("expiry_month").getAsInt()); + assertEquals(2030, details.get("expiry_year").getAsInt()); + assertEquals("John Smith", details.get("name").getAsString()); + assertEquals("100", details.get("cvv").getAsString()); + assertTrue(details.get("stored").getAsBoolean()); + assertEquals("2030-01-01T10:00:00Z", details.get("expires_on").getAsString()); + assertTrue(details.get("allow_update").getAsBoolean()); + assertTrue(card.get("store_for_future_use").getAsBoolean()); + assertEquals("cartes_bancaires", card.getAsJsonArray("schemes").get(1).getAsString()); + assertEquals("china_union_pay", card.getAsJsonArray("schemes").get(2).getAsString()); + assertEquals("cartes_bancaires", card.get("preferred_scheme").getAsString()); + assertFalse(card.has("number")); + assertFalse(card.has("cvv")); + assertFalse(card.has("expiry_month")); + assertEquals(original, serializer.fromJson(json, Card.class)); + } + + @Test + void shouldDeserializeCardSwaggerExample() { + final String json = "{" + + "\"status\":\"ready\"," + + "\"flags\":[]," + + "\"token\":{\"id\":\"tok_ubfj2q76miwundwlk72vxt2i7q\"}," + + "\"details\":{\"last4\":\"4242\",\"bin\":\"424242\",\"scheme\":\"visa\"," + + "\"expiry_month\":12,\"expiry_year\":2030,\"expires_on\":\"2030-01-01T10:00:00Z\"," + + "\"allow_update\":true}," + + "\"schemes\":[\"visa\",\"mastercard\"]," + + "\"preferred_scheme\":\"visa\"" + + "}"; + + final Card card = serializer.fromJson(json, Card.class); + + assertEquals(PaymentMethodStatus.READY, card.getStatus()); + assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", card.getToken().getId()); + assertEquals("4242", card.getDetails().getLast4()); + assertEquals("424242", card.getDetails().getBin()); + assertEquals("visa", card.getDetails().getScheme()); + assertEquals(Integer.valueOf(12), card.getDetails().getExpiryMonth()); + assertEquals(Integer.valueOf(2030), card.getDetails().getExpiryYear()); + assertEquals(Instant.parse("2030-01-01T10:00:00Z"), card.getDetails().getExpiresOn()); + assertEquals(Boolean.TRUE, card.getDetails().getAllowUpdate()); + assertEquals(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.MASTERCARD), card.getSchemes()); + assertEquals(PreferredSchema.VISA, card.getPreferredScheme()); + } + + @Test + void shouldSerializeEveryPaymentSetupSchemeValue() { + for (final PaymentSetupScheme scheme : PaymentSetupScheme.values()) { + final Card card = new Card(); + card.setSchemes(Collections.singletonList(scheme)); + final JsonObject json = JsonParser.parseString(serializer.toJson(card)).getAsJsonObject(); + assertEquals(scheme.name().toLowerCase(), json.getAsJsonArray("schemes").get(0).getAsString()); + assertEquals(scheme, serializer.fromJson(serializer.toJson(card), Card.class).getSchemes().get(0)); + } + assertEquals(17, PaymentSetupScheme.values().length); + } + + @Test + @SuppressWarnings("deprecation") + void shouldWriteUnderDetailsThroughTheDeprecatedFlatCardAccessors() { + final Card card = new Card(); + card.setNumber("4242424242424242"); + card.setLast4("4242"); + card.setBin("424242"); + card.setScheme("visa"); + card.setExpiryMonth(12); + card.setExpiryYear(2030); + card.setName("John Smith"); + card.setCvv("100"); + card.setStored(true); + card.setExpiresOn(Instant.parse("2030-01-01T10:00:00Z")); + card.setAllowUpdate(true); + + final JsonObject json = JsonParser.parseString(serializer.toJson(card)).getAsJsonObject(); + + assertEquals(1, json.size()); + assertEquals(buildSampleCard().getDetails(), card.getDetails()); + assertEquals(11, json.getAsJsonObject("details").size()); + assertEquals("4242424242424242", card.getNumber()); + assertEquals("100", card.getCvv()); + assertEquals(Instant.parse("2030-01-01T10:00:00Z"), card.getExpiresOn()); + assertNull(new Card().getNumber()); + } + + // ------------------------------------------------------------------------ + // Instrument + // PaymentSetupInstrument: id, the read-only type and action. + // ------------------------------------------------------------------------ + + @Test + void shouldRoundTripInstrumentWithEveryProperty() { + final Instrument original = new Instrument(); + original.setStatus(PaymentMethodStatus.READY); + original.setFlags(Collections.emptyList()); + original.setId("src_wmlfc3zttb4uzmk6snpwb43jbi"); + original.setType("bacs"); + original.setAction(PaymentMethodAction.builder().type("set_instrument").build()); + + final String json = serializer.toJson(original); + final JsonObject instrument = JsonParser.parseString(json).getAsJsonObject(); + + assertEquals("src_wmlfc3zttb4uzmk6snpwb43jbi", instrument.get("id").getAsString()); + assertEquals("bacs", instrument.get("type").getAsString()); + assertEquals("set_instrument", instrument.getAsJsonObject("action").get("type").getAsString()); + assertEquals(original, serializer.fromJson(json, Instrument.class)); + } + + @Test + void shouldDeserializeInstrumentSwaggerExample() { + final String json = "{\"status\":\"ready\",\"flags\":[],\"id\":\"src_wmlfc3zttb4uzmk6snpwb43jbi\"," + + "\"type\":\"bacs\",\"action\":{\"type\":\"set_instrument\"}}"; + + final Instrument instrument = serializer.fromJson(json, Instrument.class); + + assertEquals("src_wmlfc3zttb4uzmk6snpwb43jbi", instrument.getId()); + assertEquals("bacs", instrument.getType()); + assertEquals("set_instrument", instrument.getAction().getType()); + } + + private static Card buildSampleCard() { + final Card c = new Card(); + c.setStatus(PaymentMethodStatus.READY); + c.setFlags(Collections.emptyList()); + c.setToken(CardToken.builder().id("tok_ubfj2q76miwundwlk72vxt2i7q").build()); + c.setDetails(CardDetails.builder() + .number("4242424242424242") + .last4("4242") + .bin("424242") + .scheme("visa") + .expiryMonth(12) + .expiryYear(2030) + .name("John Smith") + .cvv("100") + .stored(true) + .expiresOn(Instant.parse("2030-01-01T10:00:00Z")) + .allowUpdate(true) + .build()); + c.setStoreForFutureUse(true); + c.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.CARTES_BANCAIRES, PaymentSetupScheme.CHINA_UNION_PAY)); + c.setPreferredScheme(PreferredSchema.CARTES_BANCAIRES); + return c; + } + private static Klarna buildSampleKlarna() { final Klarna k = new Klarna(); k.setStatus(PaymentMethodStatus.READY); @@ -507,7 +697,7 @@ private static Klarna buildSampleKlarna() { private static Stcpay buildSampleStcpay() { final Stcpay s = new Stcpay(); - s.setStatus(PaymentMethodStatus.PENDING); + s.setStatus(PaymentMethodStatus.INITIALIZATION_REQUIRED); s.setFlags(Collections.emptyList()); s.setInitialization(PaymentMethodInitialization.DISABLED); s.setOtp("999888"); @@ -517,7 +707,7 @@ private static Stcpay buildSampleStcpay() { private static Tabby buildSampleTabby() { final Tabby t = new Tabby(); - t.setStatus(PaymentMethodStatus.AVAILABLE); + t.setStatus(PaymentMethodStatus.INVALID); t.setFlags(Collections.singletonList("tf")); t.setInitialization(PaymentMethodInitialization.ENABLED); t.setPaymentTypes(Collections.singletonList("pay_later")); diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java index e2b1b5833..afe81722b 100644 --- a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java @@ -25,11 +25,19 @@ import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.stablecoin.Stablecoin; import com.checkout.handlepaymentsandpayouts.setups.entities.presentmentDetails.PaymentSetupPresentmentDetails; import com.checkout.handlepaymentsandpayouts.setups.entities.terminal.PaymentSetupTerminal; +import com.checkout.handlepaymentsandpayouts.setups.entities.order.OrderSubMerchant; +import com.checkout.handlepaymentsandpayouts.setups.entities.settings.Settings; +import com.checkout.handlepaymentsandpayouts.setups.requests.PaymentSetupsRequest; +import com.checkout.handlepaymentsandpayouts.setups.responses.PaymentSetupsConfirmResponse; +import com.checkout.handlepaymentsandpayouts.setups.responses.PaymentSetupsResponse; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.junit.jupiter.api.Test; import java.time.LocalDate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -211,4 +219,154 @@ void shouldDeserializeCustomerSwaggerExample() { assertEquals("207 946 0000", customer.getPhone().getNumber()); assertEquals("en_GB", customer.getDevice().getLocale()); } + + // ------------------------------------------------------------------------ + // Settings + // PaymentSetup.settings: all 4 properties. capture and + // excluded_payment_methods were missing from the model. + // ------------------------------------------------------------------------ + + @Test + void shouldRoundTripSettingsWithEveryProperty() { + final Settings original = Settings.builder() + .successUrl("https://example.com/payments/success") + .failureUrl("https://example.com/payments/fail") + .capture(false) + .excludedPaymentMethods(Arrays.asList("card", "klarna", "stcpay")) + .build(); + + final String json = serializer.toJson(original); + final JsonObject settings = JsonParser.parseString(json).getAsJsonObject(); + + assertEquals(4, settings.size()); + assertEquals("https://example.com/payments/success", settings.get("success_url").getAsString()); + assertEquals("https://example.com/payments/fail", settings.get("failure_url").getAsString()); + assertFalse(settings.get("capture").getAsBoolean()); + assertEquals(3, settings.getAsJsonArray("excluded_payment_methods").size()); + assertEquals("klarna", settings.getAsJsonArray("excluded_payment_methods").get(1).getAsString()); + assertEquals(original, serializer.fromJson(json, Settings.class)); + } + + @Test + void shouldDeserializeSettingsSwaggerExample() { + final String json = "{\"success_url\":\"https://example.com/payments/success\"," + + "\"failure_url\":\"https://example.com/payments/fail\"," + + "\"capture\":true," + + "\"excluded_payment_methods\":[\"card\",\"klarna\",\"stcpay\"]}"; + + final Settings settings = serializer.fromJson(json, Settings.class); + + assertEquals("https://example.com/payments/success", settings.getSuccessUrl()); + assertEquals("https://example.com/payments/fail", settings.getFailureUrl()); + assertEquals(Boolean.TRUE, settings.getCapture()); + assertEquals(Arrays.asList("card", "klarna", "stcpay"), settings.getExcludedPaymentMethods()); + } + + // ------------------------------------------------------------------------ + // Metadata and auto_confirm + // PaymentSetup.metadata (top level, request and response) and + // PaymentSetup.payment_methods.auto_confirm, both missing from the model. + // ------------------------------------------------------------------------ + + @Test + void shouldSerializeMetadataAndAutoConfirmOnTheRequest() { + final Map metadata = new HashMap<>(); + metadata.put("coupon_code", "NY2018"); + metadata.put("partner_id", 123989.5); + metadata.put("vip", true); + final PaymentSetupsRequest original = PaymentSetupsRequest.builder() + .processingChannelId("pc_q4dbxom5jbgudnjzjpz7j2z6uq") + .amount(1000L) + .currency(Currency.GBP) + .paymentMethods(PaymentMethods.builder().autoConfirm("card").build()) + .metadata(metadata) + .build(); + + final String json = serializer.toJson(original); + final JsonObject request = JsonParser.parseString(json).getAsJsonObject(); + + assertEquals("card", request.getAsJsonObject("payment_methods").get("auto_confirm").getAsString()); + assertFalse(request.getAsJsonObject("payment_methods").has("autoConfirm")); + assertEquals("NY2018", request.getAsJsonObject("metadata").get("coupon_code").getAsString()); + assertEquals(123989.5, request.getAsJsonObject("metadata").get("partner_id").getAsDouble()); + assertTrue(request.getAsJsonObject("metadata").get("vip").getAsBoolean()); + + final PaymentSetupsRequest restored = serializer.fromJson(json, PaymentSetupsRequest.class); + assertEquals("card", restored.getPaymentMethods().getAutoConfirm()); + assertEquals(metadata, restored.getMetadata()); + } + + @Test + void shouldDeserializeMetadataAndAutoConfirmOnTheResponses() { + final String json = "{\"id\":\"ps_2Un2jYAVxLsfUfpOvLhEHRW8Ft1\"," + + "\"payment_methods\":{\"auto_confirm\":\"card\"}," + + "\"metadata\":{\"coupon_code\":\"NY2018\"}}"; + + final PaymentSetupsResponse response = serializer.fromJson(json, PaymentSetupsResponse.class); + final PaymentSetupsConfirmResponse confirm = serializer.fromJson(json, PaymentSetupsConfirmResponse.class); + + assertEquals("card", response.getPaymentMethods().getAutoConfirm()); + assertEquals("NY2018", response.getMetadata().get("coupon_code")); + assertEquals("card", confirm.getPaymentMethods().getAutoConfirm()); + assertEquals("NY2018", confirm.getMetadata().get("coupon_code")); + } + + // ------------------------------------------------------------------------ + // OrderSubMerchant + // PaymentSetupSubMerchant.number_of_sales. The model sent number_of_trades, + // a key the API does not define; the old accessors are deprecated and now + // write number_of_sales. + // ------------------------------------------------------------------------ + + @Test + void shouldRoundTripOrderSubMerchantWithEveryProperty() { + final OrderSubMerchant original = OrderSubMerchant.builder() + .id("SUB12345") + .productCategory("Electronics") + .numberOfSales(500) + .registrationDate(LocalDate.of(2023, 1, 15)) + .build(); + + final String json = serializer.toJson(original); + final JsonObject subMerchant = JsonParser.parseString(json).getAsJsonObject(); + + assertEquals(4, subMerchant.size()); + assertEquals("SUB12345", subMerchant.get("id").getAsString()); + assertEquals("Electronics", subMerchant.get("product_category").getAsString()); + assertEquals(500, subMerchant.get("number_of_sales").getAsInt()); + assertFalse(subMerchant.has("number_of_trades")); + assertEquals("2023-01-15", subMerchant.get("registration_date").getAsString()); + assertEquals(original, serializer.fromJson(json, OrderSubMerchant.class)); + } + + @Test + void shouldDeserializeOrderSubMerchantSwaggerExample() { + final String json = "{\"id\":\"SUB12345\",\"product_category\":\"Electronics\"," + + "\"number_of_sales\":500,\"registration_date\":\"2023-01-15\"}"; + + final OrderSubMerchant subMerchant = serializer.fromJson(json, OrderSubMerchant.class); + + assertEquals("SUB12345", subMerchant.getId()); + assertEquals("Electronics", subMerchant.getProductCategory()); + assertEquals(Integer.valueOf(500), subMerchant.getNumberOfSales()); + assertEquals(LocalDate.of(2023, 1, 15), subMerchant.getRegistrationDate()); + } + + @Test + @SuppressWarnings("deprecation") + void shouldSendNumberOfSalesThroughTheDeprecatedNumberOfTradesAccessors() { + final OrderSubMerchant fromBuilder = OrderSubMerchant.builder().numberOfTrades(42).build(); + final OrderSubMerchant fromSetter = new OrderSubMerchant(); + fromSetter.setNumberOfTrades(7); + + final JsonObject builderJson = JsonParser.parseString(serializer.toJson(fromBuilder)).getAsJsonObject(); + final JsonObject setterJson = JsonParser.parseString(serializer.toJson(fromSetter)).getAsJsonObject(); + + assertEquals(42, builderJson.get("number_of_sales").getAsInt()); + assertFalse(builderJson.has("number_of_trades")); + assertEquals(7, setterJson.get("number_of_sales").getAsInt()); + assertFalse(setterJson.has("number_of_trades")); + assertEquals(Integer.valueOf(42), fromBuilder.getNumberOfTrades()); + assertEquals(Integer.valueOf(42), fromBuilder.getNumberOfSales()); + } } diff --git a/src/test/java/com/checkout/schema/LocalDateFieldsRegressionTest.java b/src/test/java/com/checkout/schema/LocalDateFieldsRegressionTest.java index 20743951e..83229a925 100644 --- a/src/test/java/com/checkout/schema/LocalDateFieldsRegressionTest.java +++ b/src/test/java/com/checkout/schema/LocalDateFieldsRegressionTest.java @@ -327,7 +327,7 @@ void orderSubMerchant_allProperties_roundTrip() { OrderSubMerchant original = OrderSubMerchant.builder() .id("SUB-FULL-001") .productCategory("Electronics & Gadgets") - .numberOfTrades(250) + .numberOfSales(250) .registrationDate(LocalDate.of(2021, 8, 22)) .build(); @@ -337,7 +337,8 @@ void orderSubMerchant_allProperties_roundTrip() { assertNotNull(deserialized); assertEquals("SUB-FULL-001", deserialized.getId()); assertEquals("Electronics & Gadgets", deserialized.getProductCategory()); - assertEquals(250, deserialized.getNumberOfTrades()); + assertEquals(250, deserialized.getNumberOfSales()); + assertFalse(json.contains("number_of_trades"), "number_of_trades is not an API field: " + json); assertEquals(LocalDate.of(2021, 8, 22), deserialized.getRegistrationDate()); } @@ -357,14 +358,14 @@ void orderSubMerchant_registrationDate_shouldSerializeAsDateOnly() { @Test void orderSubMerchant_registrationDate_shouldDeserializeFromSwaggerExample() { - String json = "{\"id\":\"SUB-123\",\"product_category\":\"Fashion\",\"number_of_trades\":50,\"registration_date\":\"2023-01-15\"}"; + String json = "{\"id\":\"SUB-123\",\"product_category\":\"Fashion\",\"number_of_sales\":50,\"registration_date\":\"2023-01-15\"}"; OrderSubMerchant subMerchant = serializer.fromJson(json, OrderSubMerchant.class); assertNotNull(subMerchant); assertEquals("SUB-123", subMerchant.getId()); assertEquals("Fashion", subMerchant.getProductCategory()); - assertEquals(50, subMerchant.getNumberOfTrades()); + assertEquals(50, subMerchant.getNumberOfSales()); assertEquals(LocalDate.of(2023, 1, 15), subMerchant.getRegistrationDate()); } } From 5a317962b0396ab90fd6ee993d14611a68c6f7ae Mon Sep 17 00:00:00 2001 From: rodrigojara-cko Date: Wed, 14 Oct 2026 19:28:21 +0200 Subject: [PATCH 2/5] fix(setups): send the apple pay and google pay token as an object The spec types payment_methods.applepay.token and googlepay.token as an object {id, expires_on}. The SDK modelled token as a string with expires_on next to it, a shape the API does not accept. token is now a WalletToken. The flat expiresOn accessors are deprecated and write inside token. This is source-breaking for code that reads or writes token as a String. That code could not have produced a valid request, since the API expects an object, so nothing that worked stops working. Breaking because the API demands it, so a minor release, to be called out in the release notes. --- .../paymentMethods/applepay/ApplePay.java | 33 +++-- .../paymentMethods/common/WalletToken.java | 32 +++++ .../paymentMethods/googlepay/GooglePay.java | 33 +++-- .../PaymentMethodsSerializationTest.java | 119 ++++++++++++++++++ 4 files changed, 199 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/WalletToken.java diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java index d4dc14bee..e3c9105a7 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java @@ -4,6 +4,7 @@ import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.CardAccountHolder; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.WalletToken; import com.checkout.payments.PreferredSchema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -25,17 +26,10 @@ public final class ApplePay extends PaymentMethodBase { private ApplePayTokenData tokenData; /** - * The Apple Pay payment token string. + * The Checkout.com Apple Pay token. * [Optional] */ - private String token; - - /** - * The date and time the token expires. - * [Optional] - * Format: date-time (ISO 8601) - */ - private Instant expiresOn; + private WalletToken token; /** * Set to true if you intend to reuse the payment credentials in subsequent payments. If set to @@ -67,4 +61,25 @@ public final class ApplePay extends PaymentMethodBase { * [Optional] */ private PreferredSchema preferredScheme; + + /** + * @deprecated Use {@link WalletToken#getExpiresOn()} through {@link #getToken()}. The API + * expects this value under {@code token} and drops it at the Apple Pay level. + */ + @Deprecated + public Instant getExpiresOn() { + return token == null ? null : token.getExpiresOn(); + } + + /** + * @deprecated Use {@link WalletToken#setExpiresOn(Instant)} through {@link #setToken(WalletToken)}. + * The API expects this value under {@code token}; this setter now writes it there. + */ + @Deprecated + public void setExpiresOn(final Instant expiresOn) { + if (token == null) { + token = new WalletToken(); + } + token.setExpiresOn(expiresOn); + } } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/WalletToken.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/WalletToken.java new file mode 100644 index 000000000..767f1f0a3 --- /dev/null +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/WalletToken.java @@ -0,0 +1,32 @@ +package com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +/** + * The Checkout.com token of an Apple Pay or Google Pay payment method. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class WalletToken { + + /** + * The token identifier, prefixed by tok_. + * [Required] + * Pattern: ^(tok)_(\w{26})$ + */ + private String id; + + /** + * The date and time the token expires. + * [Optional] + * Format: date-time (ISO 8601) + */ + private Instant expiresOn; +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java index 08bf3ab90..1daba26ea 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java @@ -4,6 +4,7 @@ import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.CardAccountHolder; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.WalletToken; import com.checkout.payments.PreferredSchema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -25,17 +26,10 @@ public final class GooglePay extends PaymentMethodBase { private GooglePayTokenData tokenData; /** - * The Google Pay payment token string. + * The Checkout.com Google Pay token. * [Optional] */ - private String token; - - /** - * The date and time the token expires. - * [Optional] - * Format: date-time (ISO 8601) - */ - private Instant expiresOn; + private WalletToken token; /** * Set to true if you intend to reuse the payment credentials in subsequent payments. If set to @@ -68,4 +62,25 @@ public final class GooglePay extends PaymentMethodBase { * [Optional] */ private PreferredSchema preferredScheme; + + /** + * @deprecated Use {@link WalletToken#getExpiresOn()} through {@link #getToken()}. The API + * expects this value under {@code token} and drops it at the Google Pay level. + */ + @Deprecated + public Instant getExpiresOn() { + return token == null ? null : token.getExpiresOn(); + } + + /** + * @deprecated Use {@link WalletToken#setExpiresOn(Instant)} through {@link #setToken(WalletToken)}. + * The API expects this value under {@code token}; this setter now writes it there. + */ + @Deprecated + public void setExpiresOn(final Instant expiresOn) { + if (token == null) { + token = new WalletToken(); + } + token.setExpiresOn(expiresOn); + } } diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java index e50520c1a..6e3168da8 100644 --- a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java @@ -2,6 +2,9 @@ import com.checkout.GsonSerializer; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.PaymentMethods; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.applepay.ApplePay; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.applepay.ApplePayTokenData; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.applepay.ApplePayTokenDataHeader; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.bizum.Bizum; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.Card; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.CardDetails; @@ -11,6 +14,9 @@ import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodInitialization; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodStatus; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.WalletToken; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.googlepay.GooglePay; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.googlepay.GooglePayTokenData; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.instrument.Instrument; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.klarna.Klarna; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.paypal.PayPal; @@ -627,6 +633,119 @@ void shouldWriteUnderDetailsThroughTheDeprecatedFlatCardAccessors() { assertNull(new Card().getNumber()); } + // ------------------------------------------------------------------------ + // Apple Pay and Google Pay + // token is an object {id, expires_on}, no longer a bare string with + // expires_on next to it; plus schemes and preferred_scheme. The flat + // expiresOn accessors are deprecated and now write inside token. + // ------------------------------------------------------------------------ + + @Test + void shouldRoundTripApplePayWithEveryProperty() { + final ApplePay original = new ApplePay(); + original.setStatus(PaymentMethodStatus.READY); + original.setFlags(Collections.emptyList()); + original.setTokenData(ApplePayTokenData.builder() + .version("EC_v1") + .data("t7GeajLB9skXB6QSWfEpPA4WPhDqB7ekdd+F7588arLzve") + .signature("MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O/0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22/VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUI/JJxE+T5O8n5sT2KGw/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw+Soyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6+W5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS+/OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5") + .header(ApplePayTokenDataHeader.builder() + .ephemeralPublicKey("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX") + .publicKeyHash("tqYV+tmG9aMh+l/K6cicUnPqkb1gUiLjSTM9gEz6Nl0=") + .transactionId("3cee89679130a4b2617c76118a1790e2b3c0bf9a0e6e4d0c9b1f4b2a4b1e3b2d") + .build()) + .build()); + original.setToken(WalletToken.builder() + .id("tok_ubfj2q76miwundwlk72vxt2i7q") + .expiresOn(Instant.parse("2030-01-01T10:00:00Z")) + .build()); + original.setStoreForFutureUse(true); + original.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.CARTES_BANCAIRES)); + original.setPreferredScheme(PreferredSchema.CARTES_BANCAIRES); + + final String json = serializer.toJson(original); + final JsonObject applePay = JsonParser.parseString(json).getAsJsonObject(); + + assertTrue(applePay.get("token").isJsonObject()); + assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", applePay.getAsJsonObject("token").get("id").getAsString()); + assertEquals("2030-01-01T10:00:00Z", applePay.getAsJsonObject("token").get("expires_on").getAsString()); + assertFalse(applePay.has("expires_on")); + assertEquals("EC_v1", applePay.getAsJsonObject("token_data").get("version").getAsString()); + assertTrue(applePay.get("store_for_future_use").getAsBoolean()); + assertEquals("cartes_bancaires", applePay.getAsJsonArray("schemes").get(1).getAsString()); + assertEquals("cartes_bancaires", applePay.get("preferred_scheme").getAsString()); + assertEquals(original, serializer.fromJson(json, ApplePay.class)); + } + + @Test + void shouldRoundTripGooglePayWithEveryProperty() { + final GooglePay original = new GooglePay(); + original.setStatus(PaymentMethodStatus.READY); + original.setFlags(Collections.emptyList()); + original.setTokenData(GooglePayTokenData.builder() + .protocolVersion("ECv2") + .signature("MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q==") + .signedMessage("{\"encryptedMessage\":\"abc\"}") + .tokenizationKey("pk_sbox_7za2ndl3ahw2k4cmptr5qgyiv4q") + .build()); + original.setToken(WalletToken.builder() + .id("tok_ubfj2q76miwundwlk72vxt2i7q") + .expiresOn(Instant.parse("2030-01-01T10:00:00Z")) + .build()); + original.setStoreForFutureUse(false); + original.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.MASTERCARD)); + original.setPreferredScheme(PreferredSchema.MASTERCARD); + + final String json = serializer.toJson(original); + final JsonObject googlePay = JsonParser.parseString(json).getAsJsonObject(); + + assertTrue(googlePay.get("token").isJsonObject()); + assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", googlePay.getAsJsonObject("token").get("id").getAsString()); + assertEquals("2030-01-01T10:00:00Z", googlePay.getAsJsonObject("token").get("expires_on").getAsString()); + assertFalse(googlePay.has("expires_on")); + assertEquals("ECv2", googlePay.getAsJsonObject("token_data").get("protocol_version").getAsString()); + assertFalse(googlePay.get("store_for_future_use").getAsBoolean()); + assertEquals("mastercard", googlePay.get("preferred_scheme").getAsString()); + assertEquals(original, serializer.fromJson(json, GooglePay.class)); + } + + @Test + void shouldDeserializeWalletTokenSwaggerExample() { + final String json = "{\"status\":\"ready\",\"flags\":[]," + + "\"token\":{\"id\":\"tok_ubfj2q76miwundwlk72vxt2i7q\",\"expires_on\":\"2030-01-01T10:00:00Z\"}," + + "\"schemes\":[\"visa\",\"mastercard\"],\"preferred_scheme\":\"visa\"}"; + + final ApplePay applePay = serializer.fromJson(json, ApplePay.class); + final GooglePay googlePay = serializer.fromJson(json, GooglePay.class); + + assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", applePay.getToken().getId()); + assertEquals(Instant.parse("2030-01-01T10:00:00Z"), applePay.getToken().getExpiresOn()); + assertEquals(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.MASTERCARD), applePay.getSchemes()); + assertEquals(PreferredSchema.VISA, applePay.getPreferredScheme()); + assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", googlePay.getToken().getId()); + assertEquals(Instant.parse("2030-01-01T10:00:00Z"), googlePay.getToken().getExpiresOn()); + } + + @Test + @SuppressWarnings("deprecation") + void shouldWriteUnderTokenThroughTheDeprecatedWalletExpiresOnAccessors() { + final ApplePay applePay = new ApplePay(); + applePay.setExpiresOn(Instant.parse("2030-01-01T10:00:00Z")); + final GooglePay googlePay = new GooglePay(); + googlePay.setToken(WalletToken.builder().id("tok_ubfj2q76miwundwlk72vxt2i7q").build()); + googlePay.setExpiresOn(Instant.parse("2030-01-01T10:00:00Z")); + + final JsonObject appleJson = JsonParser.parseString(serializer.toJson(applePay)).getAsJsonObject(); + final JsonObject googleJson = JsonParser.parseString(serializer.toJson(googlePay)).getAsJsonObject(); + + assertFalse(appleJson.has("expires_on")); + assertEquals("2030-01-01T10:00:00Z", appleJson.getAsJsonObject("token").get("expires_on").getAsString()); + assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", googleJson.getAsJsonObject("token").get("id").getAsString()); + assertEquals("2030-01-01T10:00:00Z", googleJson.getAsJsonObject("token").get("expires_on").getAsString()); + assertEquals(Instant.parse("2030-01-01T10:00:00Z"), applePay.getExpiresOn()); + assertNull(new ApplePay().getExpiresOn()); + } + // ------------------------------------------------------------------------ // Instrument // PaymentSetupInstrument: id, the read-only type and action. From 5d3e64a7ebeae4d6c9234c8d849d06641a2838f0 Mon Sep 17 00:00:00 2001 From: rodrigojara-cko Date: Thu, 15 Oct 2026 12:19:08 +0200 Subject: [PATCH 3/5] fix(setups): drop the deprecated accessors for the old payment setup shape The flat card accessors, the wallet expiresOn accessors and numberOfTrades were kept as deprecated methods that wrote into the new shape. The old shape never worked against the API (the data was dropped without an error), so removing them breaks nothing that worked, and a compile error tells a merchant who used them that their data was being lost. It also keeps these classes on plain Lombok accessors. Card, ApplePay, GooglePay and OrderSubMerchant now expose only the spec shape. Breaking because the API demands it, so a minor release. Also add the read-only klarna.iban. --- .../entities/order/OrderSubMerchant.java | 30 --- .../paymentMethods/applepay/ApplePay.java | 22 -- .../entities/paymentMethods/card/Card.java | 213 +----------------- .../paymentMethods/googlepay/GooglePay.java | 22 -- .../paymentMethods/klarna/Klarna.java | 14 +- .../PaymentMethodsSerializationTest.java | 61 +---- ...ymentSetupsNewFieldsSerializationTest.java | 20 +- 7 files changed, 23 insertions(+), 359 deletions(-) diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java index 72bba25a3..7b982ebab 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/order/OrderSubMerchant.java @@ -40,34 +40,4 @@ public final class OrderSubMerchant { * Format: yyyy-MM-dd */ private LocalDate registrationDate; - - /** - * @deprecated Use {@link #getNumberOfSales()}. The API field is {@code number_of_sales}; - * {@code number_of_trades} is not part of the API and was dropped. - */ - @Deprecated - public Integer getNumberOfTrades() { - return numberOfSales; - } - - /** - * @deprecated Use {@link #setNumberOfSales(Integer)}. The API field is {@code number_of_sales}; - * {@code number_of_trades} is not part of the API and was dropped. - */ - @Deprecated - public void setNumberOfTrades(final Integer numberOfTrades) { - this.numberOfSales = numberOfTrades; - } - - public static class OrderSubMerchantBuilder { - - /** - * @deprecated Use {@link #numberOfSales(Integer)}. The API field is {@code number_of_sales}; - * {@code number_of_trades} is not part of the API and was dropped. - */ - @Deprecated - public OrderSubMerchantBuilder numberOfTrades(final Integer numberOfTrades) { - return numberOfSales(numberOfTrades); - } - } } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java index e3c9105a7..4061a735b 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/applepay/ApplePay.java @@ -9,7 +9,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; -import java.time.Instant; import java.util.List; /** @@ -61,25 +60,4 @@ public final class ApplePay extends PaymentMethodBase { * [Optional] */ private PreferredSchema preferredScheme; - - /** - * @deprecated Use {@link WalletToken#getExpiresOn()} through {@link #getToken()}. The API - * expects this value under {@code token} and drops it at the Apple Pay level. - */ - @Deprecated - public Instant getExpiresOn() { - return token == null ? null : token.getExpiresOn(); - } - - /** - * @deprecated Use {@link WalletToken#setExpiresOn(Instant)} through {@link #setToken(WalletToken)}. - * The API expects this value under {@code token}; this setter now writes it there. - */ - @Deprecated - public void setExpiresOn(final Instant expiresOn) { - if (token == null) { - token = new WalletToken(); - } - token.setExpiresOn(expiresOn); - } } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java index bf309558e..265f866bb 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/card/Card.java @@ -8,15 +8,11 @@ import lombok.Data; import lombok.EqualsAndHashCode; -import java.time.Instant; import java.util.List; /** - * The Card payment method's details and configuration. - * - *

The card data lives under {@link #getDetails()}, which maps {@code card.details}. The flat - * accessors such as {@link #setNumber(String)} are deprecated: the API ignores those keys at the card - * level, so they now read and write the same values inside {@code details}.

+ * The Card payment method's details and configuration. The card data lives under + * {@link #getDetails()}, which maps {@code card.details}. */ @Data @EqualsAndHashCode(callSuper = true) @@ -64,209 +60,4 @@ public final class Card extends PaymentMethodBase { * [Optional] */ private PreferredSchema preferredScheme; - - private CardDetails detailsOrCreate() { - if (details == null) { - details = new CardDetails(); - } - return details; - } - - /** - * @deprecated Use {@link CardDetails#getNumber()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public String getNumber() { - return details == null ? null : details.getNumber(); - } - - /** - * @deprecated Use {@link CardDetails#setNumber(String)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setNumber(final String number) { - detailsOrCreate().setNumber(number); - } - - /** - * @deprecated Use {@link CardDetails#getLast4()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public String getLast4() { - return details == null ? null : details.getLast4(); - } - - /** - * @deprecated Use {@link CardDetails#setLast4(String)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setLast4(final String last4) { - detailsOrCreate().setLast4(last4); - } - - /** - * @deprecated Use {@link CardDetails#getBin()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public String getBin() { - return details == null ? null : details.getBin(); - } - - /** - * @deprecated Use {@link CardDetails#setBin(String)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setBin(final String bin) { - detailsOrCreate().setBin(bin); - } - - /** - * @deprecated Use {@link CardDetails#getScheme()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public String getScheme() { - return details == null ? null : details.getScheme(); - } - - /** - * @deprecated Use {@link CardDetails#setScheme(String)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setScheme(final String scheme) { - detailsOrCreate().setScheme(scheme); - } - - /** - * @deprecated Use {@link CardDetails#getExpiryMonth()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public Integer getExpiryMonth() { - return details == null ? null : details.getExpiryMonth(); - } - - /** - * @deprecated Use {@link CardDetails#setExpiryMonth(Integer)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setExpiryMonth(final Integer expiryMonth) { - detailsOrCreate().setExpiryMonth(expiryMonth); - } - - /** - * @deprecated Use {@link CardDetails#getExpiryYear()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public Integer getExpiryYear() { - return details == null ? null : details.getExpiryYear(); - } - - /** - * @deprecated Use {@link CardDetails#setExpiryYear(Integer)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setExpiryYear(final Integer expiryYear) { - detailsOrCreate().setExpiryYear(expiryYear); - } - - /** - * @deprecated Use {@link CardDetails#getName()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public String getName() { - return details == null ? null : details.getName(); - } - - /** - * @deprecated Use {@link CardDetails#setName(String)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setName(final String name) { - detailsOrCreate().setName(name); - } - - /** - * @deprecated Use {@link CardDetails#getCvv()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public String getCvv() { - return details == null ? null : details.getCvv(); - } - - /** - * @deprecated Use {@link CardDetails#setCvv(String)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setCvv(final String cvv) { - detailsOrCreate().setCvv(cvv); - } - - /** - * @deprecated Use {@link CardDetails#getStored()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public Boolean getStored() { - return details == null ? null : details.getStored(); - } - - /** - * @deprecated Use {@link CardDetails#setStored(Boolean)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setStored(final Boolean stored) { - detailsOrCreate().setStored(stored); - } - - /** - * @deprecated Use {@link CardDetails#getExpiresOn()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public Instant getExpiresOn() { - return details == null ? null : details.getExpiresOn(); - } - - /** - * @deprecated Use {@link CardDetails#setExpiresOn(Instant)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setExpiresOn(final Instant expiresOn) { - detailsOrCreate().setExpiresOn(expiresOn); - } - - /** - * @deprecated Use {@link CardDetails#getAllowUpdate()} through {@link #getDetails()}. The API - * expects this value under {@code card.details} and drops it at the card level. - */ - @Deprecated - public Boolean getAllowUpdate() { - return details == null ? null : details.getAllowUpdate(); - } - - /** - * @deprecated Use {@link CardDetails#setAllowUpdate(Boolean)} through {@link #setDetails(CardDetails)}. - * The API expects this value under {@code card.details}; this setter now writes it there. - */ - @Deprecated - public void setAllowUpdate(final Boolean allowUpdate) { - detailsOrCreate().setAllowUpdate(allowUpdate); - } } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java index 1daba26ea..d70f7b67d 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java @@ -9,7 +9,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; -import java.time.Instant; import java.util.List; /** @@ -62,25 +61,4 @@ public final class GooglePay extends PaymentMethodBase { * [Optional] */ private PreferredSchema preferredScheme; - - /** - * @deprecated Use {@link WalletToken#getExpiresOn()} through {@link #getToken()}. The API - * expects this value under {@code token} and drops it at the Google Pay level. - */ - @Deprecated - public Instant getExpiresOn() { - return token == null ? null : token.getExpiresOn(); - } - - /** - * @deprecated Use {@link WalletToken#setExpiresOn(Instant)} through {@link #setToken(WalletToken)}. - * The API expects this value under {@code token}; this setter now writes it there. - */ - @Deprecated - public void setExpiresOn(final Instant expiresOn) { - if (token == null) { - token = new WalletToken(); - } - token.setExpiresOn(expiresOn); - } } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/klarna/Klarna.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/klarna/Klarna.java index 900b9d272..f44e753e0 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/klarna/Klarna.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/klarna/Klarna.java @@ -14,14 +14,16 @@ public final class Klarna extends PaymentMethodBase { /** - * The initialization state of the payment method. Defaults to disabled. + * The initialization state of the payment method. When you create a Payment Setup, this + * defaults to disabled. * [Optional] + * Default: disabled */ private PaymentMethodInitialization initialization = PaymentMethodInitialization.DISABLED; /** - * The next available action. Contains type, client_token, and session_id. - * [Optional] + * The next available action for the payment method. Contains type, client_token, and session_id. + * [Optional] readOnly */ private PaymentMethodAction action; @@ -30,4 +32,10 @@ public final class Klarna extends PaymentMethodBase { * [Optional] readOnly */ private KlarnaAccountHolder accountHolder; + + /** + * The IBAN of the account used to complete Klarna verification. + * [Optional] readOnly + */ + private String iban; } diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java index 6e3168da8..3a0b65d1d 100644 --- a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java @@ -58,7 +58,8 @@ void shouldDeserializeKlarnaWithAction() { + "\"type\":\"sdk\"," + "\"client_token\":\"ct_klarna_abc\"," + "\"session_id\":\"sess_111\"" - + "}" + + "}," + + "\"iban\":\"DE89370400440532013000\"" + "}"; final Klarna klarna = serializer.fromJson(json, Klarna.class); @@ -72,6 +73,7 @@ void shouldDeserializeKlarnaWithAction() { assertEquals("ct_klarna_abc", klarna.getAction().getClientToken()); assertEquals("sess_111", klarna.getAction().getSessionId()); assertNull(klarna.getAction().getOrderId()); + assertEquals("DE89370400440532013000", klarna.getIban()); } @Test @@ -86,10 +88,14 @@ void shouldRoundTripKlarnaWithAllProperties() { .sessionId("sid") .orderId(null) .build()); + original.setIban("DE89370400440532013000"); final String json = serializer.toJson(original); final Klarna restored = serializer.fromJson(json, Klarna.class); + assertEquals("DE89370400440532013000", JsonParser.parseString(json).getAsJsonObject().get("iban").getAsString()); + assertEquals(original.getIban(), restored.getIban()); + assertNotNull(restored); assertEquals(original.getStatus(), restored.getStatus()); assertEquals(original.getFlags(), restored.getFlags()); @@ -531,8 +537,7 @@ void shouldRoundTripPaymentMethodInitialization() { // ------------------------------------------------------------------------ // Card // PaymentSetup.payment_methods.card: the card data lives under details, - // token is an object, plus schemes and preferred_scheme. The flat - // accessors are deprecated and now write inside details. + // token is an object, plus schemes and preferred_scheme. // ------------------------------------------------------------------------ @Test @@ -606,38 +611,10 @@ void shouldSerializeEveryPaymentSetupSchemeValue() { assertEquals(17, PaymentSetupScheme.values().length); } - @Test - @SuppressWarnings("deprecation") - void shouldWriteUnderDetailsThroughTheDeprecatedFlatCardAccessors() { - final Card card = new Card(); - card.setNumber("4242424242424242"); - card.setLast4("4242"); - card.setBin("424242"); - card.setScheme("visa"); - card.setExpiryMonth(12); - card.setExpiryYear(2030); - card.setName("John Smith"); - card.setCvv("100"); - card.setStored(true); - card.setExpiresOn(Instant.parse("2030-01-01T10:00:00Z")); - card.setAllowUpdate(true); - - final JsonObject json = JsonParser.parseString(serializer.toJson(card)).getAsJsonObject(); - - assertEquals(1, json.size()); - assertEquals(buildSampleCard().getDetails(), card.getDetails()); - assertEquals(11, json.getAsJsonObject("details").size()); - assertEquals("4242424242424242", card.getNumber()); - assertEquals("100", card.getCvv()); - assertEquals(Instant.parse("2030-01-01T10:00:00Z"), card.getExpiresOn()); - assertNull(new Card().getNumber()); - } - // ------------------------------------------------------------------------ // Apple Pay and Google Pay // token is an object {id, expires_on}, no longer a bare string with - // expires_on next to it; plus schemes and preferred_scheme. The flat - // expiresOn accessors are deprecated and now write inside token. + // expires_on next to it; plus schemes and preferred_scheme. // ------------------------------------------------------------------------ @Test @@ -726,26 +703,6 @@ void shouldDeserializeWalletTokenSwaggerExample() { assertEquals(Instant.parse("2030-01-01T10:00:00Z"), googlePay.getToken().getExpiresOn()); } - @Test - @SuppressWarnings("deprecation") - void shouldWriteUnderTokenThroughTheDeprecatedWalletExpiresOnAccessors() { - final ApplePay applePay = new ApplePay(); - applePay.setExpiresOn(Instant.parse("2030-01-01T10:00:00Z")); - final GooglePay googlePay = new GooglePay(); - googlePay.setToken(WalletToken.builder().id("tok_ubfj2q76miwundwlk72vxt2i7q").build()); - googlePay.setExpiresOn(Instant.parse("2030-01-01T10:00:00Z")); - - final JsonObject appleJson = JsonParser.parseString(serializer.toJson(applePay)).getAsJsonObject(); - final JsonObject googleJson = JsonParser.parseString(serializer.toJson(googlePay)).getAsJsonObject(); - - assertFalse(appleJson.has("expires_on")); - assertEquals("2030-01-01T10:00:00Z", appleJson.getAsJsonObject("token").get("expires_on").getAsString()); - assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", googleJson.getAsJsonObject("token").get("id").getAsString()); - assertEquals("2030-01-01T10:00:00Z", googleJson.getAsJsonObject("token").get("expires_on").getAsString()); - assertEquals(Instant.parse("2030-01-01T10:00:00Z"), applePay.getExpiresOn()); - assertNull(new ApplePay().getExpiresOn()); - } - // ------------------------------------------------------------------------ // Instrument // PaymentSetupInstrument: id, the read-only type and action. diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java index afe81722b..aa365ca03 100644 --- a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentSetupsNewFieldsSerializationTest.java @@ -314,8 +314,7 @@ void shouldDeserializeMetadataAndAutoConfirmOnTheResponses() { // ------------------------------------------------------------------------ // OrderSubMerchant // PaymentSetupSubMerchant.number_of_sales. The model sent number_of_trades, - // a key the API does not define; the old accessors are deprecated and now - // write number_of_sales. + // a key the API does not define. // ------------------------------------------------------------------------ @Test @@ -352,21 +351,4 @@ void shouldDeserializeOrderSubMerchantSwaggerExample() { assertEquals(LocalDate.of(2023, 1, 15), subMerchant.getRegistrationDate()); } - @Test - @SuppressWarnings("deprecation") - void shouldSendNumberOfSalesThroughTheDeprecatedNumberOfTradesAccessors() { - final OrderSubMerchant fromBuilder = OrderSubMerchant.builder().numberOfTrades(42).build(); - final OrderSubMerchant fromSetter = new OrderSubMerchant(); - fromSetter.setNumberOfTrades(7); - - final JsonObject builderJson = JsonParser.parseString(serializer.toJson(fromBuilder)).getAsJsonObject(); - final JsonObject setterJson = JsonParser.parseString(serializer.toJson(fromSetter)).getAsJsonObject(); - - assertEquals(42, builderJson.get("number_of_sales").getAsInt()); - assertFalse(builderJson.has("number_of_trades")); - assertEquals(7, setterJson.get("number_of_sales").getAsInt()); - assertFalse(setterJson.has("number_of_trades")); - assertEquals(Integer.valueOf(42), fromBuilder.getNumberOfTrades()); - assertEquals(Integer.valueOf(42), fromBuilder.getNumberOfSales()); - } } From 26e5ca0648b63ae62cb399749eb77fa0b6b6c6d2 Mon Sep 17 00:00:00 2001 From: rodrigojara-cko Date: Fri, 16 Oct 2026 11:16:36 +0200 Subject: [PATCH 4/5] fix(setups): add company_name to the card account holder PaymentSetupAccountHolder, used by card, applepay and googlepay, defines company_name; the model did not carry it. --- .../common/CardAccountHolder.java | 9 ++++++++- .../PaymentMethodsSerializationTest.java | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/CardAccountHolder.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/CardAccountHolder.java index 3f9c1669b..01837d0e4 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/CardAccountHolder.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/common/CardAccountHolder.java @@ -39,7 +39,14 @@ public final class CardAccountHolder { private String middleName; /** - * Indicates whether the ANI (account name inquiry) check is performed with the card scheme. + * The card account holder's company name. + * [Optional] + */ + private String companyName; + + /** + * Indicates whether the ANI (account name inquiry) check is performed with the card scheme + * (Visa or Mastercard). * [Optional] */ private Boolean accountNameInquiry; diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java index 3a0b65d1d..3cb646d2e 100644 --- a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java @@ -9,6 +9,8 @@ import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.Card; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.CardDetails; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.card.CardToken; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.CardAccountHolder; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.CardAccountHolderType; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodAction; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodInitialization; @@ -562,6 +564,14 @@ void shouldRoundTripCardWithEveryProperty() { assertEquals("2030-01-01T10:00:00Z", details.get("expires_on").getAsString()); assertTrue(details.get("allow_update").getAsBoolean()); assertTrue(card.get("store_for_future_use").getAsBoolean()); + final JsonObject accountHolder = card.getAsJsonObject("account_holder"); + assertEquals(6, accountHolder.size()); + assertEquals("corporate", accountHolder.get("type").getAsString()); + assertEquals("John", accountHolder.get("first_name").getAsString()); + assertEquals("Smith", accountHolder.get("last_name").getAsString()); + assertEquals("Paul", accountHolder.get("middle_name").getAsString()); + assertEquals("Acme Corporation", accountHolder.get("company_name").getAsString()); + assertTrue(accountHolder.get("account_name_inquiry").getAsBoolean()); assertEquals("cartes_bancaires", card.getAsJsonArray("schemes").get(1).getAsString()); assertEquals("china_union_pay", card.getAsJsonArray("schemes").get(2).getAsString()); assertEquals("cartes_bancaires", card.get("preferred_scheme").getAsString()); @@ -757,6 +767,14 @@ private static Card buildSampleCard() { .allowUpdate(true) .build()); c.setStoreForFutureUse(true); + c.setAccountHolder(CardAccountHolder.builder() + .type(CardAccountHolderType.CORPORATE) + .firstName("John") + .lastName("Smith") + .middleName("Paul") + .companyName("Acme Corporation") + .accountNameInquiry(true) + .build()); c.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.CARTES_BANCAIRES, PaymentSetupScheme.CHINA_UNION_PAY)); c.setPreferredScheme(PreferredSchema.CARTES_BANCAIRES); return c; From a5f47a4affa9c2e07c6eea7ddf2fd4a50ee12a56 Mon Sep 17 00:00:00 2001 From: rodrigojara-cko Date: Mon, 19 Oct 2026 09:53:26 +0200 Subject: [PATCH 5/5] fix(setups): narrow the google pay preferred scheme and complete the setup docs GooglePay.preferredScheme is now a GooglePayPreferredScheme, since Google Pay accepts only visa and mastercard. Document max 80 on reference, max 100 on description and readOnly on id and available_payment_methods, and cover phone and account_holder in the card and wallet roundtrips. --- .../paymentMethods/googlepay/GooglePay.java | 4 +- .../googlepay/GooglePayPreferredScheme.java | 15 +++++ .../setups/requests/PaymentSetupsRequest.java | 2 + .../PaymentSetupsConfirmResponse.java | 6 +- .../responses/PaymentSetupsResponse.java | 6 +- .../PaymentMethodsSerializationTest.java | 67 +++++++++++++++---- 6 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePayPreferredScheme.java diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java index d70f7b67d..f4af0aa36 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePay.java @@ -5,7 +5,6 @@ import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentMethodBase; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.WalletToken; -import com.checkout.payments.PreferredSchema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -57,8 +56,7 @@ public final class GooglePay extends PaymentMethodBase { /** * The preferred card scheme to process the payment. - * Google Pay accepts only VISA and MASTERCARD. * [Optional] */ - private PreferredSchema preferredScheme; + private GooglePayPreferredScheme preferredScheme; } diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePayPreferredScheme.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePayPreferredScheme.java new file mode 100644 index 000000000..e0fce9c7a --- /dev/null +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/entities/paymentMethods/googlepay/GooglePayPreferredScheme.java @@ -0,0 +1,15 @@ +package com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.googlepay; + +import com.google.gson.annotations.SerializedName; + +/** + * A card scheme Google Pay accepts as the preferred scheme. + */ +public enum GooglePayPreferredScheme { + + @SerializedName("visa") + VISA, + + @SerializedName("mastercard") + MASTERCARD +} diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java index 28106da0f..1efaaeec1 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/requests/PaymentSetupsRequest.java @@ -60,12 +60,14 @@ public final class PaymentSetupsRequest { /** * A reference you can use to identify the payment. For example, an order number * [Optional] + * max 80 */ private String reference; /** * A description of the payment setup * [Optional] + * max 100 */ private String description; diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java index 36d90a363..e589a4579 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsConfirmResponse.java @@ -34,7 +34,7 @@ public final class PaymentSetupsConfirmResponse extends Resource { /** * The unique identifier of the payment setup. - * [Optional] + * [Optional] readOnly */ private String id; @@ -67,12 +67,14 @@ public final class PaymentSetupsConfirmResponse extends Resource { /** * A reference you can use to identify the payment setup, such as an order number. * [Optional] + * max 80 */ private String reference; /** * A description of the payment setup. * [Optional] + * max 100 */ private String description; @@ -115,7 +117,7 @@ public final class PaymentSetupsConfirmResponse extends Resource { /** * An ordered list of available payment method names. The order indicates the recommended * presentation priority, with the first item being the highest priority. - * [Optional] + * [Optional] readOnly */ private List availablePaymentMethods; diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java index 655ab5c62..9df38f19f 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/setups/responses/PaymentSetupsResponse.java @@ -35,7 +35,7 @@ public final class PaymentSetupsResponse extends Resource { /** * The unique identifier of the payment setup. - * [Optional] + * [Optional] readOnly */ private String id; @@ -68,12 +68,14 @@ public final class PaymentSetupsResponse extends Resource { /** * A reference you can use to identify the payment setup, such as an order number. * [Optional] + * max 80 */ private String reference; /** * A description of the payment setup. * [Optional] + * max 100 */ private String description; @@ -116,7 +118,7 @@ public final class PaymentSetupsResponse extends Resource { /** * An ordered list of available payment method names. The order indicates the recommended * presentation priority, with the first item being the highest priority. - * [Optional] + * [Optional] readOnly */ private List availablePaymentMethods; diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java index 3cb646d2e..4bf5bdd5d 100644 --- a/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/setups/PaymentMethodsSerializationTest.java @@ -1,6 +1,7 @@ package com.checkout.handlepaymentsandpayouts.setups; import com.checkout.GsonSerializer; +import com.checkout.common.Phone; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.PaymentMethods; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.applepay.ApplePay; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.applepay.ApplePayTokenData; @@ -18,6 +19,7 @@ import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.PaymentSetupScheme; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.common.WalletToken; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.googlepay.GooglePay; +import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.googlepay.GooglePayPreferredScheme; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.googlepay.GooglePayTokenData; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.instrument.Instrument; import com.checkout.handlepaymentsandpayouts.setups.entities.paymentMethods.klarna.Klarna; @@ -564,14 +566,7 @@ void shouldRoundTripCardWithEveryProperty() { assertEquals("2030-01-01T10:00:00Z", details.get("expires_on").getAsString()); assertTrue(details.get("allow_update").getAsBoolean()); assertTrue(card.get("store_for_future_use").getAsBoolean()); - final JsonObject accountHolder = card.getAsJsonObject("account_holder"); - assertEquals(6, accountHolder.size()); - assertEquals("corporate", accountHolder.get("type").getAsString()); - assertEquals("John", accountHolder.get("first_name").getAsString()); - assertEquals("Smith", accountHolder.get("last_name").getAsString()); - assertEquals("Paul", accountHolder.get("middle_name").getAsString()); - assertEquals("Acme Corporation", accountHolder.get("company_name").getAsString()); - assertTrue(accountHolder.get("account_name_inquiry").getAsBoolean()); + assertPhoneAndAccountHolder(card); assertEquals("cartes_bancaires", card.getAsJsonArray("schemes").get(1).getAsString()); assertEquals("china_union_pay", card.getAsJsonArray("schemes").get(2).getAsString()); assertEquals("cartes_bancaires", card.get("preferred_scheme").getAsString()); @@ -647,6 +642,8 @@ void shouldRoundTripApplePayWithEveryProperty() { .expiresOn(Instant.parse("2030-01-01T10:00:00Z")) .build()); original.setStoreForFutureUse(true); + original.setPhone(samplePhone()); + original.setAccountHolder(sampleAccountHolder()); original.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.CARTES_BANCAIRES)); original.setPreferredScheme(PreferredSchema.CARTES_BANCAIRES); @@ -659,6 +656,7 @@ void shouldRoundTripApplePayWithEveryProperty() { assertFalse(applePay.has("expires_on")); assertEquals("EC_v1", applePay.getAsJsonObject("token_data").get("version").getAsString()); assertTrue(applePay.get("store_for_future_use").getAsBoolean()); + assertPhoneAndAccountHolder(applePay); assertEquals("cartes_bancaires", applePay.getAsJsonArray("schemes").get(1).getAsString()); assertEquals("cartes_bancaires", applePay.get("preferred_scheme").getAsString()); assertEquals(original, serializer.fromJson(json, ApplePay.class)); @@ -680,8 +678,10 @@ void shouldRoundTripGooglePayWithEveryProperty() { .expiresOn(Instant.parse("2030-01-01T10:00:00Z")) .build()); original.setStoreForFutureUse(false); + original.setPhone(samplePhone()); + original.setAccountHolder(sampleAccountHolder()); original.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.MASTERCARD)); - original.setPreferredScheme(PreferredSchema.MASTERCARD); + original.setPreferredScheme(GooglePayPreferredScheme.MASTERCARD); final String json = serializer.toJson(original); final JsonObject googlePay = JsonParser.parseString(json).getAsJsonObject(); @@ -692,6 +692,7 @@ void shouldRoundTripGooglePayWithEveryProperty() { assertFalse(googlePay.has("expires_on")); assertEquals("ECv2", googlePay.getAsJsonObject("token_data").get("protocol_version").getAsString()); assertFalse(googlePay.get("store_for_future_use").getAsBoolean()); + assertPhoneAndAccountHolder(googlePay); assertEquals("mastercard", googlePay.get("preferred_scheme").getAsString()); assertEquals(original, serializer.fromJson(json, GooglePay.class)); } @@ -711,6 +712,20 @@ void shouldDeserializeWalletTokenSwaggerExample() { assertEquals(PreferredSchema.VISA, applePay.getPreferredScheme()); assertEquals("tok_ubfj2q76miwundwlk72vxt2i7q", googlePay.getToken().getId()); assertEquals(Instant.parse("2030-01-01T10:00:00Z"), googlePay.getToken().getExpiresOn()); + assertEquals(GooglePayPreferredScheme.VISA, googlePay.getPreferredScheme()); + } + + @Test + void shouldSerializeEveryGooglePayPreferredSchemeValue() { + assertEquals(2, GooglePayPreferredScheme.values().length); + for (final GooglePayPreferredScheme scheme : GooglePayPreferredScheme.values()) { + final GooglePay googlePay = new GooglePay(); + googlePay.setPreferredScheme(scheme); + final String json = serializer.toJson(googlePay); + assertEquals(scheme.name().toLowerCase(), + JsonParser.parseString(json).getAsJsonObject().get("preferred_scheme").getAsString()); + assertEquals(scheme, serializer.fromJson(json, GooglePay.class).getPreferredScheme()); + } } // ------------------------------------------------------------------------ @@ -767,17 +782,41 @@ private static Card buildSampleCard() { .allowUpdate(true) .build()); c.setStoreForFutureUse(true); - c.setAccountHolder(CardAccountHolder.builder() + c.setPhone(samplePhone()); + c.setAccountHolder(sampleAccountHolder()); + c.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.CARTES_BANCAIRES, PaymentSetupScheme.CHINA_UNION_PAY)); + c.setPreferredScheme(PreferredSchema.CARTES_BANCAIRES); + return c; + } + + private static Phone samplePhone() { + return Phone.builder().countryCode("+44").number("207 946 0000").build(); + } + + private static CardAccountHolder sampleAccountHolder() { + return CardAccountHolder.builder() .type(CardAccountHolderType.CORPORATE) .firstName("John") .lastName("Smith") .middleName("Paul") .companyName("Acme Corporation") .accountNameInquiry(true) - .build()); - c.setSchemes(Arrays.asList(PaymentSetupScheme.VISA, PaymentSetupScheme.CARTES_BANCAIRES, PaymentSetupScheme.CHINA_UNION_PAY)); - c.setPreferredScheme(PreferredSchema.CARTES_BANCAIRES); - return c; + .build(); + } + + private static void assertPhoneAndAccountHolder(final JsonObject paymentMethod) { + final JsonObject phone = paymentMethod.getAsJsonObject("phone"); + assertEquals(2, phone.size()); + assertEquals("+44", phone.get("country_code").getAsString()); + assertEquals("207 946 0000", phone.get("number").getAsString()); + final JsonObject accountHolder = paymentMethod.getAsJsonObject("account_holder"); + assertEquals(6, accountHolder.size()); + assertEquals("corporate", accountHolder.get("type").getAsString()); + assertEquals("John", accountHolder.get("first_name").getAsString()); + assertEquals("Smith", accountHolder.get("last_name").getAsString()); + assertEquals("Paul", accountHolder.get("middle_name").getAsString()); + assertEquals("Acme Corporation", accountHolder.get("company_name").getAsString()); + assertTrue(accountHolder.get("account_name_inquiry").getAsBoolean()); } private static Klarna buildSampleKlarna() {