diff --git a/src/main/java/com/checkout/common/ChallengeIndicator.java b/src/main/java/com/checkout/common/ChallengeIndicator.java index a9f531d6..73dc5f14 100644 --- a/src/main/java/com/checkout/common/ChallengeIndicator.java +++ b/src/main/java/com/checkout/common/ChallengeIndicator.java @@ -3,52 +3,99 @@ import com.google.gson.annotations.SerializedName; /** - * Indicates whether a challenge is requested for an authentication. + * Indicates the preference for whether or not a 3DS challenge should be performed. The customer's + * bank has the final say on whether or not the customer receives the challenge. *
- * The first four values are valid for all endpoints that accept a challenge indicator - * (e.g. {@code POST /payments} {@code three_ds.challenge_indicator}, {@code POST /sessions}, - * and their responses). + * This is the four-value indicator accepted by the {@code 3ds.challenge_indicator} field on + * {@code POST /payments}, {@code POST /hosted-payments}, {@code POST /payment-links} and + * {@code POST /payment-sessions}. *
- * The remaining values ({@link #LOW_VALUE}, {@link #TRUSTED_LISTING}, - * {@link #TRUSTED_LISTING_PROMPT}, {@link #TRANSACTION_RISK_ASSESSMENT}, {@link #DATA_SHARE}) - * represent requests for exemption and are only valid for {@code POST /sessions} - * (3DS Standalone Authentication). If an exemption cannot be applied, the value - * {@link #NO_CHALLENGE_REQUESTED} will be used instead. + * [Optional] + *
+ * Default: {@link #NO_PREFERENCE} + * + * @see com.checkout.sessions.SessionChallengeIndicator the wider nine-value enum accepted by + * {@code POST /sessions}, which additionally supports requests for exemption */ public enum ChallengeIndicator { + /** + * A challenge is requested for this payment. + */ @SerializedName("challenge_requested") CHALLENGE_REQUESTED, + + /** + * A challenge is requested for this payment because it is mandated by local regulation or + * scheme rules. + */ @SerializedName("challenge_requested_mandate") CHALLENGE_REQUESTED_MANDATE, + + /** + * A challenge is not requested for this payment. + */ @SerializedName("no_challenge_requested") NO_CHALLENGE_REQUESTED, + + /** + * No preference as to whether a challenge should be performed. This is the default. + */ @SerializedName("no_preference") NO_PREFERENCE, + /** - * Request a low-value exemption. Only valid for {@code POST /sessions}. + * Request a low-value exemption. + * + * @deprecated only valid for {@code POST /sessions}, which is now modelled by + * {@link com.checkout.sessions.SessionChallengeIndicator#LOW_VALUE}. This value is rejected by + * the {@code 3ds.challenge_indicator} fields that use this enum. */ + @Deprecated @SerializedName("low_value") LOW_VALUE, + /** - * Request a trusted listing exemption. Only valid for {@code POST /sessions}. + * Request a trusted listing exemption. + * + * @deprecated only valid for {@code POST /sessions}, which is now modelled by + * {@link com.checkout.sessions.SessionChallengeIndicator#TRUSTED_LISTING}. This value is + * rejected by the {@code 3ds.challenge_indicator} fields that use this enum. */ + @Deprecated @SerializedName("trusted_listing") TRUSTED_LISTING, + /** * Request a trusted listing prompt to add the merchant to the cardholder's trusted list. - * Only valid for {@code POST /sessions}. + * + * @deprecated only valid for {@code POST /sessions}, which is now modelled by + * {@link com.checkout.sessions.SessionChallengeIndicator#TRUSTED_LISTING_PROMPT}. This value is + * rejected by the {@code 3ds.challenge_indicator} fields that use this enum. */ + @Deprecated @SerializedName("trusted_listing_prompt") TRUSTED_LISTING_PROMPT, + /** - * Request a transaction risk analysis (TRA) exemption. Only valid for {@code POST /sessions}. + * Request a transaction risk analysis (TRA) exemption. + * + * @deprecated only valid for {@code POST /sessions}, which is now modelled by + * {@link com.checkout.sessions.SessionChallengeIndicator#TRANSACTION_RISK_ASSESSMENT}. This + * value is rejected by the {@code 3ds.challenge_indicator} fields that use this enum. */ + @Deprecated @SerializedName("transaction_risk_assessment") TRANSACTION_RISK_ASSESSMENT, + /** - * Indicates a data-share authentication request. Only valid for {@code POST /sessions}. + * Indicates a data-share authentication request. + * + * @deprecated only valid for {@code POST /sessions}, which is now modelled by + * {@link com.checkout.sessions.SessionChallengeIndicator#DATA_SHARE}. This value is rejected by + * the {@code 3ds.challenge_indicator} fields that use this enum. */ + @Deprecated @SerializedName("data_share") DATA_SHARE, diff --git a/src/main/java/com/checkout/sessions/CreateSessionAcceptedResponse.java b/src/main/java/com/checkout/sessions/CreateSessionAcceptedResponse.java index 35e52149..6aec2253 100644 --- a/src/main/java/com/checkout/sessions/CreateSessionAcceptedResponse.java +++ b/src/main/java/com/checkout/sessions/CreateSessionAcceptedResponse.java @@ -1,6 +1,5 @@ package com.checkout.sessions; -import com.checkout.common.ChallengeIndicator; import com.checkout.common.Currency; import com.checkout.common.Resource; import lombok.Data; @@ -15,48 +14,165 @@ @ToString(callSuper = true) public final class CreateSessionAcceptedResponse extends Resource { + /** + * Session unique identifier. + * [Required] + * ^(sid)_(\w{26})$ + * min 30 characters + * max 30 characters + */ private String id; + /** + * A base64 encoded value prefixed with {@code sek_} that gives access to client-side operations + * for a single authentication within the Sessions API. + * [Required] + * ^(sek)_(.{44})$ + * min 48 characters + * max 48 characters + */ private String sessionSecret; + /** + * The transaction identifier that needs to be provided when communicating directly with the + * Access Control Server (ACS). + * [Required] + * min 36 characters + * max 36 characters + */ private String transactionId; + /** + * Indicates the scheme this authentication is carried out against. + * [Required] + */ private SessionScheme scheme; + /** + * The amount in the minor currency. + * [Required] + * min 0 + * max 9007199254740991 + */ private Long amount; + /** + * The three-letter ISO currency code. + * [Required] + */ private Currency currency; + /** + * Indicates the type of payment this session is for. Please note the spelling of + * {@code installment} consists of two {@code l}s. + * [Required] + */ private AuthenticationType authenticationType; + /** + * Indicates the category of the authentication request. + * [Required] + */ private Category authenticationCategory; + /** + * The status of the session. + * [Required] + */ private SessionStatus status; + /** + * When the session is unavailable, this points to the reason why. + *
+ * Note: this field is not present in the {@code CreateSessionAcceptedResponse} schema of the
+ * Checkout.com API Reference, where it appears only on {@code GET /sessions/{id}}. It is
+ * retained for backwards compatibility pending confirmation from the API owners.
+ * [Optional]
+ */
private StatusReason statusReason;
+ /**
+ * Specifies which action to take in order to complete the session.
+ * The {@code redirect_cardholder} action is only applicable for hosted sessions.
+ * [Required]
+ */
private List
+ * Note: the API Reference specifies only the four base values for this response field, but the
+ * request accepts nine. This is typed as {@link SessionChallengeIndicator} so that an exemption
+ * value echoed back by the API still deserializes; see
+ * {@link SessionRequest#getChallengeIndicator()}.
+ * [Required]
+ * Default: {@link SessionChallengeIndicator#NO_PREFERENCE}
+ */
+ private SessionChallengeIndicator challengeIndicator;
+
+ /**
+ * The information about the optimization options selected.
+ * [Optional]
+ */
private Optimization optimization;
}
diff --git a/src/main/java/com/checkout/sessions/GetSessionResponse.java b/src/main/java/com/checkout/sessions/GetSessionResponse.java
index 64019505..3d3364c4 100644
--- a/src/main/java/com/checkout/sessions/GetSessionResponse.java
+++ b/src/main/java/com/checkout/sessions/GetSessionResponse.java
@@ -1,6 +1,5 @@
package com.checkout.sessions;
-import com.checkout.common.ChallengeIndicator;
import com.checkout.common.Currency;
import com.checkout.common.Resource;
import com.checkout.common.ThreeDSFlowType;
@@ -17,85 +16,298 @@
@ToString(callSuper = true)
public class GetSessionResponse extends Resource {
+ /**
+ * Session unique identifier.
+ * [Required]
+ * ^(sid)_(\w{26})$
+ * min 30 characters
+ * max 30 characters
+ */
private String id;
+ /**
+ * A base64 encoded value prefixed with {@code sek_} that gives access to client-side operations
+ * for a single authentication within the Sessions API.
+ * [Optional]
+ * ^(sek)_(.{44})$
+ * min 48 characters
+ * max 48 characters
+ */
private String sessionSecret;
+ /**
+ * The transaction identifier that needs to be provided when communicating directly with the
+ * Access Control Server (ACS).
+ * [Required]
+ * min 36 characters
+ * max 36 characters
+ */
private String transactionId;
+ /**
+ * Indicates the scheme this authentication is carried out against.
+ * [Required]
+ */
private SessionScheme scheme;
+ /**
+ * The amount in the minor currency.
+ * [Required]
+ * min 0
+ * max 9007199254740991
+ */
private Long amount;
+ /**
+ * The three-letter ISO currency code.
+ * [Required]
+ */
private Currency currency;
+ /**
+ * Indicates whether this session has been completed.
+ * [Optional]
+ */
private Boolean completed;
+ /**
+ * Indicates whether this session involved a challenge. This will only be set after
+ * communication with the scheme is finished.
+ * [Optional]
+ */
private Boolean challenged;
+ /**
+ * Indicates the type of payment this session is for. Please note the spelling of
+ * {@code installment} consists of two {@code l}s.
+ * [Required]
+ */
private AuthenticationType authenticationType;
+ /**
+ * Indicates the category of the authentication request.
+ * [Required]
+ */
private Category authenticationCategory;
+ /**
+ * Public certificates specific to a Directory Server (DS) for encrypting device data and
+ * verifying ACS signed content. Required when the channel is {@code app}.
+ * [Optional]
+ */
private DsPublicKeys certificates;
+ /**
+ * Indicates the status of the session.
+ * [Required]
+ */
private SessionStatus status;
+ /**
+ * When the session is unavailable, this points to the reason why. For example,
+ * {@code ares_error} indicates there was an issue in the authentication response returned by
+ * the Directory Server, and {@code ares_status} indicates the status was set to the status in
+ * that authentication response.
+ * [Optional]
+ */
private StatusReason statusReason;
+ /**
+ * Whether the authentication was successful. This will only be set if the session is in a final
+ * state.
+ * [Optional]
+ */
private Boolean approved;
+ /**
+ * The protocol version number of the specification used by the API for authentication.
+ * [Required]
+ * max 50 characters
+ */
private String protocolVersion;
+ /**
+ * Additional information about the cardholder's account.
+ * [Optional]
+ */
@SerializedName("account_info")
private CardholderAccountInfo cardholderAccountInfo;
+ /**
+ * Additional information about the cardholder's purchase.
+ * [Optional]
+ */
private MerchantRiskInfo merchantRiskInfo;
+ /**
+ * A reference you can later use to identify this payment, such as an order number.
+ * [Optional]
+ * max 100 characters
+ */
private String reference;
+ /**
+ * Identifies the type of transaction being authenticated.
+ * [Optional]
+ * Default: {@link TransactionType#GOODS_SERVICE}
+ * max 50 characters
+ */
private TransactionType transactionType;
+ /**
+ * Specifies which action to take in order to complete the session.
+ * The {@code redirect_cardholder} action is only applicable for hosted sessions.
+ * [Optional]
+ */
private List
+ * Note: this field is not present in the {@code GetSessionResponse} schema of the Checkout.com
+ * API Reference. It is retained for backwards compatibility pending confirmation from the API
+ * owners.
+ * [Optional]
+ */
private String pareq;
+ /**
+ * Payment system-specific value provided as part of the ACS registration for each supported DS.
+ * This field is only included in responses when authenticating with a valid OAuth token, and not
+ * when authenticating with {@link #sessionSecret}.
+ * [Optional]
+ * min 28 characters
+ * max 28 characters
+ */
private String cryptogram;
+ /**
+ * Electronic Commerce Indicator. This field is only included in responses when authenticating
+ * with a valid OAuth token, and not when authenticating with {@link #sessionSecret}.
+ * [Optional]
+ * min 2 characters
+ * max 2 characters
+ */
private String eci;
+ /**
+ * The xid value to use for authorization.
+ * [Optional]
+ */
private String xid;
+ /**
+ * May provide cardholder information from the DS to be presented to the cardholder.
+ * [Optional]
+ */
private String cardholderInfo;
+ /**
+ * Details related to the session source. This property should always be in the response, unless
+ * a {@code card} source was used and communication with Checkout.com's Vault was not possible.
+ * [Optional]
+ */
private CardInfo card;
+ /**
+ * Details of a recurring authentication.
+ * [Optional]
+ */
private Recurring recurring;
+ /**
+ * Details of an installment authentication.
+ * [Optional]
+ */
private Installment installment;
+ /**
+ * Details of a previous transaction.
+ * [Optional]
+ */
private InitialTransaction initialTransaction;
+ /**
+ * Indicates the cardholder's IP address. Only available when the scheme selected is Cartes
+ * Bancaires.
+ * [Optional]
+ */
private String customerIp;
+ /**
+ * Authentication date and time.
+ * [Optional]
+ * Format: date-time (RFC 3339)
+ */
private Instant authenticationDate;
+ /**
+ * Details related to the exemption present in the 3DS flow.
+ * [Optional]
+ */
private ThreeDSExemption exemption;
+ /**
+ * Indicates whether the 3D Secure 2 authentication was challenged or frictionless.
+ * [Optional]
+ */
private ThreeDSFlowType flowType;
- private ChallengeIndicator challengeIndicator;
-
+ /**
+ * Indicates the preference for whether or not a 3DS challenge should be performed. The
+ * customer's bank has the final say on whether or not the customer receives the challenge.
+ *
+ * Note: the API Reference specifies only the four base values for this response field, but the
+ * request accepts nine. This is typed as {@link SessionChallengeIndicator} so that an exemption
+ * value echoed back by the API still deserializes; see
+ * {@link SessionRequest#getChallengeIndicator()}.
+ * [Required]
+ * Default: {@link SessionChallengeIndicator#NO_PREFERENCE}
+ */
+ private SessionChallengeIndicator challengeIndicator;
+
+ /**
+ * The information about the optimization options selected.
+ * [Optional]
+ */
private Optimization optimization;
+ /**
+ * Indicates scheme-specific information.
+ * [Optional]
+ */
private SchemeInfo schemeInfo;
}
diff --git a/src/main/java/com/checkout/sessions/SessionChallengeIndicator.java b/src/main/java/com/checkout/sessions/SessionChallengeIndicator.java
new file mode 100644
index 00000000..fe51d55f
--- /dev/null
+++ b/src/main/java/com/checkout/sessions/SessionChallengeIndicator.java
@@ -0,0 +1,89 @@
+package com.checkout.sessions;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Indicates whether a challenge is requested for this session.
+ *
+ * Used by {@link SessionRequest#getChallengeIndicator()} for {@code POST /sessions}
+ * (3DS Standalone Authentication). This is the only field in the API that accepts the
+ * exemption values below; the {@code 3ds.challenge_indicator} field on payments, hosted
+ * payments, payment links and payment sessions accepts only the first four values and is
+ * modelled by {@link com.checkout.common.ChallengeIndicator}.
+ *
+ * The following are requests for exemption:
+ * {@link #LOW_VALUE}, {@link #TRUSTED_LISTING}, {@link #TRUSTED_LISTING_PROMPT} and
+ * {@link #TRANSACTION_RISK_ASSESSMENT}. If an exemption cannot be applied, then the value
+ * {@link #NO_CHALLENGE_REQUESTED} will be used instead.
+ *
+ * [Optional]
+ *
+ * Default: {@link #NO_PREFERENCE}
+ *
+ * max 50 characters
+ */
+public enum SessionChallengeIndicator {
+
+ /**
+ * No preference as to whether a challenge should be performed. This is the default.
+ */
+ @SerializedName("no_preference")
+ NO_PREFERENCE,
+
+ /**
+ * A challenge is not requested for this session.
+ */
+ @SerializedName("no_challenge_requested")
+ NO_CHALLENGE_REQUESTED,
+
+ /**
+ * A challenge is requested for this session.
+ */
+ @SerializedName("challenge_requested")
+ CHALLENGE_REQUESTED,
+
+ /**
+ * A challenge is requested for this session because it is mandated by local regulation
+ * or scheme rules.
+ */
+ @SerializedName("challenge_requested_mandate")
+ CHALLENGE_REQUESTED_MANDATE,
+
+ /**
+ * Request a low-value exemption. If the exemption cannot be applied, the value
+ * {@link #NO_CHALLENGE_REQUESTED} will be used instead.
+ */
+ @SerializedName("low_value")
+ LOW_VALUE,
+
+ /**
+ * Request a trusted listing exemption, applied when the cardholder has already added the
+ * merchant to their list of trusted beneficiaries. If the exemption cannot be applied, the
+ * value {@link #NO_CHALLENGE_REQUESTED} will be used instead.
+ */
+ @SerializedName("trusted_listing")
+ TRUSTED_LISTING,
+
+ /**
+ * Request a trusted listing exemption and prompt the cardholder to add the merchant to their
+ * list of trusted beneficiaries. If the exemption cannot be applied, the value
+ * {@link #NO_CHALLENGE_REQUESTED} will be used instead.
+ */
+ @SerializedName("trusted_listing_prompt")
+ TRUSTED_LISTING_PROMPT,
+
+ /**
+ * Request a transaction risk analysis (TRA) exemption. If the exemption cannot be applied,
+ * the value {@link #NO_CHALLENGE_REQUESTED} will be used instead.
+ */
+ @SerializedName("transaction_risk_assessment")
+ TRANSACTION_RISK_ASSESSMENT,
+
+ /**
+ * Request a data-share authentication, where cardholder data is shared with the issuer to
+ * support their risk assessment without requesting a challenge.
+ */
+ @SerializedName("data_share")
+ DATA_SHARE,
+
+}
diff --git a/src/main/java/com/checkout/sessions/SessionRequest.java b/src/main/java/com/checkout/sessions/SessionRequest.java
index d954ed66..b43fec3c 100644
--- a/src/main/java/com/checkout/sessions/SessionRequest.java
+++ b/src/main/java/com/checkout/sessions/SessionRequest.java
@@ -1,6 +1,5 @@
package com.checkout.sessions;
-import com.checkout.common.ChallengeIndicator;
import com.checkout.common.Currency;
import com.checkout.sessions.channel.BrowserSession;
import com.checkout.sessions.channel.ChannelData;
@@ -19,54 +18,171 @@
@NoArgsConstructor
public final class SessionRequest {
+ /**
+ * The source of the authentication.
+ * [Required]
+ */
@Builder.Default
private SessionSource source = new SessionCardSource();
+ /**
+ * The payment amount in the minor currency unit.
+ * For {@code recurring} and {@code installment} payment types, this value is required and must
+ * be greater than zero.
+ * Omitting this value will set {@link #authenticationCategory} to {@link Category#NON_PAYMENT}.
+ * [Optional]
+ * min 0
+ * max 48 characters
+ */
private Long amount;
+ /**
+ * The three-letter ISO currency code.
+ * [Required]
+ * min 3 characters
+ * max 3 characters
+ */
private Currency currency;
+ /**
+ * The processing channel to be used for the session. Required if this was not set in the
+ * request for the OAuth token.
+ * [Optional]
+ * ^(pc)_(\w{26})$
+ */
private String processingChannelId;
+ /**
+ * Information related to authentication for payfac payments.
+ * [Optional]
+ */
private SessionMarketplaceData marketplace;
+ /**
+ * Indicates the type of payment this session is for. Please note the spelling of
+ * {@code installment} consists of two {@code l}s.
+ * [Optional]
+ * Default: {@link AuthenticationType#REGULAR}
+ */
@Builder.Default
private AuthenticationType authenticationType = AuthenticationType.REGULAR;
+ /**
+ * Indicates the category of the authentication request.
+ * [Optional]
+ * Default: {@link Category#PAYMENT}
+ */
@Builder.Default
private Category authenticationCategory = Category.PAYMENT;
+ /**
+ * Additional information about the cardholder's account.
+ * [Optional]
+ */
@SerializedName("account_info")
private CardholderAccountInfo cardholderAccountInfo;
+ /**
+ * Indicates whether a challenge is requested for this session.
+ * The exemption values are accepted only by {@code POST /sessions}; see
+ * {@link SessionChallengeIndicator}.
+ * [Optional]
+ * Default: {@link SessionChallengeIndicator#NO_PREFERENCE}
+ * max 50 characters
+ */
@Builder.Default
- private ChallengeIndicator challengeIndicator = ChallengeIndicator.NO_PREFERENCE;
+ private SessionChallengeIndicator challengeIndicator = SessionChallengeIndicator.NO_PREFERENCE;
+ /**
+ * An optional dynamic billing descriptor.
+ * [Optional]
+ */
private SessionsBillingDescriptor billingDescriptor;
+ /**
+ * A reference you can later use to identify this payment, such as an order number.
+ * Do not pass sensitive information in this field, for example card details.
+ * [Optional]
+ * max 100 characters
+ */
private String reference;
+ /**
+ * Additional information about the cardholder's purchase.
+ * [Optional]
+ */
private MerchantRiskInfo merchantRiskInfo;
+ /**
+ * A reference to a previous transaction for this cardholder.
+ *
+ * Note: this field is not present in the {@code SessionRequest} schema of the Checkout.com API
+ * Reference. It is retained for backwards compatibility pending confirmation from the API
+ * owners.
+ * [Optional]
+ */
private String priorTransactionReference;
+ /**
+ * Identifies the type of transaction being authenticated.
+ * [Optional]
+ * Default: {@link TransactionType#GOODS_SERVICE}
+ * max 50 characters
+ */
@Builder.Default
private TransactionType transactionType = TransactionType.GOODS_SERVICE;
+ /**
+ * The shipping address. Any special characters will be replaced.
+ * [Optional]
+ */
private SessionAddress shippingAddress;
+ /**
+ * Indicates whether the cardholder shipping address and billing address are the same.
+ * [Optional]
+ */
private Boolean shippingAddressMatchesBilling;
+ /**
+ * The redirect information needed for callbacks or redirects after the payment is completed.
+ * [Required]
+ */
private CompletionInfo completion;
+ /**
+ * The information gathered from the environment used to initiate the session.
+ * See {@link BrowserSession} for the browser channel.
+ * [Optional]
+ */
private ChannelData channelData;
+ /**
+ * Details of a recurring authentication. This property is needed only for a
+ * {@link AuthenticationType#RECURRING} authentication type. Value will be ignored in any other
+ * cases.
+ * [Optional]
+ */
private Recurring recurring;
+ /**
+ * Details of an installment authentication. This property is needed only for an
+ * {@link AuthenticationType#INSTALLMENT} authentication type. Value will be ignored in any
+ * other cases.
+ * [Optional]
+ */
private Installment installment;
+ /**
+ * Optionally opt into request optimization.
+ * [Optional]
+ */
private Optimization optimization;
+ /**
+ * Details of a previous transaction.
+ * [Optional]
+ */
private InitialTransaction initialTransaction;
/**
diff --git a/src/main/java/com/checkout/sessions/SessionScheme.java b/src/main/java/com/checkout/sessions/SessionScheme.java
index 2c6469c0..5dc575f5 100644
--- a/src/main/java/com/checkout/sessions/SessionScheme.java
+++ b/src/main/java/com/checkout/sessions/SessionScheme.java
@@ -2,18 +2,64 @@
import com.google.gson.annotations.SerializedName;
+/**
+ * Indicates the scheme this authentication is carried out against.
+ *
+ * Used by {@link GetSessionResponse#getScheme()},
+ * {@link CreateSessionAcceptedResponse#getScheme()},
+ * {@link GetSessionResponseAfterChannelDataSupplied#getScheme()},
+ * {@link SchemeInfo#getName()} and
+ * {@link com.checkout.sessions.source.SessionSource#getScheme()}.
+ *
+ * [Required] on the session responses
+ */
public enum SessionScheme {
+ /**
+ * American Express.
+ */
@SerializedName("amex")
AMEX,
+
+ /**
+ * Cartes Bancaires.
+ */
@SerializedName("cartes_bancaires")
CARTES_BANCAIRES,
+
+ /**
+ * Diners Club.
+ */
@SerializedName("diners")
DINERS,
+
+ /**
+ * Discover.
+ */
+ @SerializedName("discover")
+ DISCOVER,
+
+ /**
+ * JCB.
+ */
@SerializedName("jcb")
JCB,
+
+ /**
+ * Mastercard.
+ */
@SerializedName("mastercard")
MASTERCARD,
+
+ /**
+ * Unified Payments Interface (UPI).
+ */
+ @SerializedName("upi")
+ UPI,
+
+ /**
+ * Visa.
+ */
@SerializedName("visa")
VISA
diff --git a/src/main/java/com/checkout/sessions/SessionsCardMetadataResponse.java b/src/main/java/com/checkout/sessions/SessionsCardMetadataResponse.java
index fd2b62c7..204aa49d 100644
--- a/src/main/java/com/checkout/sessions/SessionsCardMetadataResponse.java
+++ b/src/main/java/com/checkout/sessions/SessionsCardMetadataResponse.java
@@ -3,19 +3,51 @@
import com.checkout.common.CardCategory;
import com.checkout.common.CardType;
import com.checkout.common.CountryCode;
-
+import lombok.Data;
+
+/**
+ * Additional details for this card.
+ *
+ * Returned as {@link CardInfo#getMetadata()} on the session responses.
+ */
+@Data
public final class SessionsCardMetadataResponse {
+ /**
+ * The card type.
+ * [Optional]
+ */
private CardType cardType;
+ /**
+ * The card category.
+ * [Optional]
+ */
private CardCategory cardCategory;
+ /**
+ * The card issuer's name.
+ * [Optional]
+ */
private String issuerName;
+ /**
+ * The two letter alpha country code of the card issuer.
+ * [Optional]
+ * ^[A-Z]{2}
+ */
private CountryCode issuerCountry;
+ /**
+ * The issuer/card scheme product identifier.
+ * [Optional]
+ */
private String productId;
+ /**
+ * The issuer/card scheme product type.
+ * [Optional]
+ */
private String productType;
}
diff --git a/src/main/java/com/checkout/sessions/TransactionType.java b/src/main/java/com/checkout/sessions/TransactionType.java
index aeca2127..1802ea19 100644
--- a/src/main/java/com/checkout/sessions/TransactionType.java
+++ b/src/main/java/com/checkout/sessions/TransactionType.java
@@ -2,17 +2,49 @@
import com.google.gson.annotations.SerializedName;
+/**
+ * Identifies the type of transaction being authenticated.
+ *
+ * Used by {@link SessionRequest#getTransactionType()} and
+ * {@link GetSessionResponse#getTransactionType()}.
+ *
+ * [Optional]
+ *
+ * Default: {@link #GOODS_SERVICE}
+ *
+ * max 50 characters
+ */
public enum TransactionType {
+ /**
+ * A transaction that funds an account.
+ */
@SerializedName("account_funding")
ACCOUNT_FUNDING,
+
+ /**
+ * A transaction that accepts a check.
+ */
@SerializedName("check_acceptance")
CHECK_ACCEPTANCE,
+
+ /**
+ * A transaction for goods or a service. This is the default.
+ */
@SerializedName("goods_service")
GOODS_SERVICE,
+
+ /**
+ * A transaction that activates or loads a prepaid card.
+ */
@SerializedName("prepaid_activation_and_load")
PREPAID_ACTIVATION_AND_LOAD,
- @SerializedName("quashi_card_transaction")
- QUASHI_CARD_TRANSACTION,
+
+ /**
+ * A quasi-cash transaction, for example the purchase of casino chips, money orders or
+ * traveller's cheques.
+ */
+ @SerializedName("quasi_card_transaction")
+ QUASI_CARD_TRANSACTION,
}
diff --git a/src/test/java/com/checkout/common/ChallengeIndicatorTest.java b/src/test/java/com/checkout/common/ChallengeIndicatorTest.java
index 0d01df94..5af91bf6 100644
--- a/src/test/java/com/checkout/common/ChallengeIndicatorTest.java
+++ b/src/test/java/com/checkout/common/ChallengeIndicatorTest.java
@@ -11,10 +11,36 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
+/**
+ * The five exemption values are deprecated on this shared enum in favour of
+ * {@link com.checkout.sessions.SessionChallengeIndicator}, but they are deliberately kept and still
+ * covered here: removing them would break merchants that already reference them.
+ */
+@SuppressWarnings("deprecation")
class ChallengeIndicatorTest {
private final Serializer serializer = new GsonSerializer();
+ private static Stream
+ * Every one of the class's 22 properties is populated and asserted, plus the {@code _links} it
+ * inherits from {@link com.checkout.common.Resource}.
+ */
+class CreateSessionAcceptedResponseSerializationTest {
+
+ private final Serializer serializer = new GsonSerializer();
+
+ private CreateSessionAcceptedResponse response;
+
+ private static String fullPayload() {
+ return "{"
+ + "\"id\":\"sid_y3oqhf46pyzuxjbcn2giaqnb44\","
+ + "\"session_secret\":\"sek_Dal7UyiH8rIFXA4PfgiIk2jUyQkVDeEWgVBEL4TsRTE=\","
+ + "\"transaction_id\":\"9aea641d-0549-4222-9ca9-d90b43a4f38c\","
+ + "\"scheme\":\"mastercard\","
+ + "\"amount\":6540,"
+ + "\"currency\":\"USD\","
+ + "\"authentication_type\":\"regular\","
+ + "\"authentication_category\":\"payment\","
+ + "\"status\":\"pending\","
+ + "\"status_reason\":\"ares_status\","
+ + "\"next_actions\":[\"collect_channel_data\"],"
+ + "\"protocol_version\":\"2.2.0\","
+ + "\"account_info\":{\"purchase_count\":10,\"add_card_attempts\":5,\"transactions_today\":3},"
+ + "\"merchant_risk_info\":{\"delivery_email\":\"bruce@wayne-enterprises.com\","
+ + "\"is_preorder\":false,\"is_reorder\":false},"
+ + "\"reference\":\"ORD-5023-4E89\","
+ + "\"card\":{\"instrument_id\":\"src_w4jelhppmfiufdnatndh3wtsfq\",\"fingerprint\":\"fp-1\"},"
+ + "\"recurring\":{\"days_between_payments\":30,\"expiry\":\"99991231\"},"
+ + "\"installment\":{\"number_of_payments\":3,\"days_between_payments\":30,\"expiry\":\"99991231\"},"
+ + "\"initial_transaction\":{\"acs_transaction_id\":\"acs-txn-id\","
+ + "\"authentication_method\":\"frictionless\","
+ + "\"authentication_timestamp\":\"2026-08-03T10:11:12Z\","
+ + "\"authentication_data\":\"auth-data\","
+ + "\"initial_session_id\":\"sid_y3oqhf46pyzuxjbcn2giaqnb44\"},"
+ + "\"authentication_date\":\"2026-08-03T10:11:12Z\","
+ + "\"challenge_indicator\":\"transaction_risk_assessment\","
+ + "\"optimization\":{\"optimized\":true,\"framework\":\"acceptance_rates\","
+ + "\"optimized_properties\":[{\"field\":\"amount\",\"original_value\":\"1\",\"optimized_value\":\"2\"}]},"
+ + "\"_links\":{\"self\":{\"href\":\"https://api.checkout.com/sessions/sid_y3oqhf46pyzuxjbcn2giaqnb44\"}}"
+ + "}";
+ }
+
+ @BeforeEach
+ void setUp() {
+ response = serializer.fromJson(fullPayload(), CreateSessionAcceptedResponse.class);
+ assertNotNull(response);
+ }
+
+ @Test
+ void shouldDeserializeIdentifiersAndAmounts() {
+ assertEquals("sid_y3oqhf46pyzuxjbcn2giaqnb44", response.getId());
+ assertEquals("sek_Dal7UyiH8rIFXA4PfgiIk2jUyQkVDeEWgVBEL4TsRTE=", response.getSessionSecret());
+ assertEquals("9aea641d-0549-4222-9ca9-d90b43a4f38c", response.getTransactionId());
+ assertEquals(6540L, response.getAmount());
+ assertEquals(Currency.USD, response.getCurrency());
+ assertEquals("2.2.0", response.getProtocolVersion());
+ assertEquals("ORD-5023-4E89", response.getReference());
+ }
+
+ @Test
+ void shouldDeserializeEveryEnumTypedProperty() {
+ assertEquals(SessionScheme.MASTERCARD, response.getScheme());
+ assertEquals(AuthenticationType.REGULAR, response.getAuthenticationType());
+ assertEquals(Category.PAYMENT, response.getAuthenticationCategory());
+ assertEquals(SessionStatus.PENDING, response.getStatus());
+ assertEquals(StatusReason.ARES_STATUS, response.getStatusReason());
+ assertEquals(SessionChallengeIndicator.TRANSACTION_RISK_ASSESSMENT, response.getChallengeIndicator());
+ assertEquals(Arrays.asList(NextAction.COLLECT_CHANNEL_DATA), response.getNextActions());
+ }
+
+ @Test
+ void shouldDeserializeAuthenticationDateAsInstant() {
+ assertEquals(Instant.parse("2026-08-03T10:11:12Z"), response.getAuthenticationDate());
+ }
+
+ /**
+ * This class names the field {@code accountInfo} and relies on the global
+ * {@code LOWER_CASE_WITH_UNDERSCORES} policy, whereas {@link GetSessionResponse} names it
+ * {@code cardholderAccountInfo} with an explicit {@code @SerializedName}. Both must map the same
+ * {@code account_info} wire key.
+ */
+ @Test
+ void shouldDeserializeAccountInfoUnderTheAccountInfoKey() {
+ assertNotNull(response.getAccountInfo());
+ assertEquals(10L, response.getAccountInfo().getPurchaseCount());
+ assertEquals(5L, response.getAccountInfo().getAddCardAttempts());
+ assertEquals(3L, response.getAccountInfo().getTransactionsToday());
+ }
+
+ @Test
+ void shouldDeserializeNestedObjects() {
+ assertNotNull(response.getMerchantRiskInfo());
+ assertEquals("bruce@wayne-enterprises.com", response.getMerchantRiskInfo().getDeliveryEmail());
+
+ assertNotNull(response.getCard());
+ assertEquals("src_w4jelhppmfiufdnatndh3wtsfq", response.getCard().getInstrumentId());
+ assertEquals("fp-1", response.getCard().getFingerprint());
+
+ assertNotNull(response.getRecurring());
+ assertEquals(30L, response.getRecurring().getDaysBetweenPayments());
+
+ assertNotNull(response.getInstallment());
+ assertEquals(3L, response.getInstallment().getNumberOfPayments());
+
+ assertNotNull(response.getInitialTransaction());
+ assertEquals("acs-txn-id", response.getInitialTransaction().getAcsTransactionId());
+
+ assertNotNull(response.getOptimization());
+ assertEquals(Boolean.TRUE, response.getOptimization().getOptimized());
+ assertEquals("acceptance_rates", response.getOptimization().getFramework());
+ assertNotNull(response.getOptimization().getOptimizedProperties());
+ assertEquals(1, response.getOptimization().getOptimizedProperties().size());
+ }
+
+ @Test
+ void shouldDeserializeInheritedLinks() {
+ assertNotNull(response.getSelfLink());
+ assertEquals("https://api.checkout.com/sessions/sid_y3oqhf46pyzuxjbcn2giaqnb44",
+ response.getSelfLink().getHref());
+ }
+
+ /**
+ * Guards against a property being silently dropped: every field declared on the class must be
+ * non-null after deserializing a payload that populates all of them.
+ */
+ @Test
+ void shouldLeaveNoDeclaredPropertyNull() {
+ final java.lang.reflect.Field[] fields = CreateSessionAcceptedResponse.class.getDeclaredFields();
+ for (final java.lang.reflect.Field field : fields) {
+ if (field.isSynthetic()) {
+ continue;
+ }
+ field.setAccessible(true);
+ try {
+ assertNotNull(field.get(response), "property was not deserialized: " + field.getName());
+ } catch (final IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ assertTrue(fields.length >= 22, "expected at least 22 declared properties, found " + fields.length);
+ }
+
+}
diff --git a/src/test/java/com/checkout/sessions/GetSessionResponseSerializationTest.java b/src/test/java/com/checkout/sessions/GetSessionResponseSerializationTest.java
new file mode 100644
index 00000000..5118c79f
--- /dev/null
+++ b/src/test/java/com/checkout/sessions/GetSessionResponseSerializationTest.java
@@ -0,0 +1,254 @@
+package com.checkout.sessions;
+
+import com.checkout.GsonSerializer;
+import com.checkout.Serializer;
+import com.checkout.common.CardCategory;
+import com.checkout.common.CardType;
+import com.checkout.common.CountryCode;
+import com.checkout.common.Currency;
+import com.checkout.common.Exemption;
+import com.checkout.common.ThreeDSFlowType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Full-property deserialization coverage for {@link GetSessionResponse}, the schema returned by
+ * {@code GET /sessions/{id}} and {@code PUT /sessions/{id}/collect-data}.
+ *
+ * Every one of the class's 40 properties is populated and asserted, plus the {@code _links} it
+ * inherits from {@link com.checkout.common.Resource}. Nested objects are covered transitively.
+ */
+class GetSessionResponseSerializationTest {
+
+ private final Serializer serializer = new GsonSerializer();
+
+ private GetSessionResponse response;
+
+ private static String fullPayload() {
+ return "{"
+ + "\"id\":\"sid_y3oqhf46pyzuxjbcn2giaqnb44\","
+ + "\"session_secret\":\"sek_Dal7UyiH8rIFXA4PfgiIk2jUyQkVDeEWgVBEL4TsRTE=\","
+ + "\"transaction_id\":\"9aea641d-0549-4222-9ca9-d90b43a4f38c\","
+ + "\"scheme\":\"visa\","
+ + "\"amount\":6540,"
+ + "\"currency\":\"USD\","
+ + "\"completed\":true,"
+ + "\"challenged\":true,"
+ + "\"authentication_type\":\"regular\","
+ + "\"authentication_category\":\"payment\","
+ + "\"certificates\":{\"ds_public\":\"ds-public-key\",\"ca_public\":\"ca-public-key\"},"
+ + "\"status\":\"challenged\","
+ + "\"status_reason\":\"ares_status\","
+ + "\"approved\":true,"
+ + "\"protocol_version\":\"2.2.0\","
+ + "\"account_info\":{\"purchase_count\":10,\"add_card_attempts\":5,\"transactions_today\":3},"
+ + "\"merchant_risk_info\":{\"delivery_email\":\"bruce@wayne-enterprises.com\","
+ + "\"is_preorder\":false,\"is_reorder\":false},"
+ + "\"reference\":\"ORD-5023-4E89\","
+ + "\"transaction_type\":\"goods_service\","
+ + "\"next_actions\":[\"collect_channel_data\",\"challenge_cardholder\"],"
+ + "\"ds\":{\"ds_id\":\"ds-id\",\"reference_number\":\"ds-ref\",\"transaction_id\":\"ds-txn\"},"
+ + "\"acs\":{\"reference_number\":\"acs-ref\",\"transaction_id\":\"acs-txn\","
+ + "\"operator_id\":\"acs-operator\",\"url\":\"https://acs.example.com/challenge\","
+ + "\"signed_content\":\"signed-content\",\"challenge_mandated\":true,"
+ + "\"authentication_type\":\"static\",\"challenge_cancel_reason\":\"cardholder_cancel\","
+ + "\"interface\":\"html\",\"ui_template\":\"single_select\"},"
+ + "\"response_code\":\"Y\","
+ + "\"response_status_reason\":\"01\","
+ + "\"pareq\":\"pareq-value\","
+ + "\"cryptogram\":\"MTIzNDU2Nzg5MDA5ODc2NTQzMjE=\","
+ + "\"eci\":\"05\","
+ + "\"xid\":\"XSUErNftqkiTdlkpSk8p32GWOFA\","
+ + "\"cardholder_info\":\"Card declined. Please contact your issuing bank.\","
+ + "\"card\":{\"instrument_id\":\"src_w4jelhppmfiufdnatndh3wtsfq\",\"fingerprint\":\"fp-1\","
+ + "\"metadata\":{\"card_type\":\"CREDIT\",\"card_category\":\"CONSUMER\","
+ + "\"issuer_name\":\"Checkout\",\"issuer_country\":\"GB\","
+ + "\"product_id\":\"MDS\",\"product_type\":\"Debit MasterCard Card\"}},"
+ + "\"recurring\":{\"days_between_payments\":30,\"expiry\":\"99991231\"},"
+ + "\"installment\":{\"number_of_payments\":3,\"days_between_payments\":30,\"expiry\":\"99991231\"},"
+ + "\"initial_transaction\":{\"acs_transaction_id\":\"acs-txn-id\","
+ + "\"authentication_method\":\"frictionless\","
+ + "\"authentication_timestamp\":\"2026-08-03T10:11:12Z\","
+ + "\"authentication_data\":\"auth-data\","
+ + "\"initial_session_id\":\"sid_y3oqhf46pyzuxjbcn2giaqnb44\"},"
+ + "\"customer_ip\":\"192.168.1.1\","
+ + "\"authentication_date\":\"2026-08-03T10:11:12Z\","
+ + "\"exemption\":{\"requested\":\"none\",\"applied\":\"low_value\",\"code\":\"cb-code\"},"
+ + "\"flow_type\":\"challenged\","
+ + "\"challenge_indicator\":\"trusted_listing\","
+ + "\"optimization\":{\"optimized\":true,\"framework\":\"acceptance_rates\","
+ + "\"optimized_properties\":[{\"field\":\"amount\",\"original_value\":\"1\",\"optimized_value\":\"2\"}]},"
+ + "\"scheme_info\":{\"name\":\"visa\",\"score\":\"0.5\",\"avalgo\":\"1\"},"
+ + "\"_links\":{\"self\":{\"href\":\"https://api.checkout.com/sessions/sid_y3oqhf46pyzuxjbcn2giaqnb44\"}}"
+ + "}";
+ }
+
+ @BeforeEach
+ void setUp() {
+ response = serializer.fromJson(fullPayload(), GetSessionResponse.class);
+ assertNotNull(response);
+ }
+
+ @Test
+ void shouldDeserializeIdentifiersAndAmounts() {
+ assertEquals("sid_y3oqhf46pyzuxjbcn2giaqnb44", response.getId());
+ assertEquals("sek_Dal7UyiH8rIFXA4PfgiIk2jUyQkVDeEWgVBEL4TsRTE=", response.getSessionSecret());
+ assertEquals("9aea641d-0549-4222-9ca9-d90b43a4f38c", response.getTransactionId());
+ assertEquals(6540L, response.getAmount());
+ assertEquals(Currency.USD, response.getCurrency());
+ assertEquals("2.2.0", response.getProtocolVersion());
+ assertEquals("ORD-5023-4E89", response.getReference());
+ }
+
+ @Test
+ void shouldDeserializeEveryEnumTypedProperty() {
+ assertEquals(SessionScheme.VISA, response.getScheme());
+ assertEquals(AuthenticationType.REGULAR, response.getAuthenticationType());
+ assertEquals(Category.PAYMENT, response.getAuthenticationCategory());
+ assertEquals(SessionStatus.CHALLENGED, response.getStatus());
+ assertEquals(StatusReason.ARES_STATUS, response.getStatusReason());
+ assertEquals(TransactionType.GOODS_SERVICE, response.getTransactionType());
+ assertEquals(ResponseCode.Y, response.getResponseCode());
+ assertEquals(ThreeDSFlowType.CHALLENGED, response.getFlowType());
+ assertEquals(SessionChallengeIndicator.TRUSTED_LISTING, response.getChallengeIndicator());
+ assertEquals(Arrays.asList(NextAction.COLLECT_CHANNEL_DATA, NextAction.CHALLENGE_CARDHOLDER),
+ response.getNextActions());
+ }
+
+ @Test
+ void shouldDeserializeBooleanFlags() {
+ assertEquals(Boolean.TRUE, response.getCompleted());
+ assertEquals(Boolean.TRUE, response.getChallenged());
+ assertEquals(Boolean.TRUE, response.getApproved());
+ }
+
+ @Test
+ void shouldDeserializeAuthenticationResultFields() {
+ assertEquals("01", response.getResponseStatusReason());
+ assertEquals("pareq-value", response.getPareq());
+ assertEquals("MTIzNDU2Nzg5MDA5ODc2NTQzMjE=", response.getCryptogram());
+ assertEquals("05", response.getEci());
+ assertEquals("XSUErNftqkiTdlkpSk8p32GWOFA", response.getXid());
+ assertEquals("Card declined. Please contact your issuing bank.", response.getCardholderInfo());
+ assertEquals("192.168.1.1", response.getCustomerIp());
+ }
+
+ @Test
+ void shouldDeserializeAuthenticationDateAsInstant() {
+ assertEquals(Instant.parse("2026-08-03T10:11:12Z"), response.getAuthenticationDate());
+ }
+
+ @Test
+ void shouldDeserializeAccountInfoUnderTheAccountInfoKey() {
+ assertNotNull(response.getCardholderAccountInfo());
+ assertEquals(10L, response.getCardholderAccountInfo().getPurchaseCount());
+ assertEquals(5L, response.getCardholderAccountInfo().getAddCardAttempts());
+ assertEquals(3L, response.getCardholderAccountInfo().getTransactionsToday());
+ }
+
+ @Test
+ void shouldDeserializeNestedObjects() {
+ assertNotNull(response.getCertificates());
+ assertEquals("ds-public-key", response.getCertificates().getDsPublic());
+ assertEquals("ca-public-key", response.getCertificates().getCaPublic());
+
+ assertNotNull(response.getMerchantRiskInfo());
+ assertEquals("bruce@wayne-enterprises.com", response.getMerchantRiskInfo().getDeliveryEmail());
+
+ assertNotNull(response.getDs());
+ assertEquals("ds-id", response.getDs().getDsId());
+
+ assertNotNull(response.getCard());
+ assertEquals("src_w4jelhppmfiufdnatndh3wtsfq", response.getCard().getInstrumentId());
+ assertEquals("fp-1", response.getCard().getFingerprint());
+
+ assertNotNull(response.getRecurring());
+ assertEquals(30L, response.getRecurring().getDaysBetweenPayments());
+
+ assertNotNull(response.getInstallment());
+ assertEquals(3L, response.getInstallment().getNumberOfPayments());
+
+ assertNotNull(response.getInitialTransaction());
+ assertEquals("acs-txn-id", response.getInitialTransaction().getAcsTransactionId());
+
+ assertNotNull(response.getSchemeInfo());
+ assertEquals(SessionScheme.VISA, response.getSchemeInfo().getName());
+
+ assertNotNull(response.getOptimization());
+ assertEquals(Boolean.TRUE, response.getOptimization().getOptimized());
+ assertNotNull(response.getOptimization().getOptimizedProperties());
+ assertEquals(1, response.getOptimization().getOptimizedProperties().size());
+ }
+
+ @Test
+ void shouldDeserializeAcsWithReservedInterfaceKey() {
+ assertNotNull(response.getAcs());
+ assertEquals("acs-ref", response.getAcs().getReferenceNumber());
+ assertEquals("https://acs.example.com/challenge", response.getAcs().getUrl());
+ assertEquals(Boolean.TRUE, response.getAcs().getChallengeMandated());
+ assertEquals(ChallengeCancelReason.CARDHOLDER_CANCEL, response.getAcs().getChallengeCancelReason());
+ assertEquals(SessionInterface.HTML, response.getAcs().getSessionInterface());
+ assertEquals(UIElements.SINGLE_SELECT, response.getAcs().getUiTemplate());
+ }
+
+ /**
+ * Covers all six properties of {@link SessionsCardMetadataResponse}. The spec sends the card
+ * type and category in upper case, which the shared enums accept via {@code alternate} values.
+ */
+ @Test
+ void shouldDeserializeEveryCardMetadataProperty() {
+ final SessionsCardMetadataResponse metadata = response.getCard().getMetadata();
+
+ assertNotNull(metadata);
+ assertEquals(CardType.CREDIT, metadata.getCardType());
+ assertEquals(CardCategory.CONSUMER, metadata.getCardCategory());
+ assertEquals("Checkout", metadata.getIssuerName());
+ assertEquals(CountryCode.GB, metadata.getIssuerCountry());
+ assertEquals("MDS", metadata.getProductId());
+ assertEquals("Debit MasterCard Card", metadata.getProductType());
+ }
+
+ @Test
+ void shouldDeserializeExemption() {
+ assertNotNull(response.getExemption());
+ assertEquals("none", response.getExemption().getRequested());
+ assertEquals(Exemption.LOW_VALUE, response.getExemption().getApplied());
+ assertEquals("cb-code", response.getExemption().getCode());
+ }
+
+ @Test
+ void shouldDeserializeInheritedLinks() {
+ assertNotNull(response.getSelfLink());
+ assertEquals("https://api.checkout.com/sessions/sid_y3oqhf46pyzuxjbcn2giaqnb44",
+ response.getSelfLink().getHref());
+ }
+
+ /**
+ * Guards against a property being silently dropped: every field declared on the class must be
+ * non-null after deserializing a payload that populates all of them.
+ */
+ @Test
+ void shouldLeaveNoDeclaredPropertyNull() {
+ final java.lang.reflect.Field[] fields = GetSessionResponse.class.getDeclaredFields();
+ for (final java.lang.reflect.Field field : fields) {
+ if (field.isSynthetic()) {
+ continue;
+ }
+ field.setAccessible(true);
+ try {
+ assertNotNull(field.get(response), "property was not deserialized: " + field.getName());
+ } catch (final IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ assertTrue(fields.length >= 40, "expected at least 40 declared properties, found " + fields.length);
+ }
+
+}
diff --git a/src/test/java/com/checkout/sessions/RequestAndGetSessionsTestIT.java b/src/test/java/com/checkout/sessions/RequestAndGetSessionsTestIT.java
index a3ac5285..b2a8eb72 100644
--- a/src/test/java/com/checkout/sessions/RequestAndGetSessionsTestIT.java
+++ b/src/test/java/com/checkout/sessions/RequestAndGetSessionsTestIT.java
@@ -1,6 +1,5 @@
package com.checkout.sessions;
-import com.checkout.common.ChallengeIndicator;
import com.checkout.sessions.channel.ChannelData;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
@@ -18,32 +17,32 @@ class RequestAndGetSessionsTestIT extends AbstractSessionsTestIT {
private static Stream