From 7007e3a4aaf8191e295a0f8e995a68d7539253e4 Mon Sep 17 00:00:00 2001 From: david ruiz Date: Fri, 24 Jul 2026 12:56:38 +0200 Subject: [PATCH 1/5] Accept header mandatory + schema 3.0 initial development --- src/main/java/com/checkout/ApiClient.java | 8 + src/main/java/com/checkout/ApiClientImpl.java | 30 ++++ .../com/checkout/accounts/AccountsClient.java | 16 ++ .../checkout/accounts/AccountsClientImpl.java | 72 +++++++- .../com/checkout/accounts/AgreedTerms.java | 24 +++ .../com/checkout/accounts/BusinessType.java | 26 ++- .../java/com/checkout/accounts/Company.java | 4 + .../checkout/accounts/CompanyPosition.java | 29 ++++ .../accounts/DateOfIncorporation.java | 2 + .../com/checkout/accounts/EntityRoles.java | 6 +- .../accounts/FinancialStatements.java | 18 ++ .../accounts/FinancialStatementsType.java | 9 + .../java/com/checkout/accounts/Headers.java | 6 + .../accounts/OnboardEntityRequest.java | 7 + .../accounts/OnboardSubEntityDocuments.java | 2 + .../checkout/accounts/ProcessingDetails.java | 4 + .../accounts/ProcessingDetailsAch.java | 22 +++ .../accounts/ProcessingDetailsPayments.java | 16 ++ .../com/checkout/accounts/Representative.java | 40 +++++ .../accounts/RepresentativeIndividual.java | 33 ++++ .../accounts/AccountsClientImplTest.java | 72 +++++++- .../accounts/AccountsV3SerializationTest.java | 161 ++++++++++++++++++ 22 files changed, 589 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/checkout/accounts/AgreedTerms.java create mode 100644 src/main/java/com/checkout/accounts/CompanyPosition.java create mode 100644 src/main/java/com/checkout/accounts/FinancialStatements.java create mode 100644 src/main/java/com/checkout/accounts/FinancialStatementsType.java create mode 100644 src/main/java/com/checkout/accounts/ProcessingDetailsAch.java create mode 100644 src/main/java/com/checkout/accounts/ProcessingDetailsPayments.java create mode 100644 src/main/java/com/checkout/accounts/RepresentativeIndividual.java create mode 100644 src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java diff --git a/src/main/java/com/checkout/ApiClient.java b/src/main/java/com/checkout/ApiClient.java index 4a3b6390d..536a53bf3 100644 --- a/src/main/java/com/checkout/ApiClient.java +++ b/src/main/java/com/checkout/ApiClient.java @@ -12,8 +12,12 @@ public interface ApiClient { CompletableFuture getAsync(String path, SdkAuthorization authorization, Type responseType); + CompletableFuture getAsync(String path, SdkAuthorization authorization, Class responseType, IHeaders headers); + CompletableFuture putAsync(String path, SdkAuthorization authorization, Class responseType, Object request); + CompletableFuture putAsync(String path, SdkAuthorization authorization, Class responseType, Object request, IHeaders headers); + CompletableFuture postAsync(String path, SdkAuthorization authorization, Class responseType, Object request, String idempotencyKey); CompletableFuture postAsync(String path, SdkAuthorization authorization, Class responseType, Object request, String idempotencyKey, IHeaders headers); @@ -41,8 +45,12 @@ public interface ApiClient { T get(String path, SdkAuthorization authorization, Type responseType); + T get(String path, SdkAuthorization authorization, Class responseType, IHeaders headers); + T put(String path, SdkAuthorization authorization, Class responseType, Object request); + T put(String path, SdkAuthorization authorization, Class responseType, Object request, IHeaders headers); + T patch(String path, SdkAuthorization authorization, Class responseType, Object request, String idempotencyKey); T patch(String path, SdkAuthorization authorization, Type type, Object request, String idempotencyKey); diff --git a/src/main/java/com/checkout/ApiClientImpl.java b/src/main/java/com/checkout/ApiClientImpl.java index ff20b5423..c1bfff0d8 100644 --- a/src/main/java/com/checkout/ApiClientImpl.java +++ b/src/main/java/com/checkout/ApiClientImpl.java @@ -59,6 +59,15 @@ public CompletableFuture getAsync(final String path, ); } + @Override + public CompletableFuture getAsync(final String path, final SdkAuthorization authorization, final Class responseType, final IHeaders headers) { + validateParams(PATH, path, AUTHORIZATION, authorization); + return executeAsyncOrSync( + () -> get(path, authorization, responseType, headers), + () -> sendRequestAsync(GET, path, authorization, null, null, responseType, headers) + ); + } + @Override public CompletableFuture putAsync(final String path, final SdkAuthorization authorization, final Class responseType, final Object request) { validateParams(PATH, path, AUTHORIZATION, authorization); @@ -68,6 +77,15 @@ public CompletableFuture putAsync(final String path, ); } + @Override + public CompletableFuture putAsync(final String path, final SdkAuthorization authorization, final Class responseType, final Object request, final IHeaders headers) { + validateParams(PATH, path, AUTHORIZATION, authorization); + return executeAsyncOrSync( + () -> put(path, authorization, responseType, request, headers), + () -> sendRequestAsync(PUT, path, authorization, request, null, responseType, headers) + ); + } + @Override public CompletableFuture patchAsync(final String path, final SdkAuthorization authorization, final Class responseType, final Object request, final String idempotencyKey) { validateParams(PATH, path, AUTHORIZATION, authorization); @@ -326,12 +344,24 @@ public T get(final String path, final SdkAuthorization return sendRequestSync(GET, path, authorization, null, null, responseType); } + @Override + public T get(final String path, final SdkAuthorization authorization, final Class responseType, final IHeaders headers) { + validateParams(PATH, path, AUTHORIZATION, authorization); + return sendRequestSync(GET, path, authorization, null, null, responseType, headers); + } + @Override public T put(final String path, final SdkAuthorization authorization, final Class responseType, final Object request) { validateParams(PATH, path, AUTHORIZATION, authorization); return sendRequestSync(PUT, path, authorization, request, null, responseType); } + @Override + public T put(final String path, final SdkAuthorization authorization, final Class responseType, final Object request, final IHeaders headers) { + validateParams(PATH, path, AUTHORIZATION, authorization); + return sendRequestSync(PUT, path, authorization, request, null, responseType, headers); + } + @Override public T patch(final String path, final SdkAuthorization authorization, final Class responseType, final Object request, final String idempotencyKey) { validateParams(PATH, path, AUTHORIZATION, authorization); diff --git a/src/main/java/com/checkout/accounts/AccountsClient.java b/src/main/java/com/checkout/accounts/AccountsClient.java index 4854d5d76..f9a1a9baa 100644 --- a/src/main/java/com/checkout/accounts/AccountsClient.java +++ b/src/main/java/com/checkout/accounts/AccountsClient.java @@ -26,12 +26,18 @@ public interface AccountsClient { CompletableFuture createEntity(OnboardEntityRequest entityRequest); + CompletableFuture createEntity(OnboardEntityRequest entityRequest, String schemaVersion); + CompletableFuture retrievePaymentInstrumentDetails(String entityId, String paymentInstrumentId); CompletableFuture getEntity(String entityId); + CompletableFuture getEntity(String entityId, String schemaVersion); + CompletableFuture updateEntity(OnboardEntityRequest entityRequest, String entityId); + CompletableFuture updateEntity(OnboardEntityRequest entityRequest, String entityId, String schemaVersion); + /** * @deprecated Use {{@link #createPaymentInstrument(String, PaymentInstrumentRequest)}} instead */ @@ -71,6 +77,8 @@ CompletableFuture updateReserveRule(String entityId, */ CompletableFuture getEntityRequirements(String entityId); + CompletableFuture getEntityRequirements(String entityId, String schemaVersion); + /** * Retrieves detailed information for a single requirement. * @@ -99,12 +107,18 @@ CompletableFuture updateReserveRule(String entityId, OnboardEntityResponse createEntitySync(final OnboardEntityRequest entityRequest); + OnboardEntityResponse createEntitySync(final OnboardEntityRequest entityRequest, final String schemaVersion); + PaymentInstrumentDetailsResponse retrievePaymentInstrumentDetailsSync(final String entityId, final String paymentInstrumentId); OnboardEntityDetailsResponse getEntitySync(final String entityId); + OnboardEntityDetailsResponse getEntitySync(final String entityId, final String schemaVersion); + OnboardEntityResponse updateEntitySync(final OnboardEntityRequest entityRequest, final String entityId); + OnboardEntityResponse updateEntitySync(final OnboardEntityRequest entityRequest, final String entityId, final String schemaVersion); + EmptyResponse createPaymentInstrumentSync(final AccountsPaymentInstrument accountsPaymentInstrument, final String entityId); IdResponse createPaymentInstrumentSync(final String entityId, final PaymentInstrumentRequest paymentInstrumentRequest); @@ -135,6 +149,8 @@ IdResponse updatePaymentInstrumentDetailsSync(final String entityId, */ EntityRequirementListResponse getEntityRequirementsSync(final String entityId); + EntityRequirementListResponse getEntityRequirementsSync(final String entityId, final String schemaVersion); + /** * Synchronous variant of {@link #getEntityRequirementDetails(String, String)}. */ diff --git a/src/main/java/com/checkout/accounts/AccountsClientImpl.java b/src/main/java/com/checkout/accounts/AccountsClientImpl.java index b42d43c18..bac081b8e 100644 --- a/src/main/java/com/checkout/accounts/AccountsClientImpl.java +++ b/src/main/java/com/checkout/accounts/AccountsClientImpl.java @@ -36,8 +36,16 @@ public class AccountsClientImpl extends AbstractClient implements AccountsClient private static final String RESERVE_RULES_PATH = "reserve-rules"; private static final String REQUIREMENTS_PATH = "requirements"; + private static final String DEFAULT_SCHEMA_VERSION = "3.0"; + private final ApiClient filesClient; + private static Headers buildSchemaVersionHeaders(final String schemaVersion) { + return Headers.builder() + .accept("application/json;schema_version=" + schemaVersion) + .build(); + } + public AccountsClientImpl(final ApiClient apiClient, final ApiClient filesClient, final CheckoutConfiguration configuration) { @@ -77,13 +85,19 @@ public CompletableFuture retrieveFile(final String entityId @Override public CompletableFuture createEntity(final OnboardEntityRequest entityRequest) { + return createEntity(entityRequest, DEFAULT_SCHEMA_VERSION); + } + + @Override + public CompletableFuture createEntity(final OnboardEntityRequest entityRequest, final String schemaVersion) { validateEntityRequest(entityRequest); return apiClient.postAsync( buildPath(ACCOUNTS_PATH, ENTITIES_PATH), sdkAuthorization(), OnboardEntityResponse.class, entityRequest, - null); + null, + buildSchemaVersionHeaders(schemaVersion)); } @Override @@ -97,21 +111,33 @@ public CompletableFuture retrievePaymentInstru @Override public CompletableFuture getEntity(final String entityId) { + return getEntity(entityId, DEFAULT_SCHEMA_VERSION); + } + + @Override + public CompletableFuture getEntity(final String entityId, final String schemaVersion) { validateEntityId(entityId); return apiClient.getAsync( buildPath(ACCOUNTS_PATH, ENTITIES_PATH, entityId), sdkAuthorization(), - OnboardEntityDetailsResponse.class); + OnboardEntityDetailsResponse.class, + buildSchemaVersionHeaders(schemaVersion)); } @Override public CompletableFuture updateEntity(final OnboardEntityRequest entityRequest, final String entityId) { + return updateEntity(entityRequest, entityId, DEFAULT_SCHEMA_VERSION); + } + + @Override + public CompletableFuture updateEntity(final OnboardEntityRequest entityRequest, final String entityId, final String schemaVersion) { validateEntityRequestAndId(entityRequest, entityId); return apiClient.putAsync( buildPath(ACCOUNTS_PATH, ENTITIES_PATH, entityId), sdkAuthorization(), OnboardEntityResponse.class, - entityRequest); + entityRequest, + buildSchemaVersionHeaders(schemaVersion)); } @Override @@ -243,11 +269,17 @@ public CompletableFuture getReserveRules(final String enti @Override public CompletableFuture getEntityRequirements(final String entityId) { + return getEntityRequirements(entityId, DEFAULT_SCHEMA_VERSION); + } + + @Override + public CompletableFuture getEntityRequirements(final String entityId, final String schemaVersion) { validateEntityId(entityId); return apiClient.getAsync( buildPath(ACCOUNTS_PATH, ENTITIES_PATH, entityId, REQUIREMENTS_PATH), sdkAuthorization(), - EntityRequirementListResponse.class); + EntityRequirementListResponse.class, + buildSchemaVersionHeaders(schemaVersion)); } @Override @@ -302,13 +334,19 @@ public FileDetailsResponse retrieveFileSync(final String entityId, final String @Override public OnboardEntityResponse createEntitySync(final OnboardEntityRequest entityRequest) { + return createEntitySync(entityRequest, DEFAULT_SCHEMA_VERSION); + } + + @Override + public OnboardEntityResponse createEntitySync(final OnboardEntityRequest entityRequest, final String schemaVersion) { validateEntityRequest(entityRequest); return apiClient.post( buildPath(ACCOUNTS_PATH, ENTITIES_PATH), sdkAuthorization(), OnboardEntityResponse.class, entityRequest, - null); + null, + buildSchemaVersionHeaders(schemaVersion)); } @Override @@ -322,21 +360,33 @@ public PaymentInstrumentDetailsResponse retrievePaymentInstrumentDetailsSync(fin @Override public OnboardEntityDetailsResponse getEntitySync(final String entityId) { + return getEntitySync(entityId, DEFAULT_SCHEMA_VERSION); + } + + @Override + public OnboardEntityDetailsResponse getEntitySync(final String entityId, final String schemaVersion) { validateEntityId(entityId); return apiClient.get( buildPath(ACCOUNTS_PATH, ENTITIES_PATH, entityId), sdkAuthorization(), - OnboardEntityDetailsResponse.class); + OnboardEntityDetailsResponse.class, + buildSchemaVersionHeaders(schemaVersion)); } @Override public OnboardEntityResponse updateEntitySync(final OnboardEntityRequest entityRequest, final String entityId) { + return updateEntitySync(entityRequest, entityId, DEFAULT_SCHEMA_VERSION); + } + + @Override + public OnboardEntityResponse updateEntitySync(final OnboardEntityRequest entityRequest, final String entityId, final String schemaVersion) { validateEntityRequestAndId(entityRequest, entityId); return apiClient.put( buildPath(ACCOUNTS_PATH, ENTITIES_PATH, entityId), sdkAuthorization(), OnboardEntityResponse.class, - entityRequest); + entityRequest, + buildSchemaVersionHeaders(schemaVersion)); } @Override @@ -465,11 +515,17 @@ public ReserveRulesResponse getReserveRulesSync(final String entityId) { @Override public EntityRequirementListResponse getEntityRequirementsSync(final String entityId) { + return getEntityRequirementsSync(entityId, DEFAULT_SCHEMA_VERSION); + } + + @Override + public EntityRequirementListResponse getEntityRequirementsSync(final String entityId, final String schemaVersion) { validateEntityId(entityId); return apiClient.get( buildPath(ACCOUNTS_PATH, ENTITIES_PATH, entityId, REQUIREMENTS_PATH), sdkAuthorization(), - EntityRequirementListResponse.class); + EntityRequirementListResponse.class, + buildSchemaVersionHeaders(schemaVersion)); } @Override diff --git a/src/main/java/com/checkout/accounts/AgreedTerms.java b/src/main/java/com/checkout/accounts/AgreedTerms.java new file mode 100644 index 000000000..4739b929f --- /dev/null +++ b/src/main/java/com/checkout/accounts/AgreedTerms.java @@ -0,0 +1,24 @@ +package com.checkout.accounts; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class AgreedTerms { + + private String date; + + private String ipAddress; + + private String name; + + private String email; + + private String version; + +} diff --git a/src/main/java/com/checkout/accounts/BusinessType.java b/src/main/java/com/checkout/accounts/BusinessType.java index 341e7686e..00f4c8cf1 100644 --- a/src/main/java/com/checkout/accounts/BusinessType.java +++ b/src/main/java/com/checkout/accounts/BusinessType.java @@ -17,5 +17,29 @@ public enum BusinessType { @SerializedName("unincorporated_association") UNINCORPORATED_ASSOCIATION, @SerializedName("auto_entrepreneur") - AUTO_ENTREPRENEUR + AUTO_ENTREPRENEUR, + @SerializedName("individual_or_sole_proprietorship") + INDIVIDUAL_OR_SOLE_PROPRIETORSHIP, + @SerializedName("scottish_limited_partnership") + SCOTTISH_LIMITED_PARTNERSHIP, + @SerializedName("limited_liability_corporation") + LIMITED_LIABILITY_CORPORATION, + @SerializedName("private_corporation") + PRIVATE_CORPORATION, + @SerializedName("publicly_traded_corporation") + PUBLICLY_TRADED_CORPORATION, + @SerializedName("government_agency") + GOVERNMENT_AGENCY, + @SerializedName("non_profit_entity") + NON_PROFIT_ENTITY, + @SerializedName("trust") + TRUST, + @SerializedName("club_or_society") + CLUB_OR_SOCIETY, + @SerializedName("regulated_financial_institution") + REGULATED_FINANCIAL_INSTITUTION, + @SerializedName("cftc_registered_entity") + CFTC_REGISTERED_ENTITY, + @SerializedName("sec_registered_entity") + SEC_REGISTERED_ENTITY } diff --git a/src/main/java/com/checkout/accounts/Company.java b/src/main/java/com/checkout/accounts/Company.java index 5923a58ae..38a5a4540 100644 --- a/src/main/java/com/checkout/accounts/Company.java +++ b/src/main/java/com/checkout/accounts/Company.java @@ -36,4 +36,8 @@ public final class Company { private BusinessType businessType; + private List additionalTradingNames; + + private Boolean isRegisteredCompany; + } diff --git a/src/main/java/com/checkout/accounts/CompanyPosition.java b/src/main/java/com/checkout/accounts/CompanyPosition.java new file mode 100644 index 000000000..556a19a06 --- /dev/null +++ b/src/main/java/com/checkout/accounts/CompanyPosition.java @@ -0,0 +1,29 @@ +package com.checkout.accounts; + +import com.google.gson.annotations.SerializedName; + +public enum CompanyPosition { + + @SerializedName("ceo") + CEO, + @SerializedName("cfo") + CFO, + @SerializedName("coo") + COO, + @SerializedName("managing_member") + MANAGING_MEMBER, + @SerializedName("general_partner") + GENERAL_PARTNER, + @SerializedName("president") + PRESIDENT, + @SerializedName("vice_president") + VICE_PRESIDENT, + @SerializedName("treasurer") + TREASURER, + @SerializedName("other_senior_management") + OTHER_SENIOR_MANAGEMENT, + @SerializedName("other_executive_officer") + OTHER_EXECUTIVE_OFFICER, + @SerializedName("other_non_executive_non_senior") + OTHER_NON_EXECUTIVE_NON_SENIOR +} diff --git a/src/main/java/com/checkout/accounts/DateOfIncorporation.java b/src/main/java/com/checkout/accounts/DateOfIncorporation.java index 10b31253e..639462f9f 100644 --- a/src/main/java/com/checkout/accounts/DateOfIncorporation.java +++ b/src/main/java/com/checkout/accounts/DateOfIncorporation.java @@ -15,4 +15,6 @@ public final class DateOfIncorporation { private Integer year; + private Integer day; + } diff --git a/src/main/java/com/checkout/accounts/EntityRoles.java b/src/main/java/com/checkout/accounts/EntityRoles.java index 691e5eb0d..006599535 100644 --- a/src/main/java/com/checkout/accounts/EntityRoles.java +++ b/src/main/java/com/checkout/accounts/EntityRoles.java @@ -9,5 +9,9 @@ public enum EntityRoles { @SerializedName("legal_representative") LEGAL_REPRESENTATIVE, @SerializedName("authorised_signatory") - AUTHORISED_SIGNATORY + AUTHORISED_SIGNATORY, + @SerializedName("director") + DIRECTOR, + @SerializedName("control_person") + CONTROL_PERSON } diff --git a/src/main/java/com/checkout/accounts/FinancialStatements.java b/src/main/java/com/checkout/accounts/FinancialStatements.java new file mode 100644 index 000000000..a93a815a4 --- /dev/null +++ b/src/main/java/com/checkout/accounts/FinancialStatements.java @@ -0,0 +1,18 @@ +package com.checkout.accounts; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class FinancialStatements { + + private FinancialStatementsType type; + + private String front; + +} diff --git a/src/main/java/com/checkout/accounts/FinancialStatementsType.java b/src/main/java/com/checkout/accounts/FinancialStatementsType.java new file mode 100644 index 000000000..b373a10dc --- /dev/null +++ b/src/main/java/com/checkout/accounts/FinancialStatementsType.java @@ -0,0 +1,9 @@ +package com.checkout.accounts; + +import com.google.gson.annotations.SerializedName; + +public enum FinancialStatementsType { + + @SerializedName("financial_statements") + FINANCIAL_STATEMENTS +} diff --git a/src/main/java/com/checkout/accounts/Headers.java b/src/main/java/com/checkout/accounts/Headers.java index 9dce71ab0..85ec452f3 100644 --- a/src/main/java/com/checkout/accounts/Headers.java +++ b/src/main/java/com/checkout/accounts/Headers.java @@ -20,12 +20,18 @@ public class Headers implements IHeaders { @SerializedName("if-match") private String ifMatch; + @SerializedName("accept") + private String accept; + @Override public Map getHeaders() { final Map headers = new LinkedHashMap<>(); if (ifMatch != null) { headers.put("if-match", ifMatch); } + if (accept != null) { + headers.put("Accept", accept); + } return headers; } } diff --git a/src/main/java/com/checkout/accounts/OnboardEntityRequest.java b/src/main/java/com/checkout/accounts/OnboardEntityRequest.java index 0034e9897..850c91316 100644 --- a/src/main/java/com/checkout/accounts/OnboardEntityRequest.java +++ b/src/main/java/com/checkout/accounts/OnboardEntityRequest.java @@ -23,6 +23,11 @@ public final class OnboardEntityRequest { private ProcessingDetails processingDetails; + /** + * @deprecated Not used by the Accounts API v3.0 schema; a sub-entity is represented via + * {@link #company} (with representatives). Retained for v2.0 compatibility. + */ + @Deprecated private Individual individual; private OnboardSubEntityDocuments documents; @@ -45,4 +50,6 @@ public final class OnboardEntityRequest { */ private Submitter submitter; + private AgreedTerms agreedTerms; + } diff --git a/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java b/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java index df5ae5c9e..9e026dfe2 100644 --- a/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java +++ b/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java @@ -39,4 +39,6 @@ public final class OnboardSubEntityDocuments { private FinancialVerification financialVerification; + private FinancialStatements financialStatements; + } diff --git a/src/main/java/com/checkout/accounts/ProcessingDetails.java b/src/main/java/com/checkout/accounts/ProcessingDetails.java index 7f7d717ec..10b88969d 100644 --- a/src/main/java/com/checkout/accounts/ProcessingDetails.java +++ b/src/main/java/com/checkout/accounts/ProcessingDetails.java @@ -26,4 +26,8 @@ public final class ProcessingDetails { private Currency currency; + private Integer averageOrderFulfillmentTime; + + private ProcessingDetailsPayments payments; + } diff --git a/src/main/java/com/checkout/accounts/ProcessingDetailsAch.java b/src/main/java/com/checkout/accounts/ProcessingDetailsAch.java new file mode 100644 index 000000000..65a3ee657 --- /dev/null +++ b/src/main/java/com/checkout/accounts/ProcessingDetailsAch.java @@ -0,0 +1,22 @@ +package com.checkout.accounts; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class ProcessingDetailsAch { + + private Integer annualAchVolume; + + private Integer averageAchTransactionSize; + + private Integer estimatedMonthlyCreditVolume; + + private Integer averageCreditAmount; + +} diff --git a/src/main/java/com/checkout/accounts/ProcessingDetailsPayments.java b/src/main/java/com/checkout/accounts/ProcessingDetailsPayments.java new file mode 100644 index 000000000..e565d536f --- /dev/null +++ b/src/main/java/com/checkout/accounts/ProcessingDetailsPayments.java @@ -0,0 +1,16 @@ +package com.checkout.accounts; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class ProcessingDetailsPayments { + + private ProcessingDetailsAch ach; + +} diff --git a/src/main/java/com/checkout/accounts/Representative.java b/src/main/java/com/checkout/accounts/Representative.java index 753e749fd..9443d6f98 100644 --- a/src/main/java/com/checkout/accounts/Representative.java +++ b/src/main/java/com/checkout/accounts/Representative.java @@ -10,24 +10,64 @@ @Builder public final class Representative { + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private String firstName; + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private String middleName; + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private String lastName; + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private Address address; + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private Identification identification; + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private AccountPhone phone; + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private DateOfBirth dateOfBirth; + /** + * @deprecated Not used by the Accounts API v3.0 schema; use {@link #individual} instead. + */ + @Deprecated private PlaceOfBirth placeOfBirth; private List roles; private OnboardSubEntityDocuments documents; + private RepresentativeIndividual individual; + + private String id; + + private CompanyPosition companyPosition; + + private Integer ownershipPercentage; + } diff --git a/src/main/java/com/checkout/accounts/RepresentativeIndividual.java b/src/main/java/com/checkout/accounts/RepresentativeIndividual.java new file mode 100644 index 000000000..268219ee4 --- /dev/null +++ b/src/main/java/com/checkout/accounts/RepresentativeIndividual.java @@ -0,0 +1,33 @@ +package com.checkout.accounts; + +import com.checkout.common.Address; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class RepresentativeIndividual { + + private String firstName; + + private String middleName; + + private String lastName; + + private DateOfBirth dateOfBirth; + + private PlaceOfBirth placeOfBirth; + + private String nationalIdNumber; + + private String emailAddress; + + private AccountPhone phone; + + private Address address; + +} diff --git a/src/test/java/com/checkout/accounts/AccountsClientImplTest.java b/src/test/java/com/checkout/accounts/AccountsClientImplTest.java index bc6ad2198..9bd9558b7 100644 --- a/src/test/java/com/checkout/accounts/AccountsClientImplTest.java +++ b/src/test/java/com/checkout/accounts/AccountsClientImplTest.java @@ -60,6 +60,12 @@ class AccountsClientImplTest { private AccountsClient accountsClient; + private static final Headers SCHEMA_V3_HEADERS = + Headers.builder().accept("application/json;schema_version=3.0").build(); + + private static final Headers SCHEMA_V2_HEADERS = + Headers.builder().accept("application/json;schema_version=2.0").build(); + @BeforeEach void setUp() { lenient().when(sdkCredentials.getAuthorization(SdkAuthorizationType.SECRET_KEY_OR_OAUTH)).thenReturn(authorization); @@ -207,7 +213,7 @@ void shouldGetFileDetailsSync() { final OnboardEntityRequest request = createOnboardEntityRequest(); final OnboardEntityResponse expectedResponse = createOnboardEntityResponse(); - when(apiClient.postAsync(eq("accounts/entities"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), isNull())) + when(apiClient.postAsync(eq("accounts/entities"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), isNull(), eq(SCHEMA_V3_HEADERS))) .thenReturn(CompletableFuture.completedFuture(expectedResponse)); final CompletableFuture future = accountsClient.createEntity(request); @@ -219,7 +225,7 @@ void shouldGetFileDetailsSync() { void shouldGetEntity() throws ExecutionException, InterruptedException { final OnboardEntityDetailsResponse expectedResponse = createOnboardEntityDetailsResponse(); - when(apiClient.getAsync("accounts/entities/entity_id", authorization, OnboardEntityDetailsResponse.class)) + when(apiClient.getAsync("accounts/entities/entity_id", authorization, OnboardEntityDetailsResponse.class, SCHEMA_V3_HEADERS)) .thenReturn(CompletableFuture.completedFuture(expectedResponse)); final CompletableFuture future = accountsClient.getEntity("entity_id"); @@ -232,7 +238,7 @@ void shouldUpdateEntity() throws ExecutionException, InterruptedException { final OnboardEntityRequest request = createOnboardEntityRequest(); final OnboardEntityResponse expectedResponse = createOnboardEntityResponse(); - when(apiClient.putAsync(eq("accounts/entities/entity_id"), eq(authorization), eq(OnboardEntityResponse.class), eq(request))) + when(apiClient.putAsync(eq("accounts/entities/entity_id"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), eq(SCHEMA_V3_HEADERS))) .thenReturn(CompletableFuture.completedFuture(expectedResponse)); final CompletableFuture future = accountsClient.updateEntity(request, "entity_id"); @@ -240,6 +246,56 @@ void shouldUpdateEntity() throws ExecutionException, InterruptedException { validateResponse(expectedResponse, future.get()); } + @Test + void shouldCreateEntityWithSchemaVersionOverride() throws ExecutionException, InterruptedException { + final OnboardEntityRequest request = createOnboardEntityRequest(); + final OnboardEntityResponse expectedResponse = createOnboardEntityResponse(); + + when(apiClient.postAsync(eq("accounts/entities"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), isNull(), eq(SCHEMA_V2_HEADERS))) + .thenReturn(CompletableFuture.completedFuture(expectedResponse)); + + final CompletableFuture future = accountsClient.createEntity(request, "2.0"); + + validateResponse(expectedResponse, future.get()); + } + + @Test + void shouldGetEntityWithSchemaVersionOverride() throws ExecutionException, InterruptedException { + final OnboardEntityDetailsResponse expectedResponse = createOnboardEntityDetailsResponse(); + + when(apiClient.getAsync("accounts/entities/entity_id", authorization, OnboardEntityDetailsResponse.class, SCHEMA_V2_HEADERS)) + .thenReturn(CompletableFuture.completedFuture(expectedResponse)); + + final CompletableFuture future = accountsClient.getEntity("entity_id", "2.0"); + + validateResponse(expectedResponse, future.get()); + } + + @Test + void shouldUpdateEntityWithSchemaVersionOverride() throws ExecutionException, InterruptedException { + final OnboardEntityRequest request = createOnboardEntityRequest(); + final OnboardEntityResponse expectedResponse = createOnboardEntityResponse(); + + when(apiClient.putAsync(eq("accounts/entities/entity_id"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), eq(SCHEMA_V2_HEADERS))) + .thenReturn(CompletableFuture.completedFuture(expectedResponse)); + + final CompletableFuture future = accountsClient.updateEntity(request, "entity_id", "2.0"); + + validateResponse(expectedResponse, future.get()); + } + + @Test + void shouldGetEntityRequirementsWithSchemaVersionOverride() throws ExecutionException, InterruptedException { + final EntityRequirementListResponse expectedResponse = new EntityRequirementListResponse(); + + when(apiClient.getAsync("accounts/entities/entity_id/requirements", authorization, EntityRequirementListResponse.class, SCHEMA_V2_HEADERS)) + .thenReturn(CompletableFuture.completedFuture(expectedResponse)); + + final CompletableFuture future = accountsClient.getEntityRequirements("entity_id", "2.0"); + + validateResponse(expectedResponse, future.get()); + } + @Test void shouldCreatePaymentInstrumentDeprecated() throws ExecutionException, InterruptedException { final AccountsPaymentInstrument request = createAccountsPaymentInstrument(); @@ -348,7 +404,7 @@ void shouldCreateEntitySync() { final OnboardEntityRequest request = createOnboardEntityRequest(); final OnboardEntityResponse expectedResponse = createOnboardEntityResponse(); - when(apiClient.post(eq("accounts/entities"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), isNull())) + when(apiClient.post(eq("accounts/entities"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), isNull(), eq(SCHEMA_V3_HEADERS))) .thenReturn(expectedResponse); final OnboardEntityResponse actualResponse = accountsClient.createEntitySync(request); @@ -360,7 +416,7 @@ void shouldCreateEntitySync() { void shouldGetEntitySync() { final OnboardEntityDetailsResponse expectedResponse = createOnboardEntityDetailsResponse(); - when(apiClient.get("accounts/entities/entity_id", authorization, OnboardEntityDetailsResponse.class)) + when(apiClient.get("accounts/entities/entity_id", authorization, OnboardEntityDetailsResponse.class, SCHEMA_V3_HEADERS)) .thenReturn(expectedResponse); final OnboardEntityDetailsResponse actualResponse = accountsClient.getEntitySync("entity_id"); @@ -373,7 +429,7 @@ void shouldUpdateEntitySync() { final OnboardEntityRequest request = createOnboardEntityRequest(); final OnboardEntityResponse expectedResponse = createOnboardEntityResponse(); - when(apiClient.put(eq("accounts/entities/entity_id"), eq(authorization), eq(OnboardEntityResponse.class), eq(request))) + when(apiClient.put(eq("accounts/entities/entity_id"), eq(authorization), eq(OnboardEntityResponse.class), eq(request), eq(SCHEMA_V3_HEADERS))) .thenReturn(expectedResponse); final OnboardEntityResponse actualResponse = accountsClient.updateEntitySync(request, "entity_id"); @@ -621,7 +677,7 @@ void shouldGetReserveRules() throws ExecutionException, InterruptedException { void shouldGetEntityRequirements() throws ExecutionException, InterruptedException { final EntityRequirementListResponse expectedResponse = createEntityRequirementListResponse(); - when(apiClient.getAsync("accounts/entities/entity_id/requirements", authorization, EntityRequirementListResponse.class)) + when(apiClient.getAsync("accounts/entities/entity_id/requirements", authorization, EntityRequirementListResponse.class, SCHEMA_V3_HEADERS)) .thenReturn(CompletableFuture.completedFuture(expectedResponse)); final CompletableFuture future = accountsClient.getEntityRequirements("entity_id"); @@ -733,7 +789,7 @@ void shouldGetReserveRulesSync() { void shouldGetEntityRequirementsSync() { final EntityRequirementListResponse expectedResponse = createEntityRequirementListResponse(); - when(apiClient.get("accounts/entities/entity_id/requirements", authorization, EntityRequirementListResponse.class)) + when(apiClient.get("accounts/entities/entity_id/requirements", authorization, EntityRequirementListResponse.class, SCHEMA_V3_HEADERS)) .thenReturn(expectedResponse); final EntityRequirementListResponse actualResponse = accountsClient.getEntityRequirementsSync("entity_id"); diff --git a/src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java b/src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java new file mode 100644 index 000000000..8daca3524 --- /dev/null +++ b/src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java @@ -0,0 +1,161 @@ +package com.checkout.accounts; + +import com.checkout.GsonSerializer; +import com.checkout.common.Currency; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AccountsV3SerializationTest { + + private final GsonSerializer serializer = new GsonSerializer(); + + @Test + void shouldSerializeProcessingDetailsWithPayments() { + final ProcessingDetails processingDetails = ProcessingDetails.builder() + .annualProcessingVolume(1000000) + .averageTransactionValue(5000) + .averageOrderFulfillmentTime(3) + .highestTransactionValue(25000) + .currency(Currency.GBP) + .settlementCountry("GB") + .targetCountries(Collections.singletonList("GB")) + .payments(ProcessingDetailsPayments.builder() + .ach(ProcessingDetailsAch.builder() + .annualAchVolume(1000000) + .averageAchTransactionSize(5000) + .estimatedMonthlyCreditVolume(100000) + .averageCreditAmount(5000) + .build()) + .build()) + .build(); + + final String json = serializer.toJson(processingDetails); + + assertTrue(json.contains("\"annual_processing_volume\"")); + assertTrue(json.contains("\"average_order_fulfillment_time\"")); + assertTrue(json.contains("\"highest_transaction_value\"")); + assertTrue(json.contains("\"settlement_country\"")); + assertTrue(json.contains("\"target_countries\"")); + assertTrue(json.contains("\"payments\"")); + assertTrue(json.contains("\"ach\"")); + assertTrue(json.contains("\"annual_ach_volume\"")); + assertTrue(json.contains("\"average_ach_transaction_size\"")); + assertTrue(json.contains("\"estimated_monthly_credit_volume\"")); + assertTrue(json.contains("\"average_credit_amount\"")); + } + + @Test + void shouldSerializeAgreedTerms() { + final AgreedTerms agreedTerms = AgreedTerms.builder() + .date("2026-07-20T10:00:00Z") + .ipAddress("203.0.113.42") + .name("John Representative") + .email("john@example.com") + .version("1.0") + .build(); + + final String json = serializer.toJson(agreedTerms); + + assertTrue(json.contains("\"date\"")); + assertTrue(json.contains("\"ip_address\"")); + assertTrue(json.contains("\"name\"")); + assertTrue(json.contains("\"email\"")); + assertTrue(json.contains("\"version\"")); + } + + @Test + void shouldSerializeCompanyV3Fields() { + final Company company = Company.builder() + .legalName("Super Hero Masks Inc.") + .tradingName("Super Hero Masks") + .businessRegistrationNumber("01234567") + .businessType(BusinessType.LIMITED_COMPANY) + .additionalTradingNames(Collections.singletonList("SHM")) + .isRegisteredCompany(true) + .dateOfIncorporation(DateOfIncorporation.builder().day(1).month(6).year(2010).build()) + .build(); + + final String json = serializer.toJson(company); + + assertTrue(json.contains("\"additional_trading_names\"")); + assertTrue(json.contains("\"is_registered_company\"")); + assertTrue(json.contains("\"business_type\"")); + assertTrue(json.contains("limited_company")); + assertTrue(json.contains("\"date_of_incorporation\"")); + assertTrue(json.contains("\"day\"")); + } + + @Test + void shouldSerializeRepresentativeV3Fields() { + final Representative representative = Representative.builder() + .id("rep_00000000000000000000000000") + .individual(RepresentativeIndividual.builder() + .firstName("John") + .lastName("Representative") + .nationalIdNumber("AB123456C") + .emailAddress("john@example.com") + .build()) + .companyPosition(CompanyPosition.CEO) + .ownershipPercentage(100) + .roles(Arrays.asList(EntityRoles.UBO, EntityRoles.AUTHORISED_SIGNATORY, EntityRoles.DIRECTOR, EntityRoles.CONTROL_PERSON)) + .build(); + + final String json = serializer.toJson(representative); + + assertTrue(json.contains("\"individual\"")); + assertTrue(json.contains("\"first_name\"")); + assertTrue(json.contains("\"national_id_number\"")); + assertTrue(json.contains("\"email_address\"")); + assertTrue(json.contains("\"company_position\"")); + assertTrue(json.contains("ceo")); + assertTrue(json.contains("\"ownership_percentage\"")); + assertTrue(json.contains("director")); + assertTrue(json.contains("control_person")); + } + + @Test + void shouldSerializeFinancialStatementsDocument() { + final OnboardSubEntityDocuments documents = OnboardSubEntityDocuments.builder() + .financialStatements(FinancialStatements.builder() + .type(FinancialStatementsType.FINANCIAL_STATEMENTS) + .front("file_00000000000000000000000000") + .build()) + .build(); + + final String json = serializer.toJson(documents); + + assertTrue(json.contains("\"financial_statements\"")); + assertTrue(json.contains("financial_statements")); + assertTrue(json.contains("\"front\"")); + } + + @Test + void shouldSerializeOnboardEntityRequestWithAgreedTermsAndSellerCategory() { + final OnboardEntityRequest request = OnboardEntityRequest.builder() + .reference("ref_1") + .sellerCategory("cat_electronics") + .agreedTerms(AgreedTerms.builder().date("2026-07-20T10:00:00Z").version("1.0").build()) + .build(); + + final String json = serializer.toJson(request); + + assertTrue(json.contains("\"seller_category\"")); + assertTrue(json.contains("\"agreed_terms\"")); + } + + @Test + void shouldDeserializeNewEnumValuesToExactSwaggerStrings() { + assertEquals(EntityRoles.DIRECTOR, serializer.fromJson("\"director\"", EntityRoles.class)); + assertEquals(EntityRoles.CONTROL_PERSON, serializer.fromJson("\"control_person\"", EntityRoles.class)); + assertEquals(BusinessType.INDIVIDUAL_OR_SOLE_PROPRIETORSHIP, serializer.fromJson("\"individual_or_sole_proprietorship\"", BusinessType.class)); + assertEquals(BusinessType.GOVERNMENT_AGENCY, serializer.fromJson("\"government_agency\"", BusinessType.class)); + assertEquals(BusinessType.SEC_REGISTERED_ENTITY, serializer.fromJson("\"sec_registered_entity\"", BusinessType.class)); + assertEquals(CompanyPosition.CEO, serializer.fromJson("\"ceo\"", CompanyPosition.class)); + assertEquals(CompanyPosition.OTHER_NON_EXECUTIVE_NON_SENIOR, serializer.fromJson("\"other_non_executive_non_senior\"", CompanyPosition.class)); + } +} From e330fe9c60fba2c43bd1256c3c73970a34766e52 Mon Sep 17 00:00:00 2001 From: david ruiz Date: Fri, 24 Jul 2026 13:44:18 +0200 Subject: [PATCH 2/5] Some schema fixes --- .../java/com/checkout/accounts/OnboardEntityRequest.java | 9 +++++++++ .../com/checkout/accounts/OnboardSubEntityDocuments.java | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/checkout/accounts/OnboardEntityRequest.java b/src/main/java/com/checkout/accounts/OnboardEntityRequest.java index 850c91316..18c535c3f 100644 --- a/src/main/java/com/checkout/accounts/OnboardEntityRequest.java +++ b/src/main/java/com/checkout/accounts/OnboardEntityRequest.java @@ -32,6 +32,11 @@ public final class OnboardEntityRequest { private OnboardSubEntityDocuments documents; + /** + * @deprecated Not present in the current Accounts API schema + * retained for backwards compatibility only. + */ + @Deprecated private AdditionalInfo additionalInfo; /** @@ -47,7 +52,11 @@ public final class OnboardEntityRequest { * Captures evidence of the end-user's consent to onboarding. * Used for US ISV onboarding variants. * [Optional] + * + * @deprecated Not present in the current Accounts API schema + * retained for backwards compatibility only. */ + @Deprecated private Submitter submitter; private AgreedTerms agreedTerms; diff --git a/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java b/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java index 9e026dfe2..40f2fb3a9 100644 --- a/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java +++ b/src/main/java/com/checkout/accounts/OnboardSubEntityDocuments.java @@ -26,13 +26,13 @@ public final class OnboardSubEntityDocuments { private ProofOfPrincipalAddress proofOfPrincipalAddress; - @SerializedName("additional_document_1") + @SerializedName("additional_document1") private AdditionalDocument additionalDocument1; - @SerializedName("additional_document_2") + @SerializedName("additional_document2") private AdditionalDocument additionalDocument2; - @SerializedName("additional_document_3") + @SerializedName("additional_document3") private AdditionalDocument additionalDocument3; private TaxVerification taxVerification; From b755dec4e1c10c0a13c5e32119f9d8c9afefef97 Mon Sep 17 00:00:00 2001 From: david ruiz Date: Mon, 27 Jul 2026 09:04:18 +0200 Subject: [PATCH 3/5] Setting Accounts tests to 2.0 --- .../com/checkout/accounts/AccountsTestIT.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/test/java/com/checkout/accounts/AccountsTestIT.java b/src/test/java/com/checkout/accounts/AccountsTestIT.java index 1641b6ddd..acd174d1c 100644 --- a/src/test/java/com/checkout/accounts/AccountsTestIT.java +++ b/src/test/java/com/checkout/accounts/AccountsTestIT.java @@ -50,17 +50,17 @@ void shouldCreateGetAndUpdateOnboardIndividualEntity() { final String randomReference = RandomStringUtils.random(15, true, true); final OnboardEntityRequest onboardEntityRequest = buildIndividualEntity(randomReference); - final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient().createEntity(onboardEntityRequest)); + final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient().createEntity(onboardEntityRequest, "2.0")); validateEntityCreationResponse(entityResponse, randomReference); - final OnboardEntityDetailsResponse entityDetailsResponse = blocking(() -> checkoutApi.accountsClient().getEntity(entityResponse.getId())); + final OnboardEntityDetailsResponse entityDetailsResponse = blocking(() -> checkoutApi.accountsClient().getEntity(entityResponse.getId(), "2.0")); validateIndividualEntityDetails(entityDetailsResponse, onboardEntityRequest, randomReference); onboardEntityRequest.getIndividual().setFirstName("Jhon"); - final OnboardEntityResponse updatedEntityResponse = blocking(() -> checkoutApi.accountsClient().updateEntity(onboardEntityRequest, entityResponse.getId())); + final OnboardEntityResponse updatedEntityResponse = blocking(() -> checkoutApi.accountsClient().updateEntity(onboardEntityRequest, entityResponse.getId(), "2.0")); assertNotNull(updatedEntityResponse); - final OnboardEntityDetailsResponse verifyUpdated = blocking(() -> checkoutApi.accountsClient().getEntity(entityResponse.getId())); + final OnboardEntityDetailsResponse verifyUpdated = blocking(() -> checkoutApi.accountsClient().getEntity(entityResponse.getId(), "2.0")); assertEquals(onboardEntityRequest.getIndividual().getFirstName(), verifyUpdated.getIndividual().getFirstName()); } @@ -70,10 +70,10 @@ void shouldCreateGetAndUpdateOnboardCompanyEntity() { final String randomReference = RandomStringUtils.random(15, true, true); final OnboardEntityRequest onboardEntityRequest = buildCompanyEntity(randomReference); - final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient().createEntity(onboardEntityRequest)); + final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient().createEntity(onboardEntityRequest, "2.0")); validateEntityCreationResponse(entityResponse, randomReference); - final OnboardEntityDetailsResponse entityDetailsResponse = blocking(() -> checkoutApi.accountsClient().getEntity(entityResponse.getId())); + final OnboardEntityDetailsResponse entityDetailsResponse = blocking(() -> checkoutApi.accountsClient().getEntity(entityResponse.getId(), "2.0")); validateCompanyEntityDetails(entityDetailsResponse, onboardEntityRequest, randomReference); } @@ -114,7 +114,7 @@ void shouldCreateAndRetrievePaymentInstrument() throws URISyntaxException { final CheckoutApi checkoutApi = getAccountsCheckoutApi(); final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient() - .createEntity(buildCompanyEntity(RandomStringUtils.random(15, true, true)))); + .createEntity(buildCompanyEntity(RandomStringUtils.random(15, true, true)), "2.0")); assertNotNull(entityResponse); assertNotNull(entityResponse.getId()); @@ -136,17 +136,17 @@ void shouldCreateGetAndUpdateOnboardIndividualEntitySync() { final String randomReference = RandomStringUtils.random(15, true, true); final OnboardEntityRequest onboardEntityRequest = buildIndividualEntity(randomReference); - final OnboardEntityResponse entityResponse = checkoutApi.accountsClient().createEntitySync(onboardEntityRequest); + final OnboardEntityResponse entityResponse = checkoutApi.accountsClient().createEntitySync(onboardEntityRequest, "2.0"); validateEntityCreationResponse(entityResponse, randomReference); - final OnboardEntityDetailsResponse entityDetailsResponse = checkoutApi.accountsClient().getEntitySync(entityResponse.getId()); + final OnboardEntityDetailsResponse entityDetailsResponse = checkoutApi.accountsClient().getEntitySync(entityResponse.getId(), "2.0"); validateIndividualEntityDetails(entityDetailsResponse, onboardEntityRequest, randomReference); onboardEntityRequest.getIndividual().setFirstName("Jhon"); - final OnboardEntityResponse updatedEntityResponse = checkoutApi.accountsClient().updateEntitySync(onboardEntityRequest, entityResponse.getId()); + final OnboardEntityResponse updatedEntityResponse = checkoutApi.accountsClient().updateEntitySync(onboardEntityRequest, entityResponse.getId(), "2.0"); assertNotNull(updatedEntityResponse); - final OnboardEntityDetailsResponse verifyUpdated = checkoutApi.accountsClient().getEntitySync(entityResponse.getId()); + final OnboardEntityDetailsResponse verifyUpdated = checkoutApi.accountsClient().getEntitySync(entityResponse.getId(), "2.0"); assertEquals(onboardEntityRequest.getIndividual().getFirstName(), verifyUpdated.getIndividual().getFirstName()); } @@ -156,10 +156,10 @@ void shouldCreateGetAndUpdateOnboardCompanyEntitySync() { final String randomReference = RandomStringUtils.random(15, true, true); final OnboardEntityRequest onboardEntityRequest = buildCompanyEntity(randomReference); - final OnboardEntityResponse entityResponse = checkoutApi.accountsClient().createEntitySync(onboardEntityRequest); + final OnboardEntityResponse entityResponse = checkoutApi.accountsClient().createEntitySync(onboardEntityRequest, "2.0"); validateEntityCreationResponse(entityResponse, randomReference); - final OnboardEntityDetailsResponse entityDetailsResponse = checkoutApi.accountsClient().getEntitySync(entityResponse.getId()); + final OnboardEntityDetailsResponse entityDetailsResponse = checkoutApi.accountsClient().getEntitySync(entityResponse.getId(), "2.0"); validateCompanyEntityDetails(entityDetailsResponse, onboardEntityRequest, randomReference); } @@ -175,7 +175,7 @@ void shouldCreateAndRetrievePaymentInstrumentSync() throws URISyntaxException { final CheckoutApi checkoutApi = getAccountsCheckoutApi(); final OnboardEntityResponse entityResponse = checkoutApi.accountsClient() - .createEntitySync(buildCompanyEntity(RandomStringUtils.random(15, true, true))); + .createEntitySync(buildCompanyEntity(RandomStringUtils.random(15, true, true)), "2.0"); assertNotNull(entityResponse); assertNotNull(entityResponse.getId()); @@ -281,7 +281,7 @@ void shouldGetReserveRules() { void shouldGetEntityRequirements() { final String entityId = Objects.requireNonNull(System.getenv("CHECKOUT_DEFAULT_ENTITY_ID")); - final EntityRequirementListResponse response = blocking(() -> checkoutApi.accountsClient().getEntityRequirements(entityId)); + final EntityRequirementListResponse response = blocking(() -> checkoutApi.accountsClient().getEntityRequirements(entityId, "2.0")); validateEntityRequirementListResponse(response); } @@ -289,7 +289,7 @@ void shouldGetEntityRequirements() { @Test void shouldGetEntityRequirementDetails() { final String entityId = Objects.requireNonNull(System.getenv("CHECKOUT_DEFAULT_ENTITY_ID")); - final EntityRequirementListResponse listResponse = blocking(() -> checkoutApi.accountsClient().getEntityRequirements(entityId)); + final EntityRequirementListResponse listResponse = blocking(() -> checkoutApi.accountsClient().getEntityRequirements(entityId, "2.0")); assertNotNull(listResponse.getData()); assertNotNull(listResponse.getData().get(0)); @@ -302,7 +302,7 @@ void shouldGetEntityRequirementDetails() { @Test void shouldResolveEntityRequirement() { final String entityId = Objects.requireNonNull(System.getenv("CHECKOUT_DEFAULT_ENTITY_ID")); - final EntityRequirementListResponse listResponse = blocking(() -> checkoutApi.accountsClient().getEntityRequirements(entityId)); + final EntityRequirementListResponse listResponse = blocking(() -> checkoutApi.accountsClient().getEntityRequirements(entityId, "2.0")); assertNotNull(listResponse.getData()); assertNotNull(listResponse.getData().get(0)); @@ -420,7 +420,7 @@ private String createTestEntity() { .build()) .build(); - final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient().createEntity(entityRequest)); + final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient().createEntity(entityRequest, "2.0")); assertNotNull(entityResponse); assertNotNull(entityResponse.getId()); return entityResponse.getId(); From 22d9cf37db37772a2cfdceec9ee8e5005046f7aa Mon Sep 17 00:00:00 2001 From: david ruiz Date: Mon, 27 Jul 2026 09:56:14 +0200 Subject: [PATCH 4/5] Schema version 3.0 Accounts tests --- .../com/checkout/accounts/AccountsTestIT.java | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/src/test/java/com/checkout/accounts/AccountsTestIT.java b/src/test/java/com/checkout/accounts/AccountsTestIT.java index acd174d1c..61ec0fd1f 100644 --- a/src/test/java/com/checkout/accounts/AccountsTestIT.java +++ b/src/test/java/com/checkout/accounts/AccountsTestIT.java @@ -30,6 +30,7 @@ import java.io.File; import java.net.URISyntaxException; import java.net.URL; +import java.util.Arrays; import java.util.Collections; import java.util.Objects; @@ -163,6 +164,86 @@ void shouldCreateGetAndUpdateOnboardCompanyEntitySync() { validateCompanyEntityDetails(entityDetailsResponse, onboardEntityRequest, randomReference); } + // ===================== Accounts API schema_version 3.0 ===================== + // v3.0 onboards a sub-entity as a company whose representatives carry nested `individual` + // details + `roles`, plus a required `processing_details` object. These mirror the 2.0 + // onboarding tests above (which pin schema_version to "2.0"); here createEntity/getEntity/ + // updateEntity use the SDK default (3.0). They run through the accounts-scoped OAuth client, + // which is the one provisioned for v3.0 onboarding. + @Test + void shouldCreateGetAndUpdateOnboardCompanyEntityV3() { + final CheckoutApi checkoutApi = getAccountsCheckoutApi(); + final String randomReference = RandomStringUtils.random(15, true, true); + final OnboardEntityRequest onboardEntityRequest = buildCompanyEntityV3(randomReference); + + final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient().createEntity(onboardEntityRequest)); + validateEntityCreationResponse(entityResponse, randomReference); + + final OnboardEntityDetailsResponse entityDetailsResponse = blocking(() -> checkoutApi.accountsClient().getEntity(entityResponse.getId())); + validateCompanyEntityDetails(entityDetailsResponse, onboardEntityRequest, randomReference); + + final OnboardEntityResponse updatedEntityResponse = blocking(() -> checkoutApi.accountsClient().updateEntity(onboardEntityRequest, entityResponse.getId())); + assertNotNull(updatedEntityResponse); + assertNotNull(updatedEntityResponse.getId()); + } + + @Test + void shouldCreateGetAndUpdateOnboardCompanyEntitySyncV3() { + final CheckoutApi checkoutApi = getAccountsCheckoutApi(); + final String randomReference = RandomStringUtils.random(15, true, true); + final OnboardEntityRequest onboardEntityRequest = buildCompanyEntityV3(randomReference); + + final OnboardEntityResponse entityResponse = checkoutApi.accountsClient().createEntitySync(onboardEntityRequest); + validateEntityCreationResponse(entityResponse, randomReference); + + final OnboardEntityDetailsResponse entityDetailsResponse = checkoutApi.accountsClient().getEntitySync(entityResponse.getId()); + validateCompanyEntityDetails(entityDetailsResponse, onboardEntityRequest, randomReference); + + final OnboardEntityResponse updatedEntityResponse = checkoutApi.accountsClient().updateEntitySync(onboardEntityRequest, entityResponse.getId()); + assertNotNull(updatedEntityResponse); + assertNotNull(updatedEntityResponse.getId()); + } + + @Test + void shouldCreateAndRetrievePaymentInstrumentV3() throws URISyntaxException { + final CheckoutApi checkoutApi = getAccountsCheckoutApi(); + + final OnboardEntityResponse entityResponse = blocking(() -> checkoutApi.accountsClient() + .createEntity(buildCompanyEntityV3(RandomStringUtils.random(15, true, true)))); + assertNotNull(entityResponse); + assertNotNull(entityResponse.getId()); + + final IdResponse file = uploadFile(); + final PaymentInstrumentRequest instrumentRequest = buildPaymentInstrumentRequest(file.getId()); + + final IdResponse instrumentResponse = blocking(() -> checkoutApi.accountsClient().createPaymentInstrument(entityResponse.getId(), instrumentRequest)); + assertNotNull(instrumentResponse); + assertNotNull(instrumentResponse.getId()); + + final PaymentInstrumentDetailsResponse instrumentDetailsResponse = blocking(() -> checkoutApi.accountsClient().retrievePaymentInstrumentDetails(entityResponse.getId(), instrumentResponse.getId())); + validatePaymentInstrumentDetailsResponse(instrumentDetailsResponse); + } + + @Test + void shouldCreateAndRetrievePaymentInstrumentSyncV3() throws URISyntaxException { + final CheckoutApi checkoutApi = getAccountsCheckoutApi(); + + final OnboardEntityResponse entityResponse = checkoutApi.accountsClient() + .createEntitySync(buildCompanyEntityV3(RandomStringUtils.random(15, true, true))); + assertNotNull(entityResponse); + assertNotNull(entityResponse.getId()); + + final IdResponse file = uploadFileSync(); + final PaymentInstrumentRequest instrumentRequest = buildPaymentInstrumentRequest(file.getId()); + + final IdResponse instrumentResponse = checkoutApi.accountsClient().createPaymentInstrumentSync(entityResponse.getId(), instrumentRequest); + assertNotNull(instrumentResponse); + assertNotNull(instrumentResponse.getId()); + + final PaymentInstrumentDetailsResponse instrumentDetailsResponse = checkoutApi.accountsClient().retrievePaymentInstrumentDetailsSync(entityResponse.getId(), instrumentResponse.getId()); + validatePaymentInstrumentDetailsResponse(instrumentDetailsResponse); + } + @Test void shouldUploadAccountsFileSync() throws URISyntaxException { final IdResponse fileResponse = uploadFileSync(); @@ -615,6 +696,89 @@ private static OnboardEntityRequest buildCompanyEntity(final String randomRefere .build(); } + // Builds an onboarding request that conforms to the Accounts API v3.0 schema: the representative + // carries nested `individual` details + `roles`, the company has `businessType` + + // `dateOfIncorporation`, and `processingDetails` is populated. The holding-currency scope is + // configured on the platform (USD here) while `processingDetails.currency` reflects the + // sub-entity region (GBP) — the two are independent. + private static OnboardEntityRequest buildCompanyEntityV3(final String randomReference) { + final Address address = Address.builder() + .addressLine1("90 Tottenham Court Road") + .city("London") + .zip("W1T4TJ") + .country(CountryCode.GB) + .build(); + + return OnboardEntityRequest.builder() + .reference(randomReference) + .contactDetails(ContactDetails.builder() + .phone(AccountPhone.builder() + .countryCode(CountryCode.GB) + .number("2345678910") + .build()) + .emailAddresses(EntityEmailAddresses.builder() + .primary(generateRandomEmail()) + .build()) + .build()) + .profile(Profile.builder() + .urls(Collections.singletonList("https://www.superheroexample.com")) + .mccs(Collections.singletonList("0742")) + .defaultHoldingCurrency(Currency.USD) + .holdingCurrencies(Collections.singletonList(Currency.USD)) + .build()) + .company(Company.builder() + .businessRegistrationNumber("01234567") + .businessType(BusinessType.LIMITED_COMPANY) + .legalName("Super Hero Masks Inc.") + .tradingName("Super Hero Masks") + .dateOfIncorporation(DateOfIncorporation.builder() + .day(1) + .month(6) + .year(2010) + .build()) + .principalAddress(address) + .registeredAddress(address) + .representatives(Collections.singletonList(Representative.builder() + .individual(RepresentativeIndividual.builder() + .firstName("John") + .lastName("Doe") + .dateOfBirth(DateOfBirth.builder() + .day(5) + .month(6) + .year(1995) + .build()) + .placeOfBirth(PlaceOfBirth.builder() + .country(CountryCode.GB) + .build()) + .address(address) + .build()) + .roles(Arrays.asList( + EntityRoles.UBO, + EntityRoles.AUTHORISED_SIGNATORY, + EntityRoles.DIRECTOR, + EntityRoles.CONTROL_PERSON)) + .build())) + .build()) + .processingDetails(ProcessingDetails.builder() + .annualProcessingVolume(1000000) + .averageTransactionValue(5000) + .averageOrderFulfillmentTime(3) + .highestTransactionValue(25000) + .currency(Currency.GBP) + .settlementCountry(CountryCode.GB.name()) + .targetCountries(Collections.singletonList(CountryCode.GB.name())) + .payments(ProcessingDetailsPayments.builder() + .ach(ProcessingDetailsAch.builder() + .annualAchVolume(1000000) + .averageAchTransactionSize(5000) + .estimatedMonthlyCreditVolume(100000) + .averageCreditAmount(5000) + .build()) + .build()) + .build()) + .build(); + } + private IdResponse uploadFile() throws URISyntaxException { final URL resource = getClass().getClassLoader().getResource("checkout.jpeg"); final File file = new File(resource.toURI()); From e62edf47a2ddb3528f04aa96211a533628e4a031 Mon Sep 17 00:00:00 2001 From: david ruiz Date: Mon, 27 Jul 2026 13:23:35 +0200 Subject: [PATCH 5/5] Added missing fields in RepresentativeIndividual --- .../com/checkout/accounts/Citizenship.java | 28 +++++++++++++++++++ .../com/checkout/accounts/NationalIdType.java | 21 ++++++++++++++ .../accounts/RepresentativeIndividual.java | 6 ++++ .../accounts/AccountsV3SerializationTest.java | 10 +++++++ 4 files changed, 65 insertions(+) create mode 100644 src/main/java/com/checkout/accounts/Citizenship.java create mode 100644 src/main/java/com/checkout/accounts/NationalIdType.java diff --git a/src/main/java/com/checkout/accounts/Citizenship.java b/src/main/java/com/checkout/accounts/Citizenship.java new file mode 100644 index 000000000..f521980e8 --- /dev/null +++ b/src/main/java/com/checkout/accounts/Citizenship.java @@ -0,0 +1,28 @@ +package com.checkout.accounts; + +import com.checkout.common.CountryCode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * A citizenship or legal status held by a company representative, as required by the + * Accounts API v3.0 individual schema. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public final class Citizenship { + + /** + * The type of citizenship or legal status (for example, {@code citizenship} or {@code residency}). + */ + private String type; + + /** + * The two-letter ISO 3166-1 alpha-2 country code. + */ + private CountryCode country; +} diff --git a/src/main/java/com/checkout/accounts/NationalIdType.java b/src/main/java/com/checkout/accounts/NationalIdType.java new file mode 100644 index 000000000..85dfde68b --- /dev/null +++ b/src/main/java/com/checkout/accounts/NationalIdType.java @@ -0,0 +1,21 @@ +package com.checkout.accounts; + +import com.google.gson.annotations.SerializedName; + +public enum NationalIdType { + + @SerializedName("ssn") + SSN, + @SerializedName("itin") + ITIN, + @SerializedName("passport") + PASSPORT, + @SerializedName("driving_license") + DRIVING_LICENSE, + @SerializedName("national_id_card") + NATIONAL_ID_CARD, + @SerializedName("residence_permit") + RESIDENCE_PERMIT, + @SerializedName("other") + OTHER +} diff --git a/src/main/java/com/checkout/accounts/RepresentativeIndividual.java b/src/main/java/com/checkout/accounts/RepresentativeIndividual.java index 268219ee4..e35dc5e71 100644 --- a/src/main/java/com/checkout/accounts/RepresentativeIndividual.java +++ b/src/main/java/com/checkout/accounts/RepresentativeIndividual.java @@ -6,6 +6,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @Builder @NoArgsConstructor @@ -22,6 +24,10 @@ public final class RepresentativeIndividual { private PlaceOfBirth placeOfBirth; + private List citizenships; + + private NationalIdType nationalIdType; + private String nationalIdNumber; private String emailAddress; diff --git a/src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java b/src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java index 8daca3524..bb9ab2b66 100644 --- a/src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java +++ b/src/test/java/com/checkout/accounts/AccountsV3SerializationTest.java @@ -1,6 +1,7 @@ package com.checkout.accounts; import com.checkout.GsonSerializer; +import com.checkout.common.CountryCode; import com.checkout.common.Currency; import org.junit.jupiter.api.Test; @@ -97,6 +98,11 @@ void shouldSerializeRepresentativeV3Fields() { .individual(RepresentativeIndividual.builder() .firstName("John") .lastName("Representative") + .citizenships(Collections.singletonList(Citizenship.builder() + .type("citizenship") + .country(CountryCode.US) + .build())) + .nationalIdType(NationalIdType.SSN) .nationalIdNumber("AB123456C") .emailAddress("john@example.com") .build()) @@ -109,6 +115,10 @@ void shouldSerializeRepresentativeV3Fields() { assertTrue(json.contains("\"individual\"")); assertTrue(json.contains("\"first_name\"")); + assertTrue(json.contains("\"citizenships\"")); + assertTrue(json.contains("\"country\"")); + assertTrue(json.contains("\"national_id_type\"")); + assertTrue(json.contains("ssn")); assertTrue(json.contains("\"national_id_number\"")); assertTrue(json.contains("\"email_address\"")); assertTrue(json.contains("\"company_position\""));