diff --git a/lib/checkout_sdk/accounts/accounts.rb b/lib/checkout_sdk/accounts/accounts.rb index 34c2412..4ecfe7f 100644 --- a/lib/checkout_sdk/accounts/accounts.rb +++ b/lib/checkout_sdk/accounts/accounts.rb @@ -81,3 +81,11 @@ require 'checkout_sdk/accounts/financial_statements' require 'checkout_sdk/accounts/financial_statements_type' require 'checkout_sdk/accounts/articles_of_association' +require 'checkout_sdk/accounts/certified_authorised_signatory_type' +require 'checkout_sdk/accounts/certified_authorised_signatory' +require 'checkout_sdk/accounts/proof_of_residential_address_type' +require 'checkout_sdk/accounts/proof_of_residential_address' +require 'checkout_sdk/accounts/proof_of_registration_type' +require 'checkout_sdk/accounts/proof_of_registration' +require 'checkout_sdk/accounts/representative_documents' +require 'checkout_sdk/accounts/file_purpose' diff --git a/lib/checkout_sdk/accounts/accounts_client.rb b/lib/checkout_sdk/accounts/accounts_client.rb index 31bfa59..4f3bf71 100644 --- a/lib/checkout_sdk/accounts/accounts_client.rb +++ b/lib/checkout_sdk/accounts/accounts_client.rb @@ -78,13 +78,17 @@ def retrieve_payment_instrument_details(entity_id, payment_instrument_id) sdk_authorization) end + # Updates a payment instrument (PATCH /accounts/entities/{entityId}/payment-instruments/{id}). + # The API reads the ETag only from the If-Match HTTP header and answers 428 without it, so the + # request's headers.if_match is sent as that header. # @param [String] entity_id # @param [String] instrument_id # @param [Hash, UpdatePaymentInstrumentRequest] update_payment_instrument def update_payment_instrument(entity_id, instrument_id, update_payment_instrument) api_client.invoke_patch(build_path(ACCOUNTS, ENTITIES, entity_id, PAYMENT_INSTRUMENTS, instrument_id), sdk_authorization, - update_payment_instrument) + update_payment_instrument, + payment_instrument_headers(update_payment_instrument)) end # @param [String] entity_id @@ -100,7 +104,10 @@ def retrieve_payout_schedule(entity_id) api_client.invoke_get(build_path(ACCOUNTS, ENTITIES, entity_id, PAYOUT_SCHEDULE), sdk_authorization) end - # @param [Hash, FileRequest] file_request + # Uploads a file to the Files API (POST /files on the Files host), as a multipart request. The + # returned ID is what document front and back attributes take. + # @param [Hash, FileRequest] file_request The file to upload and its {FilePurpose}. + # @return [Hash] The ID of the uploaded file. def upload_file(file_request) files_client.submit_file(FILES, sdk_authorization, file_request) end @@ -216,20 +223,25 @@ def reinvite_sub_entity_member(entity_id, user_id, reinvite_request) ) end - # Upload a file scoped to a sub-entity. Hits POST /entities/{entityId}/files. - # @param [String] entity_id - # @param [Hash, EntityFilesRequest] file_request + # Create a file upload scoped to a sub-entity. Hits POST /entities/{entityId}/files on the Files host + # with a JSON body carrying only the purpose. The file content is not part of this request: send the + # raw bytes with an HTTP PUT to the returned _links.upload.href. + # @param [String] entity_id The ID of the sub-entity. + # @param [Hash, EntityFilesRequest] file_request The {FilePurpose} of the file to upload. + # @return [Hash] The file ID, the maximum size allowed, the MIME types allowed for the purpose, and the + # upload link. def upload_entity_file(entity_id, file_request) - files_client.submit_file( + files_client.invoke_post( build_path(ENTITIES, entity_id, FILES), sdk_authorization, file_request ) end - # Retrieve a file scoped to a sub-entity. Hits GET /entities/{entityId}/files/{fileId}. - # @param [String] entity_id - # @param [String] file_id + # Retrieve a file scoped to a sub-entity. Hits GET /entities/{entityId}/files/{fileId} on the Files host. + # @param [String] entity_id The ID of the sub-entity. + # @param [String] file_id The ID of the file. + # @return [Hash] The file's status, size, MIME type, upload date and purpose. def get_entity_file(entity_id, file_id) files_client.invoke_get( build_path(ENTITIES, entity_id, FILES, file_id), @@ -239,6 +251,20 @@ def get_entity_file(entity_id, file_id) private + # The If-Match header of a payment instrument update, from an UpdatePaymentInstrumentRequest or + # a Hash. Returns nil when no ETag was given. + def payment_instrument_headers(request) + headers = request.is_a?(Hash) ? (request[:headers] || request['headers']) : request&.headers + return headers unless headers.is_a?(Hash) + + etag = headers[:if_match] || headers['if_match'] || headers['if-match'] + return nil if etag.nil? + + http_headers = CheckoutSdk::Common::Headers.new + http_headers.if_match = etag + http_headers + end + # Builds the versioned Accept header for Accounts onboarding operations. # @param [String] schema_version # @return [CheckoutSdk::Common::Headers] diff --git a/lib/checkout_sdk/accounts/additional_document.rb b/lib/checkout_sdk/accounts/additional_document.rb index 749ef9d..90cf3bb 100644 --- a/lib/checkout_sdk/accounts/additional_document.rb +++ b/lib/checkout_sdk/accounts/additional_document.rb @@ -2,7 +2,13 @@ module CheckoutSdk module Accounts + # Additional space for documents to be provided when requested. Carries a file ID only; the API defines + # no document type for it. # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class AdditionalDocument attr_accessor :front diff --git a/lib/checkout_sdk/accounts/additional_info.rb b/lib/checkout_sdk/accounts/additional_info.rb index c70191b..702048a 100644 --- a/lib/checkout_sdk/accounts/additional_info.rb +++ b/lib/checkout_sdk/accounts/additional_info.rb @@ -2,11 +2,16 @@ module CheckoutSdk module Accounts + # Not defined by any Accounts API schema. + # @deprecated Not part of any Accounts API schema; the API does not read it. # @!attribute field1 + # @deprecated Not defined by any Accounts API schema. # @return [String] # @!attribute field2 + # @deprecated Not defined by any Accounts API schema. # @return [String] # @!attribute field3 + # @deprecated Not defined by any Accounts API schema. # @return [String] class AdditionalInfo attr_accessor :field1, diff --git a/lib/checkout_sdk/accounts/agreed_terms.rb b/lib/checkout_sdk/accounts/agreed_terms.rb index bed00dc..b12ee04 100644 --- a/lib/checkout_sdk/accounts/agreed_terms.rb +++ b/lib/checkout_sdk/accounts/agreed_terms.rb @@ -2,16 +2,29 @@ module CheckoutSdk module Accounts - # The terms of service the sub-entity agreed to (Accounts API v3.0, SaaS onboarding). + # Evidence of consent to Checkout.com onboarding: the person who agreed to the terms and conditions + # (US ISV Seller variants). # @!attribute date + # Date and time the terms were agreed, in RFC 3339 or ISO 8601 format. + # [Required] + # Format: date-time # @return [String] Date and time the terms were agreed (RFC 3339 / ISO 8601). # @!attribute ip_address + # IP address (IPv4 or IPv6) of the person at the time they agreed the terms. + # [Required] # @return [String] IP address (IPv4 or IPv6) of the person at the time they agreed. # @!attribute name + # First and last name of the person who agreed to the terms. + # [Required] # @return [String] First and last name of the person who agreed to the terms. # @!attribute email + # Email address of the person who agreed to the terms. + # [Required] + # Format: email # @return [String] Email address of the person who agreed to the terms. # @!attribute version + # Identifier of the terms version that was agreed. + # [Required] # @return [String] Identifier of the terms version that was agreed. class AgreedTerms attr_accessor :date, diff --git a/lib/checkout_sdk/accounts/articles_of_association.rb b/lib/checkout_sdk/accounts/articles_of_association.rb index 3bb672e..d974ea5 100644 --- a/lib/checkout_sdk/accounts/articles_of_association.rb +++ b/lib/checkout_sdk/accounts/articles_of_association.rb @@ -2,9 +2,16 @@ module CheckoutSdk module Accounts + # Memorandum or Articles of Association document. # @!attribute type - # @return [ArticlesOfAssociationType] + # The type of document used. + # [Required] + # @return [String] {ArticlesOfAssociationType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class ArticlesOfAssociation attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/articles_of_association_type.rb b/lib/checkout_sdk/accounts/articles_of_association_type.rb index 6879c08..d2d7e43 100644 --- a/lib/checkout_sdk/accounts/articles_of_association_type.rb +++ b/lib/checkout_sdk/accounts/articles_of_association_type.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The document types accepted as memorandum or articles of association. module ArticlesOfAssociationType MEMORANDUM_OF_ASSOCIATION = 'memorandum_of_association' ARTICLES_OF_ASSOCIATION = 'articles_of_association' diff --git a/lib/checkout_sdk/accounts/bank_verification.rb b/lib/checkout_sdk/accounts/bank_verification.rb index f25be5d..cbd9375 100644 --- a/lib/checkout_sdk/accounts/bank_verification.rb +++ b/lib/checkout_sdk/accounts/bank_verification.rb @@ -2,9 +2,16 @@ module CheckoutSdk module Accounts + # A document showing transactions from the last 3 months. # @!attribute type - # @return [BankVerificationType] + # The type of document being used as bank verification. + # [Required] + # @return [String] {BankVerificationType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class BankVerification attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/bank_verification_type.rb b/lib/checkout_sdk/accounts/bank_verification_type.rb index 594ac99..1a548f8 100644 --- a/lib/checkout_sdk/accounts/bank_verification_type.rb +++ b/lib/checkout_sdk/accounts/bank_verification_type.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The document type accepted as bank verification. module BankVerificationType BANK_STATEMENT = 'bank_statement' end diff --git a/lib/checkout_sdk/accounts/business_type.rb b/lib/checkout_sdk/accounts/business_type.rb index 65883b7..c589e15 100644 --- a/lib/checkout_sdk/accounts/business_type.rb +++ b/lib/checkout_sdk/accounts/business_type.rb @@ -2,6 +2,8 @@ module CheckoutSdk module Accounts + # The legal type of the company. Must be INDIVIDUAL_OR_SOLE_PROPRIETORSHIP for the sole trader variants; + # which other values a variant accepts depends on the variant. module BusinessType INDIVIDUAL_OR_SOLE_PROPRIETORSHIP = 'individual_or_sole_proprietorship' GENERAL_PARTNERSHIP = 'general_partnership' diff --git a/lib/checkout_sdk/accounts/certified_authorised_signatory.rb b/lib/checkout_sdk/accounts/certified_authorised_signatory.rb new file mode 100644 index 0000000..3b07d69 --- /dev/null +++ b/lib/checkout_sdk/accounts/certified_authorised_signatory.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # Certified authorised signatory document. Required when the legal representative or other role owner + # is not registered on the certificate of incorporation. Representative documents only + # (company.representatives[].documents), EEA, GB and US Company Full (3.0) and US ISV Seller Company + # (3.0). + # @!attribute type + # The type of document. + # [Required] + # @return [String] {CertifiedAuthorisedSignatoryType} + # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters + # @return [String] + class CertifiedAuthorisedSignatory + attr_accessor :type, + :front + end + end +end diff --git a/lib/checkout_sdk/accounts/certified_authorised_signatory_type.rb b/lib/checkout_sdk/accounts/certified_authorised_signatory_type.rb new file mode 100644 index 0000000..1653748 --- /dev/null +++ b/lib/checkout_sdk/accounts/certified_authorised_signatory_type.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # The document type accepted as a representative's certified authorised signatory document. + module CertifiedAuthorisedSignatoryType + POWER_OF_ATTORNEY = 'power_of_attorney' + end + end +end diff --git a/lib/checkout_sdk/accounts/citizenship.rb b/lib/checkout_sdk/accounts/citizenship.rb index 4bd2042..ab1d6a3 100644 --- a/lib/checkout_sdk/accounts/citizenship.rb +++ b/lib/checkout_sdk/accounts/citizenship.rb @@ -2,10 +2,15 @@ module CheckoutSdk module Accounts - # A citizenship or legal status held by a company representative (Accounts API v3.0). + # A citizenship or legal-status record of a representative (US ISV Seller variants). # @!attribute type + # The type of citizenship or legal status (for example citizenship or residency). + # [Optional] # @return [String] The type of citizenship or legal status (e.g. `citizenship`, `residency`). # @!attribute country + # The two-letter ISO 3166-1 alpha-2 country code. + # [Required] + # Format: iso-3166-1-alpha-2 # @return [String] {CheckoutSdk::Common::Country} two-letter ISO 3166-1 alpha-2 code. class Citizenship attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/company.rb b/lib/checkout_sdk/accounts/company.rb index fc1cfd3..bd5e6cd 100644 --- a/lib/checkout_sdk/accounts/company.rb +++ b/lib/checkout_sdk/accounts/company.rb @@ -2,31 +2,84 @@ module CheckoutSdk module Accounts + # Information about the company represented by the sub-entity: on every company and v3.0 sole trader + # variant, and as the controlling company of a {Representative} (where only legal_name, trading_name + # and registered_address apply). # @!attribute business_registration_number + # The sub-entity's business registration number: a Commercial Registration or Ministry of Commerce + # certificate number, or an equivalent registration number. + # [Required] for the Full variants and US ISV Seller Company (3.0); [Optional] for the Lite (2.0) + # variants. Not part of the sole trader variants. + # The format depends on the variant: + # EEA: min 2 characters, max 39 characters; a SIRET number for sub-entities based in France. + # GB (3.0): a Companies House number, 8 characters, matching one of the three alternatives of the + # spec's pattern, ^(A|B|C)$: + # A: ((AC|CE|CS|FC|FE|GE|GS|IC|LP|NC|NF|NI|NL|NO|NP|OC|OE|PC|R0|RC|SA|SC|SE|SF|SG|SI|SL|SO|SR|SZ|ZC|\d{2})\d{6}) + # B: ((IP|SP|RS)[A-Z\d]{6}) + # C: (SL\d{5}[\dA]) + # GB (2.0) accepts the same pattern case-insensitively. + # US: an Employer Identification Number (EIN), ^[0-9]{9}$, 9 characters; US ISV Seller Company (3.0) + # also accepts the hyphenated form, ^[0-9]{2}-?[0-9]{7}$, min 9 characters, max 11. # @return [String] # @!attribute business_type + # The legal type of the company ({BusinessType} values). Must be individual_or_sole_proprietorship for + # the sole trader variants. + # [Required], except on EEA and US Company Lite (2.0) where it is [Optional]. Not part of GB Company + # Full and Lite (2.0). # @return [String] {BusinessType} # @!attribute legal_name + # The legal name of the sub-entity. + # [Required] for every company variant and the controlling company; not part of the sole trader + # variants. + # min 2 characters, max 300 characters # @return [String] # @!attribute trading_name + # The trading name of the sub-entity, also referred to as 'doing business as'. + # [Required] + # min 2 characters, max 300 characters # @return [String] # @!attribute additional_trading_names + # The collection of additional trading names for the sub-entity. + # [Optional] (US ISV Seller variants only) # @return [Array(String)] # @!attribute is_registered_company + # Whether the sub-entity is a registered legal entity. Must be false for US ISV Seller Sole Trader + # (3.0). + # [Required] for US ISV Seller Sole Trader (3.0); not part of the other variants. # @return [Boolean] # @!attribute date_of_incorporation + # The date the company was incorporated, or the date the sole trader started trading. + # [Required] for every v3.0 variant; [Optional] for EEA, GB and US Company Full (2.0). # @return [DateOfIncorporation] # @!attribute regulatory_licence_number + # The regulatory licence number of the company. + # [Optional] (EEA Company Full (3.0) only) + # ^[a-zA-Z0-9\-]+$ + # min 4 characters, max 32 characters # @return [String] # @!attribute principal_address + # The primary location where business is performed. + # [Required] for every company and v3.0 sole trader variant. # @return [CheckoutSdk::Common::Address] # @!attribute registered_address + # The registered address of the company. + # [Required] for every company variant and the controlling company; not part of the sole trader + # variants. # @return [CheckoutSdk::Common::Address] # @!attribute representatives - # @return [Array(EntityRepresentative)] + # Information about the representatives of this company. + # [Required] + # min 1 item; max 1 item for the sole trader variants (the individual themselves, with roles [ubo]), + # max 5 on v2.0, max 25 on EEA, GB and US Company Full (3.0), no maximum on US ISV Seller Company + # (3.0) + # @return [Array(Representative)] # @!attribute document + # @deprecated Not defined by any Accounts API company schema; the API does not read it. # @return [EntityDocument] # @!attribute financial_details + # Seller financial questions. + # [Required] for EEA and US Company Full (2.0); [Optional] for EEA and US Company Lite (2.0). Not part + # of the other variants. # @return [EntityFinancialDetails] class Company attr_accessor :business_registration_number, diff --git a/lib/checkout_sdk/accounts/company_position.rb b/lib/checkout_sdk/accounts/company_position.rb index d1fab2e..be7b70c 100644 --- a/lib/checkout_sdk/accounts/company_position.rb +++ b/lib/checkout_sdk/accounts/company_position.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The position of a representative within the company (required for the control_person role). module CompanyPosition CEO = 'ceo' CFO = 'cfo' diff --git a/lib/checkout_sdk/accounts/company_verification.rb b/lib/checkout_sdk/accounts/company_verification.rb index 0813ca4..d1f2280 100644 --- a/lib/checkout_sdk/accounts/company_verification.rb +++ b/lib/checkout_sdk/accounts/company_verification.rb @@ -2,9 +2,18 @@ module CheckoutSdk module Accounts + # The document to use to confirm the company's identity (certified by a power of attorney within the + # last 3 months). # @!attribute type + # The type of document used for company verification. articles_of_association is accepted on the US + # Company (2.0) variants only. + # [Required] # @return [String] {CompanyVerificationType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class CompanyVerification attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/company_verification_type.rb b/lib/checkout_sdk/accounts/company_verification_type.rb index c433bff..764fd4f 100644 --- a/lib/checkout_sdk/accounts/company_verification_type.rb +++ b/lib/checkout_sdk/accounts/company_verification_type.rb @@ -2,6 +2,9 @@ module CheckoutSdk module Accounts + # The document types accepted as company verification. ARTICLES_OF_ASSOCIATION is accepted on the US + # Company (2.0) variants only; articles of association sent as their own document use + # {ArticlesOfAssociationType} instead. module CompanyVerificationType INCORPORATION_DOCUMENT = 'incorporation_document' ARTICLES_OF_ASSOCIATION = 'articles_of_association' diff --git a/lib/checkout_sdk/accounts/contact_details.rb b/lib/checkout_sdk/accounts/contact_details.rb index 3df6b5c..2dcb2f6 100644 --- a/lib/checkout_sdk/accounts/contact_details.rb +++ b/lib/checkout_sdk/accounts/contact_details.rb @@ -2,11 +2,29 @@ module CheckoutSdk module Accounts + # Contact details of the sub-entity. # @!attribute phone + # The phone number of the sub-entity. + # [Required] for every Accounts API v2.0 variant and the US ISV Seller variants; [Optional] for the + # other v3.0 variants. + # On v3.0 country_code is required and is the ISO 3166-1 alpha-2 country where the number is registered + # (for example FR), not the dialling code; v2.0 takes number only. number is the number without the + # country calling code, and its format depends on the variant: + # v3.0 EEA: ^[0-9]{6,13}$, min 6 characters, max 13 characters + # v3.0 GB: ^[0-9]{7,11}$, min 7 characters, max 11 characters + # v3.0 US and US ISV Seller: ^[1-9][0-9]{9,16}$, min 10 characters, max 16 characters + # v2.0: ^[1-9][0-9]{7,15}$, min 8 characters, max 16 characters; on the US v2.0 variants + # ^[2-9]{1}[0-9]{9,15}$, min 10 characters # @return [Phone] # @!attribute email_addresses + # Email addresses for this sub-entity. + # [Required] for every Accounts API v2.0 variant and the US ISV Seller variants; [Optional] for the + # other v3.0 variants. # @return [EntityEmailAddresses] # @!attribute invitee + # The details of the user responsible for onboarding the sub-entity. + # [Required] in the hosted onboarding invite request; [Optional] in the Full and Lite onboarding + # variants; not part of the US ISV Seller variants. # @return [Invitee] class ContactDetails attr_accessor :phone, diff --git a/lib/checkout_sdk/accounts/date_of_birth.rb b/lib/checkout_sdk/accounts/date_of_birth.rb index 2fc6381..d29af2e 100644 --- a/lib/checkout_sdk/accounts/date_of_birth.rb +++ b/lib/checkout_sdk/accounts/date_of_birth.rb @@ -2,11 +2,21 @@ module CheckoutSdk module Accounts + # The date of birth of the person according to the Gregorian calendar. # @!attribute day + # The calendar day of the month they were born. + # [Required] + # min 1, max 31 # @return [Integer] # @!attribute month + # The month of the year they were born. + # [Required] + # min 1, max 12 # @return [Integer] # @!attribute year + # The year they were born. + # [Required] + # min 1900, max 2999 # @return [Integer] class DateOfBirth attr_accessor :day, diff --git a/lib/checkout_sdk/accounts/date_of_incorporation.rb b/lib/checkout_sdk/accounts/date_of_incorporation.rb index 2652d0b..b65af2c 100644 --- a/lib/checkout_sdk/accounts/date_of_incorporation.rb +++ b/lib/checkout_sdk/accounts/date_of_incorporation.rb @@ -2,11 +2,21 @@ module CheckoutSdk module Accounts + # The date the company was incorporated, or the date the sole trader started trading. # @!attribute day + # The day of the month the company was incorporated. + # [Optional] + # min 1, max 31 # @return [Integer] # @!attribute month + # The month the company was incorporated. + # [Required] + # min 1, max 12 # @return [Integer] # @!attribute year + # The year the company was incorporated. + # [Required] + # min 1500, max 2999 # @return [Integer] class DateOfIncorporation attr_accessor :day, diff --git a/lib/checkout_sdk/accounts/document.rb b/lib/checkout_sdk/accounts/document.rb index 98523e7..ad06515 100644 --- a/lib/checkout_sdk/accounts/document.rb +++ b/lib/checkout_sdk/accounts/document.rb @@ -2,11 +2,23 @@ module CheckoutSdk module Accounts + # The document to use to confirm an individual's identity (identity_verification): on a representative + # ({RepresentativeDocuments}), or at the top level of the v2.0 sole trader variants. # @!attribute type + # The type of document used for identity verification. + # [Required] # @return [String] {DocumentType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] # @!attribute back + # The ID of the back side of the document as represented within Checkout.com systems. + # [Optional] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class Document attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/document_type.rb b/lib/checkout_sdk/accounts/document_type.rb index 4f27c88..e8415d4 100644 --- a/lib/checkout_sdk/accounts/document_type.rb +++ b/lib/checkout_sdk/accounts/document_type.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The document types accepted to confirm an individual's identity ({Document#type}). module DocumentType PASSPORT = 'passport' NATIONAL_IDENTITY_CARD = 'national_identity_card' diff --git a/lib/checkout_sdk/accounts/entity_document.rb b/lib/checkout_sdk/accounts/entity_document.rb index 10b7634..ee622e5 100644 --- a/lib/checkout_sdk/accounts/entity_document.rb +++ b/lib/checkout_sdk/accounts/entity_document.rb @@ -2,9 +2,14 @@ module CheckoutSdk module Accounts + # Not defined by any Accounts API onboarding schema. Referenced only by the deprecated {Company#document} + # and {EntityFinancialDocuments}. + # @deprecated Not part of any Accounts API onboarding schema. # @!attribute type + # @deprecated Not defined by any Accounts API onboarding schema. # @return [String] # @!attribute file_id + # @deprecated Not defined by any Accounts API onboarding schema. # @return [String] class EntityDocument attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/entity_email_addresses.rb b/lib/checkout_sdk/accounts/entity_email_addresses.rb index 953111a..0e23125 100644 --- a/lib/checkout_sdk/accounts/entity_email_addresses.rb +++ b/lib/checkout_sdk/accounts/entity_email_addresses.rb @@ -2,10 +2,21 @@ module CheckoutSdk module Accounts + # Email addresses for this sub-entity. # @!attribute primary + # The main email address for this sub-entity. + # [Required] + # Format: email + # @return [String] + # @!attribute pci_compliance_contact + # The email address of the person responsible for PCI compliance at this sub-entity. + # [Required] for the US ISV Seller variants (3.0), together with primary; not part of the other + # variants. + # Format: email # @return [String] class EntityEmailAddresses - attr_accessor :primary + attr_accessor :primary, + :pci_compliance_contact end end end diff --git a/lib/checkout_sdk/accounts/entity_files_request.rb b/lib/checkout_sdk/accounts/entity_files_request.rb index 84d4c3d..9fcb8fa 100644 --- a/lib/checkout_sdk/accounts/entity_files_request.rb +++ b/lib/checkout_sdk/accounts/entity_files_request.rb @@ -2,10 +2,11 @@ module CheckoutSdk module Accounts - # Request body for POST /entities/{entityId}/files. - # + # Request for POST /entities/{entityId}/files. # @!attribute purpose - # @return [String] Purpose of the file (e.g. "bank_verification"). + # The purpose of the file upload: the onboarding document the file is for. + # [Required] + # @return [String] {FilePurpose} class EntityFilesRequest attr_accessor :purpose end diff --git a/lib/checkout_sdk/accounts/entity_financial_details.rb b/lib/checkout_sdk/accounts/entity_financial_details.rb index 2cf9967..a418e31 100644 --- a/lib/checkout_sdk/accounts/entity_financial_details.rb +++ b/lib/checkout_sdk/accounts/entity_financial_details.rb @@ -2,15 +2,30 @@ module CheckoutSdk module Accounts + # Seller financial questions (financial_details): on the company of EEA and US Company Full and Lite + # (2.0), and on the individual of US Sole Trader Full and Lite (2.0). # @!attribute annual_processing_volume + # The estimated annual processing volume. In minor units without decimals. + # [Required] on the Full (2.0) variants; [Optional] on the Lite (2.0) variants. + # min 0 # @return [Integer] # @!attribute average_transaction_value + # The expected average transaction value. In minor units without decimals. + # [Required] on the Full (2.0) variants; [Optional] on the Lite (2.0) variants. + # min 0 # @return [Integer] # @!attribute highest_transaction_value + # The expected highest transaction value. In minor units without decimals. + # [Required] on the Full (2.0) variants; [Optional] on the Lite (2.0) variants. + # min 0 # @return [Integer] # @!attribute documents + # @deprecated Not defined by any Accounts API schema; the API does not read it. Supporting documents go on + # {OnboardSubEntityDocuments} instead. # @return [EntityFinancialDocuments] # @!attribute currency + # The currency used for the financial details provided. + # [Required] on US Company Full and US Sole Trader Full (2.0); [Optional] on the other variants. # @return [String] {CheckoutSdk::Common::Currency} class EntityFinancialDetails attr_accessor :annual_processing_volume, diff --git a/lib/checkout_sdk/accounts/entity_financial_documents.rb b/lib/checkout_sdk/accounts/entity_financial_documents.rb index f0f75fd..7eadfad 100644 --- a/lib/checkout_sdk/accounts/entity_financial_documents.rb +++ b/lib/checkout_sdk/accounts/entity_financial_documents.rb @@ -2,9 +2,14 @@ module CheckoutSdk module Accounts + # Not defined by any Accounts API schema: financial_details carries the three amounts and the currency + # only. + # @deprecated Not part of any Accounts API schema; the API does not read it. # @!attribute bank_statement + # @deprecated Not defined by any Accounts API schema. # @return [EntityDocument] # @!attribute financial_statement + # @deprecated Not defined by any Accounts API schema. # @return [EntityDocument] class EntityFinancialDocuments attr_accessor :bank_statement, diff --git a/lib/checkout_sdk/accounts/entity_roles.rb b/lib/checkout_sdk/accounts/entity_roles.rb index ceeb540..c19da19 100644 --- a/lib/checkout_sdk/accounts/entity_roles.rb +++ b/lib/checkout_sdk/accounts/entity_roles.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # A role a representative holds within the company. For sole traders, the only accepted role is UBO. module EntityRoles UBO = 'ubo' LEGAL_REPRESENTATIVE = 'legal_representative' diff --git a/lib/checkout_sdk/accounts/file_purpose.rb b/lib/checkout_sdk/accounts/file_purpose.rb new file mode 100644 index 0000000..2dd096a --- /dev/null +++ b/lib/checkout_sdk/accounts/file_purpose.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # The purpose of an onboarding document upload ({AccountsClient#upload_file} and + # {AccountsClient#upload_entity_file}): the values PlatformsFileUpload defines. + module FilePurpose + ADDITIONAL_DOCUMENT = 'additional_document' + ARTICLES_OF_ASSOCIATION = 'articles_of_association' + BANK_VERIFICATION = 'bank_verification' + CERTIFIED_AUTHORISED_SIGNATORY = 'certified_authorised_signatory' + COMPANY_OWNERSHIP = 'company_ownership' + COMPANY_VERIFICATION = 'company_verification' + FINANCIAL_VERIFICATION = 'financial_verification' + IDENTITY_VERIFICATION = 'identity_verification' + PROOF_OF_LEGALITY = 'proof_of_legality' + PROOF_OF_PRINCIPAL_ADDRESS = 'proof_of_principal_address' + SHAREHOLDER_STRUCTURE = 'shareholder_structure' + TAX_VERIFICATION = 'tax_verification' + PROOF_OF_RESIDENTIAL_ADDRESS = 'proof_of_residential_address' + PROOF_OF_REGISTRATION = 'proof_of_registration' + end + end +end diff --git a/lib/checkout_sdk/accounts/file_request.rb b/lib/checkout_sdk/accounts/file_request.rb index f90ab21..e0829d1 100644 --- a/lib/checkout_sdk/accounts/file_request.rb +++ b/lib/checkout_sdk/accounts/file_request.rb @@ -2,6 +2,13 @@ module CheckoutSdk module Accounts + # A file to upload with {AccountsClient#upload_file} (POST /files on the Files host), sent as a + # multipart request. The returned ID is what document front and back attributes take. + # + # The Files host POST /files is not in the API reference: the POST /files it documents is the disputes + # upload on the API host (purpose dispute_evidence or arbitration_evidence). For onboarding, set + # purpose to one of the PlatformsFileUpload purposes the API reference lists for the sub-entity upload + # (POST /entities/{entityId}/files), available as {FilePurpose} values. class FileRequest < CheckoutSdk::Common::FileRequest end end diff --git a/lib/checkout_sdk/accounts/financial_statements.rb b/lib/checkout_sdk/accounts/financial_statements.rb index 38c55ad..9757cc4 100644 --- a/lib/checkout_sdk/accounts/financial_statements.rb +++ b/lib/checkout_sdk/accounts/financial_statements.rb @@ -2,9 +2,17 @@ module CheckoutSdk module Accounts + # Audited or management-prepared financial statements (when applicable). US ISV Seller variants only. + # Not the same document as {FinancialVerification}, whose type is the singular financial_statement. # @!attribute type - # @return [FinancialStatementsType] + # The type of document. + # [Required] + # @return [String] {FinancialStatementsType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class FinancialStatements attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/financial_statements_type.rb b/lib/checkout_sdk/accounts/financial_statements_type.rb index 383e261..2c33082 100644 --- a/lib/checkout_sdk/accounts/financial_statements_type.rb +++ b/lib/checkout_sdk/accounts/financial_statements_type.rb @@ -2,6 +2,8 @@ module CheckoutSdk module Accounts + # The document type accepted as financial statements (US ISV Seller variants). Note the plural + # financial_statements; {FinancialVerificationType} is a different module. module FinancialStatementsType FINANCIAL_STATEMENTS = 'financial_statements' end diff --git a/lib/checkout_sdk/accounts/financial_verification.rb b/lib/checkout_sdk/accounts/financial_verification.rb index 4176906..ca3863a 100644 --- a/lib/checkout_sdk/accounts/financial_verification.rb +++ b/lib/checkout_sdk/accounts/financial_verification.rb @@ -2,13 +2,21 @@ module CheckoutSdk module Accounts + # Financial statement document. Becomes mandatory depending on the answer provided for + # annual_processing_volume; the sub-entity's status changes to requirements_due when it is needed. # @!attribute type - # @return [FinancialVerificationType] + # The type of the file. + # [Required] + # @return [String] {FinancialVerificationType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class FinancialVerification - attr_reader :type, - :front + attr_accessor :type, + :front end end end diff --git a/lib/checkout_sdk/accounts/financial_verification_type.rb b/lib/checkout_sdk/accounts/financial_verification_type.rb index c9ed63b..f575bd9 100644 --- a/lib/checkout_sdk/accounts/financial_verification_type.rb +++ b/lib/checkout_sdk/accounts/financial_verification_type.rb @@ -2,6 +2,8 @@ module CheckoutSdk module Accounts + # The document type accepted as financial verification. Note the singular financial_statement; + # {FinancialStatementsType} is a different module. module FinancialVerificationType FINANCIAL_STATEMENT = 'financial_statement' end diff --git a/lib/checkout_sdk/accounts/identification.rb b/lib/checkout_sdk/accounts/identification.rb index aa37fb5..35fe3bc 100644 --- a/lib/checkout_sdk/accounts/identification.rb +++ b/lib/checkout_sdk/accounts/identification.rb @@ -2,9 +2,16 @@ module CheckoutSdk module Accounts + # The identification of a representative or individual on the Accounts API v2.0 US variants. # @!attribute national_id_number + # Social Security Number (SSN), or Individual Taxpayer Identification Number (ITIN) for non-US + # citizens. + # [Required] + # ^\d{9}$ + # 9 characters # @return [String] # @!attribute document + # @deprecated Not defined by the Accounts API: the identification object carries national_id_number only. # @return [Document] class Identification attr_accessor :national_id_number, diff --git a/lib/checkout_sdk/accounts/individual.rb b/lib/checkout_sdk/accounts/individual.rb index 2e29556..9afc3c9 100644 --- a/lib/checkout_sdk/accounts/individual.rb +++ b/lib/checkout_sdk/accounts/individual.rb @@ -2,27 +2,53 @@ module CheckoutSdk module Accounts + # The top-level individual of the Accounts API v2.0 sole trader variants. On v3.0 a sole trader is + # onboarded as a {Company} with one {Representative}. # @!attribute first_name + # The individual's first name. + # [Required] + # min 2 characters, max 50 characters # @return [String] # @!attribute middle_name + # The individual's middle name. Required if it appears in official documents. + # [Optional] + # min 2 characters, max 50 characters # @return [String] # @!attribute last_name + # The individual's last name. + # [Required] + # min 2 characters, max 50 characters # @return [String] # @!attribute legal_name + # @deprecated Not defined by the Accounts API for an individual; legal_name exists on the company only. # @return [String] # @!attribute trading_name + # The trading name of the sub-entity, also referred to as 'doing business as'. + # [Required] + # min 2 characters, max 300 characters # @return [String] # @!attribute national_tax_id + # @deprecated Not defined by any Accounts API schema; the API does not read it. # @return [String] # @!attribute registered_address + # The registered address of the sole trader's business. + # [Required] # @return [CheckoutSdk::Common::Address] # @!attribute date_of_birth + # The date of birth of the person according to the Gregorian calendar. + # [Required], except on GB Sole Trader Lite (2.0) where it is [Optional]. # @return [DateOfBirth] # @!attribute place_of_birth + # The place of birth of the person. + # [Required] for EEA Sole Trader Full and Lite (2.0); not part of the other v2.0 variants. # @return [PlaceOfBirth] # @!attribute identification + # The individual's identification. US Sole Trader (2.0) only. + # [Required] for US Sole Trader Full (2.0); [Optional] for US Sole Trader Lite (2.0). # @return [Identification] # @!attribute financial_details + # Seller financial questions. US Sole Trader (2.0) only. + # [Required] for US Sole Trader Full (2.0); [Optional] for US Sole Trader Lite (2.0). # @return [EntityFinancialDetails] class Individual attr_accessor :first_name, diff --git a/lib/checkout_sdk/accounts/invitee.rb b/lib/checkout_sdk/accounts/invitee.rb index be50ef6..826808b 100644 --- a/lib/checkout_sdk/accounts/invitee.rb +++ b/lib/checkout_sdk/accounts/invitee.rb @@ -2,7 +2,13 @@ module CheckoutSdk module Accounts + # The details of the user responsible for onboarding the sub-entity. # @!attribute email + # The main email address for this sub-entity. Despite the spec's wording, this is the address of the + # invitee, the user responsible for onboarding the sub-entity. + # [Required] in the hosted onboarding invite request; [Optional] in the Full and Lite onboarding + # variants; not part of the US ISV Seller variants. + # Format: email # @return [String] class Invitee attr_accessor :email diff --git a/lib/checkout_sdk/accounts/national_id_type.rb b/lib/checkout_sdk/accounts/national_id_type.rb index 4b435a4..a98797a 100644 --- a/lib/checkout_sdk/accounts/national_id_type.rb +++ b/lib/checkout_sdk/accounts/national_id_type.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The classification of a representative's national identification number (US ISV Seller variants). module NationalIdType SSN = 'ssn' ITIN = 'itin' diff --git a/lib/checkout_sdk/accounts/onboard_entity.rb b/lib/checkout_sdk/accounts/onboard_entity.rb index 4f6a15d..4b63d9f 100644 --- a/lib/checkout_sdk/accounts/onboard_entity.rb +++ b/lib/checkout_sdk/accounts/onboard_entity.rb @@ -2,33 +2,60 @@ module CheckoutSdk module Accounts + # The request body of POST /accounts/entities and PUT /accounts/entities/{id}. Which attributes are + # required depends on the onboarding variant. # @!attribute reference + # A unique reference you can later use to identify the sub-entity. + # [Required] + # min 1 character, max 50 characters # @return [String] # @!attribute is_draft + # Whether the sub-entity should remain in draft on PUT, skipping due diligence checks. POST always + # creates the entity in draft. + # [Optional] # @return [Boolean] # @!attribute profile + # Information about the profile of the sub-entity. + # [Required] # @return [Profile] # @!attribute contact_details + # Contact details of this sub-entity. + # [Required], except on EEA Company Full (3.0) where it is [Optional]. # @return [ContactDetails] # @!attribute company + # Information about the company represented by the sub-entity. + # [Required] for every company and v3.0 sole trader variant. # @return [Company] # @!attribute processing_details + # Information about the sub-entity's expected processing. + # [Required] for every v3.0 variant. # @return [ProcessingDetails] # @!attribute individual + # Information about the individual represented by the sub-entity. Accounts API v2.0 sole traders only. + # [Required] for the v2.0 sole trader variants. + # @deprecated Not used by the Accounts API v3.0 schema, where a sole trader is onboarded as a company with a + # representative. # @return [Individual] # @!attribute documents + # The top-level documents used to support the verification of the sub-entity's details. + # [Required] on the EEA, GB and US Company and Sole Trader Full (3.0) variants, EEA Company Full (2.0) + # and EEA Sole Trader Full (2.0); [Optional] otherwise. # @return [OnboardSubEntityDocuments] # @!attribute additional_info + # @deprecated Not defined by any Accounts API schema; the API does not read it. # @return [AdditionalInfo] # @!attribute seller_category + # The identifier of a seller category set up for your platform. Seller categories define the pricing, + # capabilities and risk profile applied to sub-entities. + # [Required] for the US ISV Seller variants only. # @return [String] Identifier of a seller category configured on the platform - # during onboarding. Used for US ISV onboarding variants. # @!attribute agreed_terms + # Details of the person who agreed to the terms and conditions on behalf of the sub-entity. + # [Required] for the US ISV Seller variants only. # @return [AgreedTerms] Details of the person who agreed to the terms and - # conditions (Accounts API v3.0 SaaS onboarding). # @!attribute submitter + # @deprecated Not defined by any Accounts API schema; the API does not read it. # @return [Submitter] Captures evidence of the end-user's consent to onboarding. - # Used for US ISV onboarding variants. class OnboardEntity attr_accessor :reference, :is_draft, diff --git a/lib/checkout_sdk/accounts/onboard_sub_entity_documents.rb b/lib/checkout_sdk/accounts/onboard_sub_entity_documents.rb index 1a00786..d93362d 100644 --- a/lib/checkout_sdk/accounts/onboard_sub_entity_documents.rb +++ b/lib/checkout_sdk/accounts/onboard_sub_entity_documents.rb @@ -2,31 +2,68 @@ module CheckoutSdk module Accounts + # The top-level request documents ({OnboardEntity#documents}). The API ignores keys it does not + # recognise here rather than rejecting them, so a misplaced document is dropped silently. The + # representative's own documents go on {Representative#documents} ({RepresentativeDocuments}). # @!attribute identity_verification - # @return [EntityIdentificationDocument] + # The document to use to confirm the individual's identity. + # [Required] for the six sole trader variants of Accounts API v2.0 (EEA, GB and US, Full and Lite), the + # only variants that take it at this level. On v3.0 it belongs on the representative. + # @return [Document] # @!attribute company_verification + # The document to use to confirm the company's identity (certified by a power of attorney within the + # last 3 months). + # [Required] for EEA Company Full (2.0 and 3.0) and GB Company Full (2.0); [Optional] for the other + # company variants and the US ISV Seller variants. # @return [CompanyVerification] # @!attribute articles_of_association + # Memorandum or Articles of Association document. + # [Required] for EEA and GB Company Full (3.0); [Optional] for US Company Full (3.0) and the US ISV + # Seller variants. # @return [ArticlesOfAssociation] # @!attribute bank_verification + # A document showing transactions from the last 3 months. + # [Required] for EEA Company Full (3.0) and the EEA, GB and US Sole Trader Full (3.0) variants; + # [Optional] for GB and US Company Full (3.0) and EEA Company Full and Lite (2.0). # @return [BankVerification] # @!attribute shareholder_structure + # Shareholder structure chart (including % of shares) certified by a competent authority individual and + # dated within the last 3 months. + # [Required] for EEA and GB Company Full (3.0); [Optional] for US Company Full (3.0) and US ISV Seller + # Company (3.0). # @return [ShareholderStructure] # @!attribute proof_of_legality + # A regulatory licence document required for the company to operate (when applicable). + # [Optional] (EEA, GB and US Company Full (3.0) and the US ISV Seller variants) # @return [ProofOfLegality] # @!attribute proof_of_principal_address + # Proof of the company's principal place of business. + # [Optional] (EEA, GB and US Company Full (3.0) and the US ISV Seller variants) # @return [ProofOfPrincipalAddress] # @!attribute additional_document1 + # Additional space for documents to be provided when requested. + # [Optional] (EEA, GB and US Company and Sole Trader Full (3.0); not the US ISV Seller variants) # @return [AdditionalDocument] # @!attribute additional_document2 + # Additional space for documents to be provided when requested. + # [Optional] (EEA, GB and US Company and Sole Trader Full (3.0); not the US ISV Seller variants) # @return [AdditionalDocument] # @!attribute additional_document3 + # Additional space for documents to be provided when requested. + # [Optional] (EEA, GB and US Company and Sole Trader Full (3.0); not the US ISV Seller variants) # @return [AdditionalDocument] # @!attribute tax_verification + # IRS-issued Employer Identification Number document used to verify the entity's tax identification. + # [Optional] (US Company variants and the US ISV Seller variants only) # @return [TaxVerification] # @!attribute financial_verification + # Financial statement document. Becomes mandatory depending on the answer provided for + # annual_processing_volume. + # [Optional] (EEA Company Full and Lite (2.0) only) # @return [FinancialVerification] # @!attribute financial_statements + # Audited or management-prepared financial statements (when applicable). + # [Optional] (US ISV Seller variants only) # @return [FinancialStatements] class OnboardSubEntityDocuments attr_accessor :identity_verification, diff --git a/lib/checkout_sdk/accounts/phone.rb b/lib/checkout_sdk/accounts/phone.rb index 533ba5e..df8bcd6 100644 --- a/lib/checkout_sdk/accounts/phone.rb +++ b/lib/checkout_sdk/accounts/phone.rb @@ -2,9 +2,15 @@ module CheckoutSdk module Accounts + # A phone number on the Accounts API: the sub-entity's contact phone, or a representative's phone. See + # {ContactDetails#phone} for the per-variant number format. # @!attribute country_code - # @return [String] ISO 3166-1 alpha-2 country code (Accounts API v3.0), e.g. "GB". + # The ISO 3166-1 alpha-2 country where the number is registered, not the dialling code. + # [Required] on Accounts API v3.0; not part of the v2.0 schemas. + # @return [String] # @!attribute number + # The phone number, without the country calling code. + # [Required] # @return [String] class Phone attr_accessor :country_code, diff --git a/lib/checkout_sdk/accounts/place_of_birth.rb b/lib/checkout_sdk/accounts/place_of_birth.rb index 6b9e70d..258d83e 100644 --- a/lib/checkout_sdk/accounts/place_of_birth.rb +++ b/lib/checkout_sdk/accounts/place_of_birth.rb @@ -2,7 +2,11 @@ module CheckoutSdk module Accounts + # The place of birth of the person. # @!attribute country + # The country code (iso-3166-1 alpha-2). + # [Required] + # Format: iso-3166-1-alpha-2 # @return [String] {CheckoutSdk::Common::Country} class PlaceOfBirth attr_accessor :country diff --git a/lib/checkout_sdk/accounts/processing_details.rb b/lib/checkout_sdk/accounts/processing_details.rb index 0197a6f..54636b5 100644 --- a/lib/checkout_sdk/accounts/processing_details.rb +++ b/lib/checkout_sdk/accounts/processing_details.rb @@ -2,21 +2,48 @@ module CheckoutSdk module Accounts + # The sub-entity's expected processing (processing_details, Accounts API v3.0). # @!attribute settlement_country + # The country code (iso-3166-1 alpha-2) where the settlement bank account is located. + # [Required] on the EEA, GB and US Company and Sole Trader Full (3.0) variants; not part of the US ISV + # Seller variants. + # Format: iso-3166-1-alpha-2 + # 2 characters # @return [String] # @!attribute target_countries + # Target country codes (iso-3166-1 alpha-2) with more than 10% expected volume processing with + # Checkout.com. + # [Required] + # min 1 item, max 10 items # @return [Array(String)] # @!attribute annual_processing_volume + # The estimated annual processing volume. In minor units without decimals. + # [Required] + # min 0 # @return [Integer] # @!attribute average_transaction_value + # The expected average transaction value. In minor units without decimals. + # [Required] + # min 0 # @return [Integer] # @!attribute average_order_fulfillment_time + # The average time in days between accepting payment and fulfilling the order. + # [Required] on the US ISV Seller variants only. + # min 0 # @return [Integer] # @!attribute highest_transaction_value + # The expected highest transaction value. In minor units without decimals. + # [Required] on the EEA, GB and US Company and Sole Trader Full (3.0) variants; not part of the US ISV + # Seller variants. + # min 0 # @return [Integer] # @!attribute currency + # The currency used for the processing details provided. + # [Required] # @return [CheckoutSdk::Common::Currency] # @!attribute payments + # Payment method-specific processing details. + # [Required] on the US ISV Seller variants only. # @return [ProcessingDetailsPayments] class ProcessingDetails attr_accessor :settlement_country, diff --git a/lib/checkout_sdk/accounts/processing_details_ach.rb b/lib/checkout_sdk/accounts/processing_details_ach.rb index 1267ca3..a7235ca 100644 --- a/lib/checkout_sdk/accounts/processing_details_ach.rb +++ b/lib/checkout_sdk/accounts/processing_details_ach.rb @@ -2,14 +2,27 @@ module CheckoutSdk module Accounts - # ACH-specific processing details (Accounts API v3.0). All values in minor units without decimals. + # The expected ACH processing (US ISV Seller variants). All amounts are in minor units without + # decimals. # @!attribute annual_ach_volume + # The estimated annual ACH processing volume. + # [Required] + # min 0 # @return [Integer] # @!attribute average_ach_transaction_size + # The expected average ACH transaction size. + # [Required] + # min 0 # @return [Integer] # @!attribute estimated_monthly_credit_volume + # The estimated monthly volume of ACH credit transactions (for example, refunds issued to customers). + # [Required] + # min 0 # @return [Integer] # @!attribute average_credit_amount + # The average value of an ACH credit transaction (for example, a refund). + # [Required] + # min 0 # @return [Integer] class ProcessingDetailsAch attr_accessor :annual_ach_volume, diff --git a/lib/checkout_sdk/accounts/processing_details_payments.rb b/lib/checkout_sdk/accounts/processing_details_payments.rb index 153224d..2314982 100644 --- a/lib/checkout_sdk/accounts/processing_details_payments.rb +++ b/lib/checkout_sdk/accounts/processing_details_payments.rb @@ -2,8 +2,10 @@ module CheckoutSdk module Accounts - # Payment method-specific processing details (Accounts API v3.0). + # Payment method-specific processing details (US ISV Seller variants). # @!attribute ach + # The ACH processing details. + # [Required] # @return [ProcessingDetailsAch] class ProcessingDetailsPayments attr_accessor :ach diff --git a/lib/checkout_sdk/accounts/profile.rb b/lib/checkout_sdk/accounts/profile.rb index 048d61a..9e677e2 100644 --- a/lib/checkout_sdk/accounts/profile.rb +++ b/lib/checkout_sdk/accounts/profile.rb @@ -2,13 +2,27 @@ module CheckoutSdk module Accounts + # Information about the profile of the sub-entity, primarily regarding the products and services + # offered. # @!attribute urls + # A collection of website URLs the sub-entity accepts payments on. + # [Required] + # max 100 items; each ^(http|https):\/\/\S{2,293}$, Format: uri # @return [Array(String)] # @!attribute mccs + # The merchant category codes (4-digit ISO 18245) that most closely describe the business. + # [Required] + # min 1 item, max 5 items; each ^[0-9]{4}$ # @return [Array(String)] # @!attribute default_holding_currency + # The default holding currency (ISO 4217). + # [Required] on every v3.0 variant; [Optional] on the v2.0 variants. + # Format: iso-4217 # @return [String] {CheckoutSdk::Common::Currency} # @!attribute holding_currencies + # The currencies incoming funds are held in. + # [Required] on every v3.0 variant; [Optional] on the v2.0 variants. + # min 1 item on v3.0; USD only on the US variants # @return [Array(CheckoutSdk::Common::Currency)] class Profile attr_accessor :urls, diff --git a/lib/checkout_sdk/accounts/proof_of_legality.rb b/lib/checkout_sdk/accounts/proof_of_legality.rb index 3b31aec..4459374 100644 --- a/lib/checkout_sdk/accounts/proof_of_legality.rb +++ b/lib/checkout_sdk/accounts/proof_of_legality.rb @@ -2,9 +2,16 @@ module CheckoutSdk module Accounts + # A regulatory licence document required for the company to operate (when applicable). # @!attribute type - # @return [ProofOfLegalityType] + # The type of document used for proof of legality. + # [Required] + # @return [String] {ProofOfLegalityType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class ProofOfLegality attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/proof_of_legality_type.rb b/lib/checkout_sdk/accounts/proof_of_legality_type.rb index e3c3e2c..f1abab0 100644 --- a/lib/checkout_sdk/accounts/proof_of_legality_type.rb +++ b/lib/checkout_sdk/accounts/proof_of_legality_type.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The document type accepted as proof of legality. module ProofOfLegalityType PROOF_OF_LEGALITY = 'proof_of_legality' end diff --git a/lib/checkout_sdk/accounts/proof_of_principal_address.rb b/lib/checkout_sdk/accounts/proof_of_principal_address.rb index f9de87a..1a5d5c6 100644 --- a/lib/checkout_sdk/accounts/proof_of_principal_address.rb +++ b/lib/checkout_sdk/accounts/proof_of_principal_address.rb @@ -2,13 +2,20 @@ module CheckoutSdk module Accounts + # Proof of the company's principal place of business. # @!attribute type - # @return [ProofOfPrincipalAddressType] + # The type of document being used as address verification. + # [Required] + # @return [String] {ProofOfPrincipalAddressType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class ProofOfPrincipalAddress - attr_reader :type, - :front + attr_accessor :type, + :front end end end diff --git a/lib/checkout_sdk/accounts/proof_of_principal_address_type.rb b/lib/checkout_sdk/accounts/proof_of_principal_address_type.rb index a8f2dca..d701948 100644 --- a/lib/checkout_sdk/accounts/proof_of_principal_address_type.rb +++ b/lib/checkout_sdk/accounts/proof_of_principal_address_type.rb @@ -2,6 +2,9 @@ module CheckoutSdk module Accounts + # The document type accepted as proof of the company's principal place of business. Same + # proof_of_address value as {ProofOfResidentialAddressType}, but the API defines the two as separate + # enums on separate documents. module ProofOfPrincipalAddressType PROOF_OF_ADDRESS = 'proof_of_address' end diff --git a/lib/checkout_sdk/accounts/proof_of_registration.rb b/lib/checkout_sdk/accounts/proof_of_registration.rb new file mode 100644 index 0000000..5743597 --- /dev/null +++ b/lib/checkout_sdk/accounts/proof_of_registration.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # Proof of the sole trader's registration, for example an extract from a trade register. + # Representative documents only (company.representatives[].documents), EEA Sole Trader Full (3.0). + # @!attribute type + # The type of document being used as proof of registration. + # [Required] + # @return [String] {ProofOfRegistrationType} + # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters + # @return [String] + class ProofOfRegistration + attr_accessor :type, + :front + end + end +end diff --git a/lib/checkout_sdk/accounts/proof_of_registration_type.rb b/lib/checkout_sdk/accounts/proof_of_registration_type.rb new file mode 100644 index 0000000..dd1f875 --- /dev/null +++ b/lib/checkout_sdk/accounts/proof_of_registration_type.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # The document types accepted as a sole trader's proof of registration (EEA Sole Trader Full (3.0)). + module ProofOfRegistrationType + EXTRACT_FROM_TRADE_REGISTER = 'extract_from_trade_register' + OTHER = 'other' + end + end +end diff --git a/lib/checkout_sdk/accounts/proof_of_residential_address.rb b/lib/checkout_sdk/accounts/proof_of_residential_address.rb new file mode 100644 index 0000000..243a76e --- /dev/null +++ b/lib/checkout_sdk/accounts/proof_of_residential_address.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # Proof of residential address of the representative. Representative documents only + # (company.representatives[].documents), EEA Sole Trader Full (3.0). + # @!attribute type + # The type of document being used as address verification. + # [Required] + # @return [String] {ProofOfResidentialAddressType} + # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters + # @return [String] + class ProofOfResidentialAddress + attr_accessor :type, + :front + end + end +end diff --git a/lib/checkout_sdk/accounts/proof_of_residential_address_type.rb b/lib/checkout_sdk/accounts/proof_of_residential_address_type.rb new file mode 100644 index 0000000..5fd0e13 --- /dev/null +++ b/lib/checkout_sdk/accounts/proof_of_residential_address_type.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # The document type accepted as a representative's proof of residential address (EEA Sole Trader Full + # (3.0)). Same proof_of_address value as {ProofOfPrincipalAddressType}, but the API defines the two as + # separate enums on separate documents. + module ProofOfResidentialAddressType + PROOF_OF_ADDRESS = 'proof_of_address' + end + end +end diff --git a/lib/checkout_sdk/accounts/representative.rb b/lib/checkout_sdk/accounts/representative.rb index 0882028..60b5c7c 100644 --- a/lib/checkout_sdk/accounts/representative.rb +++ b/lib/checkout_sdk/accounts/representative.rb @@ -2,32 +2,93 @@ module CheckoutSdk module Accounts + # A representative of the sub-entity. One class covers every shape the Accounts API defines: the v3.0 + # person of interest (individual, roles, company_position, ownership_percentage, documents), the v3.0 + # controlling company of EEA and GB Company Full (company, ownership_percentage), and the v2.0 company + # representative (the flat person fields, roles, documents and, on the US variants, identification). # @!attribute id + # The representative's id. + # [Optional] + # ^rep_[a-z0-9]{26}$ + # 30 characters # @return [String] # @!attribute individual + # Information about the individual representing the sub-entity. + # [Required] for every v3.0 person of interest. # @return [RepresentativeIndividual] Personal details (Accounts API v3.0). # @!attribute company_position + # The position of the representative within the company (required for the control_person role). + # [Optional] (EEA, GB and US Company Full (3.0) and US ISV Seller Company (3.0)) # @return [String] {CompanyPosition} # @!attribute ownership_percentage + # The percentage ownership of the UBO or controlling company (required when over 25%). + # [Optional] + # min 25, max 100 on the EEA, GB and US Company Full (3.0) variants; min 0, max 100 on the US ISV + # Seller variants # @return [Integer] + # @!attribute roles + # The individual's roles within the company ({EntityRoles} values). For sole traders, must be ubo only. + # [Required] for every variant except EEA and US Company Lite (2.0), where it is [Optional]. + # @return [Array(String)] {EntityRoles} + # @!attribute documents + # Verification documents for the individual representative. On the EEA, GB and US Company Full (3.0) + # and Sole Trader Full (3.0) variants the API validates this object strictly and rejects any key the + # variant does not define; the US ISV Seller (3.0) and v2.0 variants do not declare it strict. See + # {RepresentativeDocuments} for the keys each variant accepts. + # [Required] for the EEA, GB and US Sole Trader Full (3.0) variants and EEA Company Full (2.0); + # [Optional] otherwise. + # @return [RepresentativeDocuments] + # @!attribute company + # The controlling company, when the representative is a company rather than an individual. + # [Required] for a controlling company representative (EEA and GB Company Full (3.0) only). + # The API reads only three attributes here, all [Required]: legal_name, trading_name and + # registered_address. Leave the other {Company} attributes unset. + # @return [Company] # @!attribute first_name + # The representative's first name. Accounts API v2.0 only. + # [Required] (v2.0) + # min 2 characters, max 50 characters + # @deprecated Not used by the Accounts API v3.0 schema; use individual. + # @return [String] + # @!attribute middle_name + # The representative's middle name. Required if it appears in official documents. Accounts API v2.0 + # only. + # [Optional] + # min 2 characters, max 50 characters + # @deprecated Not used by the Accounts API v3.0 schema; use individual. # @return [String] # @!attribute last_name + # The representative's last name. Accounts API v2.0 only. + # [Required] (v2.0) + # min 2 characters, max 50 characters + # @deprecated Not used by the Accounts API v3.0 schema; use individual. # @return [String] # @!attribute address + # The representative's address. Accounts API v2.0 only. + # [Required] (v2.0) + # @deprecated Not used by the Accounts API v3.0 schema; use individual. # @return [CheckoutSdk::Common::Address] # @!attribute identification + # The representative's identification. Accounts API v2.0 US Company variants only. + # [Required] for US Company Full (2.0); [Optional] for US Company Lite (2.0). + # @deprecated Not used by the Accounts API v3.0 schema. # @return [Identification] # @!attribute phone + # The representative's phone number. Accounts API v2.0 only. + # [Optional] + # @deprecated Not used by the Accounts API v3.0 schema; use individual. # @return [Phone] # @!attribute date_of_birth + # The date of birth of the person according to the Gregorian calendar. Accounts API v2.0 only. + # [Required] for the v2.0 Full variants; [Optional] for the v2.0 Lite variants. + # @deprecated Not used by the Accounts API v3.0 schema; use individual. # @return [DateOfBirth] # @!attribute place_of_birth + # The place of birth of the person. Accounts API v2.0 only. + # [Required] for EEA Company Full (2.0); [Optional] for EEA Company Lite (2.0). Not part of the other + # v2.0 variants. + # @deprecated Not used by the Accounts API v3.0 schema; use individual. # @return [PlaceOfBirth] - # @!attribute roles - # @return [Array(String)] - # @!attribute documents - # @return [OnboardSubEntityDocuments] class Representative attr_accessor :id, :individual, @@ -35,8 +96,10 @@ class Representative :ownership_percentage, :roles, :documents, + :company, # v2.0 only — deprecated; use `individual` for v3.0 :first_name, + :middle_name, :last_name, :address, :identification, diff --git a/lib/checkout_sdk/accounts/representative_documents.rb b/lib/checkout_sdk/accounts/representative_documents.rb new file mode 100644 index 0000000..a9a67a9 --- /dev/null +++ b/lib/checkout_sdk/accounts/representative_documents.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Accounts + # Verification documents for an individual representative, sent as company.representatives[].documents. + # + # On the EEA, GB and US Company Full (3.0) and the EEA, GB and US Sole Trader Full (3.0) variants the + # API validates this object strictly: a key it does not recognise is rejected, not ignored. The US ISV + # Seller variants (3.0) and the v2.0 variants do not declare it strict. These four are the only keys + # any variant defines, and which apply depends on the variant: EEA Sole Trader Full (3.0) requires + # identity_verification, proof_of_residential_address and proof_of_registration; GB and US Sole Trader + # Full (3.0) require identity_verification; the EEA, GB and US Company Full (3.0) variants and US ISV + # Seller Company (3.0) accept identity_verification and certified_authorised_signatory, both optional; + # US ISV Seller Sole Trader (3.0) accepts identity_verification, optional. The v2.0 company + # representatives use identity_verification only. + # + # Leave an attribute unset rather than assigning nil: an attribute set to nil is sent as null. + # Company-level documents such as bank_verification belong on {OnboardSubEntityDocuments}. + # @!attribute identity_verification + # The document to use to confirm the individual's identity. + # [Optional] (required for the sole trader full variants) + # @return [Document] + # @!attribute certified_authorised_signatory + # Certified authorised signatory document. Required when the legal representative or other role + # owner is not registered on the certificate of incorporation. + # [Optional] (EEA, GB and US Company Full (3.0) and US ISV Seller Company (3.0) only) + # @return [CertifiedAuthorisedSignatory] + # @!attribute proof_of_residential_address + # Proof of residential address of the representative. + # [Optional] (required for EEA Sole Trader Full (3.0), and only valid there) + # @return [ProofOfResidentialAddress] + # @!attribute proof_of_registration + # Proof of the sole trader's registration, for example an extract from a trade register. + # [Optional] (required for EEA Sole Trader Full (3.0), and only valid there) + # @return [ProofOfRegistration] + class RepresentativeDocuments + attr_accessor :identity_verification, + :certified_authorised_signatory, + :proof_of_residential_address, + :proof_of_registration + end + end +end diff --git a/lib/checkout_sdk/accounts/representative_individual.rb b/lib/checkout_sdk/accounts/representative_individual.rb index 8afa497..033fc5f 100644 --- a/lib/checkout_sdk/accounts/representative_individual.rb +++ b/lib/checkout_sdk/accounts/representative_individual.rb @@ -2,29 +2,62 @@ module CheckoutSdk module Accounts - # The personal details of a company representative ("person of interest"), as required by the - # Accounts API v3.0 schema. + # The personal details of a company representative (company.representatives[].individual), Accounts + # API v3.0. # @!attribute first_name + # The representative's first name. + # [Required] + # min 2 characters, max 50 characters # @return [String] # @!attribute middle_name + # The representative's middle name. Required if it appears in official documents. + # [Optional] + # min 2 characters, max 50 characters # @return [String] # @!attribute last_name + # The representative's last name. + # [Required] + # min 2 characters, max 50 characters # @return [String] # @!attribute date_of_birth + # The date of birth of the person according to the Gregorian calendar. + # [Required] # @return [DateOfBirth] # @!attribute place_of_birth + # The place of birth of the person. + # [Required] # @return [PlaceOfBirth] # @!attribute citizenships + # The list of citizenships or legal statuses for the representative. + # [Required] for the US ISV Seller variants only; not part of the other v3.0 schemas, leave unset for + # them. # @return [Array(Citizenship)] # @!attribute national_id_type + # The classification of the national identification number provided. + # [Required] for the US ISV Seller variants only; not part of the other v3.0 schemas, leave unset for + # them. # @return [String] {NationalIdType} # @!attribute national_id_number + # The representative's national identification number. + # [Required] for the US ISV Seller variants; [Optional] for the other v3.0 variants. + # The format depends on the variant: + # US ISV Seller: the number for the national_id_type given. ^[a-zA-Z0-9\-]+$, min 5 characters, max + # 16 characters. + # Other v3.0 variants: a Social Security Number (SSN) or Individual Taxpayer Identification Number + # (ITIN), US residents only. ^\d{9}$, 9 characters. # @return [String] # @!attribute email_address + # The representative's personal email address. + # [Required] for the US ISV Seller variants; [Optional] for the other v3.0 variants. + # Format: email # @return [String] # @!attribute phone + # The representative's phone number. + # [Required] for the US ISV Seller variants; [Optional] for the other v3.0 variants. # @return [Phone] # @!attribute address + # The representative's address. + # [Required] # @return [CheckoutSdk::Common::Address] class RepresentativeIndividual attr_accessor :first_name, diff --git a/lib/checkout_sdk/accounts/shareholder_structure.rb b/lib/checkout_sdk/accounts/shareholder_structure.rb index c5f721f..fb0fca1 100644 --- a/lib/checkout_sdk/accounts/shareholder_structure.rb +++ b/lib/checkout_sdk/accounts/shareholder_structure.rb @@ -2,9 +2,17 @@ module CheckoutSdk module Accounts + # Shareholder structure chart (including % of shares) certified by a competent authority individual and + # dated within the last 3 months. # @!attribute type - # @return [ShareholderStructureType] + # The type of document. + # [Required] + # @return [String] {ShareholderStructureType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class ShareholderStructure attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/shareholder_structure_type.rb b/lib/checkout_sdk/accounts/shareholder_structure_type.rb index f5a1078..711a650 100644 --- a/lib/checkout_sdk/accounts/shareholder_structure_type.rb +++ b/lib/checkout_sdk/accounts/shareholder_structure_type.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The document type accepted as a certified shareholder structure. module ShareholderStructureType CERTIFIED_SHAREHOLDER_STRUCTURE = 'certified_shareholder_structure' end diff --git a/lib/checkout_sdk/accounts/submitter.rb b/lib/checkout_sdk/accounts/submitter.rb index a932746..8431f41 100644 --- a/lib/checkout_sdk/accounts/submitter.rb +++ b/lib/checkout_sdk/accounts/submitter.rb @@ -2,10 +2,11 @@ module CheckoutSdk module Accounts - # Captures evidence of the end-user's consent to onboarding. - # + # Not defined by any Accounts API onboarding schema. + # @deprecated Not part of any Accounts API onboarding schema; the API does not read it. # @!attribute ip_address - # @return [String] IP address of the end-user submitting the onboarding request. + # @deprecated Not defined by any Accounts API onboarding schema. + # @return [String] class Submitter attr_accessor :ip_address end diff --git a/lib/checkout_sdk/accounts/tax_verification.rb b/lib/checkout_sdk/accounts/tax_verification.rb index 8b4b9c8..acb19e0 100644 --- a/lib/checkout_sdk/accounts/tax_verification.rb +++ b/lib/checkout_sdk/accounts/tax_verification.rb @@ -2,9 +2,17 @@ module CheckoutSdk module Accounts + # IRS-issued Employer Identification Number document used to verify the entity's tax identification + # (US variants). # @!attribute type + # The type of IRS-issued document used for tax verification. + # [Required] # @return [String] {TaxVerificationType} # @!attribute front + # The ID of the front side of the document as represented within Checkout.com systems. + # [Required] + # ^file_[a-z2-7]{26}$ + # 31 characters # @return [String] class TaxVerification attr_accessor :type, diff --git a/lib/checkout_sdk/accounts/tax_verification_type.rb b/lib/checkout_sdk/accounts/tax_verification_type.rb index 3c4a1dd..49850b0 100644 --- a/lib/checkout_sdk/accounts/tax_verification_type.rb +++ b/lib/checkout_sdk/accounts/tax_verification_type.rb @@ -2,6 +2,7 @@ module CheckoutSdk module Accounts + # The document type accepted as tax verification: an IRS-issued Employer Identification Number letter. module TaxVerificationType EIN_LETTER = 'ein_letter' end diff --git a/lib/checkout_sdk/accounts/update_payment_instrument_request.rb b/lib/checkout_sdk/accounts/update_payment_instrument_request.rb index 3c8b3ae..95c2ea1 100644 --- a/lib/checkout_sdk/accounts/update_payment_instrument_request.rb +++ b/lib/checkout_sdk/accounts/update_payment_instrument_request.rb @@ -2,12 +2,24 @@ module CheckoutSdk module Accounts + # Request for PATCH /accounts/entities/{entityId}/payment-instruments/{id} + # (PlatformsPaymentInstrumentUpdate). # @!attribute label + # A reference that you can use to identify the payment instrument. + # [Optional] + # min 1 character, max 50 characters # @return [String] # @!attribute default + # Deprecated by the API: for scheduled payouts the first payment instrument created for a currency + # is used; to change it, update the payout schedule. + # [Optional] # @return [TrueClass, FalseClass] # @!attribute headers - # @return [Headers] + # The payment instrument ETag, as returned in the ETag header of the GET, in headers.if_match. + # {AccountsClient#update_payment_instrument} sends it as the If-Match HTTP header; the API + # answers 428 without it and 412 when it does not match. + # [Required] by the API. + # @return [CheckoutSdk::Common::Headers] class UpdatePaymentInstrumentRequest attr_accessor :label, :default, diff --git a/spec/checkout_sdk/accounts/accounts_files_spec.rb b/spec/checkout_sdk/accounts/accounts_files_spec.rb new file mode 100644 index 0000000..e7904fe --- /dev/null +++ b/spec/checkout_sdk/accounts/accounts_files_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +RSpec.describe CheckoutSdk::Accounts do + let(:credentials_mock) { double('credentials') } + let(:api_client_mock) { double('api_client') } + let(:files_client_mock) { double('files_client') } + let(:configuration_mock) { double('configuration') } + let(:entity_id) { 'ent_ovpg62ssyywodc4veodhelfrpv' } + let(:file_id) { 'file_6lbss42ezvoufcb2beo76rvwly' } + let(:client) do + CheckoutSdk::Accounts::AccountsClient.new(api_client_mock, files_client_mock, configuration_mock) + end + + before do + allow(configuration_mock).to receive(:credentials).and_return(credentials_mock) + allow(credentials_mock).to receive(:get_authorization).and_return('secret_key') + end + + describe 'file uploads, routed through the files client' do + it 'upload_file submits the file to files' do + request = CheckoutSdk::Accounts::FileRequest.new + request.file = './spec/resources/checkout.jpeg' + request.purpose = CheckoutSdk::Accounts::FilePurpose::PROOF_OF_REGISTRATION + expect(files_client_mock).to receive(:submit_file).with('files', 'secret_key', request).and_return('r') + + expect(client.upload_file(request)).to eq('r') + end + + it 'upload_entity_file posts the purpose as JSON to entities/{entity_id}/files' do + request = CheckoutSdk::Accounts::EntityFilesRequest.new + request.purpose = CheckoutSdk::Accounts::FilePurpose::PROOF_OF_RESIDENTIAL_ADDRESS + expect(files_client_mock).to receive(:invoke_post) + .with("entities/#{entity_id}/files", 'secret_key', request).and_return('r') + + expect(client.upload_entity_file(entity_id, request)).to eq('r') + end + + it 'upload_entity_file accepts a Hash request' do + request = { purpose: CheckoutSdk::Accounts::FilePurpose::IDENTITY_VERIFICATION } + expect(files_client_mock).to receive(:invoke_post) + .with("entities/#{entity_id}/files", 'secret_key', request).and_return('r') + + expect(client.upload_entity_file(entity_id, request)).to eq('r') + end + + it 'get_entity_file reads entities/{entity_id}/files/{file_id}' do + expect(files_client_mock).to receive(:invoke_get) + .with("entities/#{entity_id}/files/#{file_id}", 'secret_key').and_return('r') + + expect(client.get_entity_file(entity_id, file_id)).to eq('r') + end + end + + # The SDK has no typed file models: requests go through JsonSerializer and responses come back as + # OpenStruct. The payloads below are the per-field swagger examples of PlatformsFileUploadResponse and + # PlatformsFileRetrieveResponse, driven through the real parse path. + describe 'file request and response shapes' do + let(:configuration) do + double( + 'CheckoutConfiguration', + http_client: Faraday.new, + multipart_http_client: Faraday.new, + logger: Logger.new(File::NULL) + ) + end + let(:api_client) { CheckoutSdk::ApiClient.new(configuration, 'https://files.sandbox.checkout.com') } + + def parse(body) + response = double('Response', status: 200, body: body, headers: { 'Content-Type' => 'application/json' }) + allow(CheckoutSdk::CheckoutUtils).to receive(:map_to_http_metadata).with(response).and_return( + OpenStruct.new(status_code: 200, body: body) + ) + api_client.send(:parse_response, response) + end + + it 'serializes EntityFilesRequest to the purpose only' do + request = CheckoutSdk::Accounts::EntityFilesRequest.new + request.purpose = CheckoutSdk::Accounts::FilePurpose::IDENTITY_VERIFICATION + + expect(CheckoutSdk::JsonSerializer.to_custom_hash(request)).to eq('purpose' => 'identity_verification') + end + + it 'parses every field of the PlatformsFileUploadResponse example' do + upload_href = 'https://s3.eu-west-1.amazonaws.com/mp-files-api-staging-prod/ent_ociwguf5a5fe3ndmpnvpnwsi3e/' \ + "#{file_id}?AWSAccessKeyId=ASIX4BFJOBCQFLAMPKU3&Expires=1661355993&x-amz-security-token=some_token" + body = { + 'id' => file_id, + 'maximum_size_in_bytes' => 4_194_304, + 'document_types_for_purpose' => %w[image/jpeg image/png image/jpg], + '_links' => { + 'upload' => { 'href' => upload_href }, + 'self' => { 'href' => "https://files.checkout.com/files/#{file_id}" } + } + }.to_json + + result = parse(body) + + expect(result.id).to eq(file_id) + expect(result.maximum_size_in_bytes).to eq(4_194_304) + expect(result.document_types_for_purpose).to eq(%w[image/jpeg image/png image/jpg]) + expect(result._links.upload.href).to eq(upload_href) + expect(result._links.self.href).to eq("https://files.checkout.com/files/#{file_id}") + end + + it 'parses every field of the PlatformsFileRetrieveResponse example' do + download_href = 'https://s3.eu-west-1.amazonaws.com/mp-files-api-clean-prod/ent_ociwguf5a5fe3ndmpnvpnwsi3e/' \ + "#{file_id}?X-Amz-Expires=3600&x-amz-security-token=some_token" + body = { + 'id' => file_id, + 'status' => 'invalid', + 'status_reasons' => ['InvalidMimeType'], + 'size' => 1024, + 'mime_type' => 'application/pdf', + 'uploaded_on' => '2020-12-01T15:01:01.0000000+00:00', + 'purpose' => 'identity_verification', + '_links' => { + 'download' => { 'href' => download_href }, + 'self' => { 'href' => "https://files.checkout.com/files/#{file_id}" } + } + }.to_json + + result = parse(body) + + expect(result.id).to eq(file_id) + expect(result.status).to eq('invalid') + expect(result.status_reasons).to eq(['InvalidMimeType']) + expect(result.size).to eq(1024) + expect(result.mime_type).to eq('application/pdf') + # Seven fractional digits: the SDK keeps the value as the raw string, it never parses it as a date. + expect(result.uploaded_on).to eq('2020-12-01T15:01:01.0000000+00:00') + expect(result.purpose).to eq('identity_verification') + expect(result._links.download.href).to eq(download_href) + expect(result._links.self.href).to eq("https://files.checkout.com/files/#{file_id}") + end + end +end diff --git a/spec/checkout_sdk/accounts/accounts_integration_spec.rb b/spec/checkout_sdk/accounts/accounts_integration_spec.rb index faef6eb..80dcbb0 100644 --- a/spec/checkout_sdk/accounts/accounts_integration_spec.rb +++ b/spec/checkout_sdk/accounts/accounts_integration_spec.rb @@ -1,3 +1,5 @@ +require 'net/http' + RSpec.describe CheckoutSdk::Accounts do before(:all) do @@ -7,8 +9,13 @@ end describe 'when sub entity operations' do + # A schema 3.0 GB Sole Trader Full entity (see build_sole_trader_v3). It replaces the schema 2.0 sole + # trader (a top-level individual), which the sandbox answers with HTTP 500 even for a body that + # validates against the spec. before(:all) do - @entity = create_entity @accounts_sdk + @identity_file = upload_file_accounts(@accounts_sdk, CheckoutSdk::Accounts::FilePurpose::IDENTITY_VERIFICATION) + @bank_file = upload_file_accounts @accounts_sdk + @entity = create_entity @accounts_sdk, @identity_file, @bank_file end describe '.create_entity' do context 'when creating a entity with valid data' do @@ -23,8 +30,9 @@ context 'when sub-entity onboarding request conflicted with an existing sub-entity' do it 'raises an error' do random_uuid = SecureRandom.uuid - accounts_checkout_api.accounts.create_entity(build_entity(random_uuid), '2.0') - expect { accounts_checkout_api.accounts.create_entity(build_entity(random_uuid), '2.0') } + request = build_sole_trader_v3(random_uuid, @identity_file, @bank_file) + accounts_checkout_api.accounts.create_entity(request, '3.0') + expect { accounts_checkout_api.accounts.create_entity(request, '3.0') } .to raise_error(CheckoutSdk::CheckoutApiException) { |e| expect(e.http_metadata.status_code).to eq 409 # The conflict body's `id` is not asserted: under an explicit schema_version Accept header @@ -36,7 +44,7 @@ describe '.get_entity' do context 'when fetching a valid entity' do it 'returns entity data' do - response = @accounts_sdk.accounts.get_entity(@entity.id, '2.0') + response = @accounts_sdk.accounts.get_entity(@entity.id, '3.0') expect(response).not_to be nil expect(response.id).to eq(@entity.id) @@ -48,18 +56,19 @@ describe '.update' do context 'when updating a valid entity' do it 'should update successfully' do - request = build_entity + # The reference is set at creation and not sent again on update. + request = build_sole_trader_v3(nil, @identity_file, @bank_file) request.contact_details.phone.number = '1818151551' request.contact_details.email_addresses.primary = generate_random_email request.profile.urls = ['https://www.anothersuperheroexample.com'] - response = @accounts_sdk.accounts.update_entity(@entity.id, request, '2.0') + response = @accounts_sdk.accounts.update_entity(@entity.id, request, '3.0') expect(response).not_to be nil expect(response.id).to eq(@entity.id) expect(response.http_metadata.status_code).to eq 200 - verify_update = @accounts_sdk.accounts.get_entity(@entity.id, '2.0') + verify_update = @accounts_sdk.accounts.get_entity(@entity.id, '3.0') expect(verify_update).not_to be nil expect(verify_update.contact_details.phone.number).to eq(request.contact_details.phone.number) expect(verify_update.contact_details.email_addresses.primary).to eq(request.contact_details.email_addresses.primary) @@ -86,16 +95,94 @@ expect(fetched).not_to be nil expect(fetched.id).to eq(created.id) end + + # The representative's documents on schema 3.0. The sandbox platform resolves to a company variant + # (GB/US scope, USD only), where identity_verification and certified_authorised_signatory are the + # representative documents the API accepts; the EEA Sole Trader keys are covered by + # accounts_v3_serialization_spec, since this platform rejects them. + it 'creates a v3.0 sub-entity with representative documents and reads them back' do + identity_file = upload_file_accounts(@accounts_sdk, CheckoutSdk::Accounts::FilePurpose::IDENTITY_VERIFICATION) + signatory_file = upload_file_accounts(@accounts_sdk, + CheckoutSdk::Accounts::FilePurpose::CERTIFIED_AUTHORISED_SIGNATORY) + + identity = CheckoutSdk::Accounts::Document.new + identity.type = CheckoutSdk::Accounts::DocumentType::PASSPORT + identity.front = identity_file.id + signatory = CheckoutSdk::Accounts::CertifiedAuthorisedSignatory.new + signatory.type = CheckoutSdk::Accounts::CertifiedAuthorisedSignatoryType::POWER_OF_ATTORNEY + signatory.front = signatory_file.id + documents = CheckoutSdk::Accounts::RepresentativeDocuments.new + documents.identity_verification = identity + documents.certified_authorised_signatory = signatory + request = build_entity_v3(SecureRandom.uuid) + request.company.representatives[0].documents = documents + + created = @accounts_sdk.accounts.create_entity(request) + expect(created.id).not_to be nil + + # The documents are linked on the representative, not dropped: the API echoes them back. + linked = @accounts_sdk.accounts.get_entity(created.id).company.representatives[0].documents + expect(linked.identity_verification.type).to eq('passport') + expect(linked.identity_verification.front).to eq(identity_file.id) + expect(linked.certified_authorised_signatory.type).to eq('power_of_attorney') + expect(linked.certified_authorised_signatory.front).to eq(signatory_file.id) + end + + # POST /entities/{entityId}/files takes only the purpose as JSON and answers with an upload link; + # the file bytes go to that link in a separate PUT. A v3.0 entity, since the sandbox rejects v2.0 here. + it 'creates a sub-entity file upload, sends the bytes to the upload link and retrieves the file' do + entity = @accounts_sdk.accounts.create_entity(build_entity_v3(SecureRandom.uuid)) + request = CheckoutSdk::Accounts::EntityFilesRequest.new + request.purpose = CheckoutSdk::Accounts::FilePurpose::IDENTITY_VERIFICATION + + upload = @accounts_sdk.accounts.upload_entity_file(entity.id, request) + expect(upload.id).to match(/^file_[a-z2-7]{26}$/) + expect(upload._links.upload.href).not_to be_nil + + upload_uri = URI(upload._links.upload.href) + put_request = Net::HTTP::Put.new(upload_uri) + put_request['Content-Type'] = 'image/jpeg' + put_request.body = File.binread('./spec/resources/checkout.jpeg') + put_response = Net::HTTP.start(upload_uri.host, upload_uri.port, use_ssl: upload_uri.scheme == 'https') do |http| + http.request(put_request) + end + expect(put_response.code.to_i).to be_between(200, 299) + + retrieved = @accounts_sdk.accounts.get_entity_file(entity.id, upload.id) + expect(retrieved.id).to eq(upload.id) + expect(retrieved.purpose).to eq(CheckoutSdk::Accounts::FilePurpose::IDENTITY_VERIFICATION) + end + + # The update only succeeds when the ETag reaches the API as the If-Match HTTP header: without it the + # API answers 428, and with a stale ETag 412. A v3.0 entity, since the sandbox rejects v2.0 here. + it 'updates a payment instrument with its ETag' do + entity = @accounts_sdk.accounts.create_entity(build_entity_v3(SecureRandom.uuid)) + file = upload_file_accounts @accounts_sdk + + instrument_id = @accounts_sdk.accounts.add_payment_instrument(entity.id, build_payment_instrument(file)).id + + details = @accounts_sdk.accounts.retrieve_payment_instrument_details(entity.id, instrument_id) + request = CheckoutSdk::Accounts::UpdatePaymentInstrumentRequest.new + request.label = 'Renamed account' + request.headers = CheckoutSdk::Common::Headers.new + request.headers.if_match = details.http_metadata.headers['etag'] + + response = @accounts_sdk.accounts.update_payment_instrument(entity.id, instrument_id, request) + expect(response.id).to eq(instrument_id) + updated = @accounts_sdk.accounts.retrieve_payment_instrument_details(entity.id, instrument_id) + expect(updated.label).to eq('Renamed account') + end end describe 'when entity payment instrument operations' do + # A schema 3.0 company entity: payment instruments need company data (a sole trader is answered + # with entity_business_registration_number_required and similar). before(:all) do - @entity = create_entity @accounts_sdk + @entity = @accounts_sdk.accounts.create_entity(build_entity_v3(SecureRandom.uuid)) @file = upload_file_accounts @accounts_sdk end - describe '.add_payment_instrument', - skip: 'sandbox rejects add_payment_instrument for this entity with 422 entity_business_registration_number_required, entity_legal_name_required, entity_registered_address_required - the v2.0 individual entity built here has no company data. Unrelated to the instruments work; needs an accounts-owned fix to build_entity or a company entity for this block.' do + describe '.add_payment_instrument' do context 'when adding payment instrument to existing entity' do it 'creates instrument for entity successfully' do request = build_payment_instrument @file @@ -107,8 +194,7 @@ end end - describe '.retrieve_payment_instrument_details', - skip: 'sandbox rejects add_payment_instrument for this entity with 422 entity_business_registration_number_required, entity_legal_name_required, entity_registered_address_required - the v2.0 individual entity built here has no company data. Unrelated to the instruments work; needs an accounts-owned fix to build_entity or a company entity for this block.' do + describe '.retrieve_payment_instrument_details' do context 'when fetching existing payment instrument for valid entity' do subject(:payment_instrument) { @accounts_sdk.accounts.add_payment_instrument @entity.id, build_payment_instrument(@file) @@ -137,31 +223,28 @@ end end - describe '.update_payment_instrument', skip: 'returns 428 status when updating' do - subject(:payment_instrument) { - @accounts_sdk.accounts.add_payment_instrument @entity.id, build_payment_instrument(@file) - } + describe '.update_payment_instrument' do context 'when updating existing payment instrument for valid entity' do - it 'returns http 200' do + # The API reads the ETag only from the If-Match header: without it the update gets 428. + it 'updates the instrument and reflects the new values' do + instrument_id = @accounts_sdk.accounts.add_payment_instrument(@entity.id, build_payment_instrument(@file)).id + details = @accounts_sdk.accounts.retrieve_payment_instrument_details @entity.id, instrument_id + request = CheckoutSdk::Accounts::UpdatePaymentInstrumentRequest.new request.label = 'new label' request.default = true + request.headers = CheckoutSdk::Common::Headers.new + request.headers.if_match = details.http_metadata.headers['etag'] - response = @accounts_sdk.accounts.update_payment_instrument @entity.id, - payment_instrument.id, - request + response = @accounts_sdk.accounts.update_payment_instrument @entity.id, instrument_id, request assert_response response, %w[id] - end - it 'reflects new values for updated fields' do - response = @accounts_sdk.accounts.retrieve_payment_instrument_details @entity.id, - payment_instrument.id - - assert_response response, %w[id - label - default] - expect(response.label).to eq 'new label' - expect(response.default).to be true + updated = @accounts_sdk.accounts.retrieve_payment_instrument_details @entity.id, instrument_id + assert_response updated, %w[id + label + default] + expect(updated.label).to eq 'new label' + expect(updated.default).to be true end end end @@ -205,15 +288,18 @@ private -def create_entity(sdk) - request = build_entity(SecureRandom.uuid) - # v2.0 payload (top-level individual) — pin to 2.0 (SDK now defaults to 3.0) - sdk.accounts.create_entity(request, '2.0') +def create_entity(sdk, identity_file, bank_file) + sdk.accounts.create_entity(build_sole_trader_v3(SecureRandom.uuid, identity_file, bank_file), '3.0') end -def build_entity(reference = nil) +# A schema 3.0 GB Sole Trader Full request (GBSoleTraderFull3-0): a company of business type +# individual_or_sole_proprietorship with exactly one ubo representative, the representative's identity +# document and the top-level bank statement. The processing currency is USD, the only currency in the +# sandbox platform's currency scope (see build_entity_v3); the addresses and settlement country stay GB. +def build_sole_trader_v3(reference, identity_file, bank_file) phone = CheckoutSdk::Accounts::Phone.new - phone.number = '2345678910' + phone.country_code = 'GB' + phone.number = '2072343000' email_addresses = CheckoutSdk::Accounts::EntityEmailAddresses.new email_addresses.primary = generate_random_email @@ -225,29 +311,68 @@ def build_entity(reference = nil) profile = CheckoutSdk::Accounts::Profile.new profile.urls = ['https://www.superheroexample.com'] profile.mccs = ['0742'] + profile.default_holding_currency = CheckoutSdk::Common::Currency::USD + profile.holding_currencies = [CheckoutSdk::Common::Currency::USD] - birth = CheckoutSdk::Accounts::DateOfBirth.new - birth.day = 5 - birth.month = 5 - birth.year = 1996 + dob = CheckoutSdk::Accounts::DateOfBirth.new + dob.day = 5 + dob.month = 6 + dob.year = 1995 - identification = CheckoutSdk::Accounts::Identification.new - identification.national_id_number = 'AB123456C' + pob = CheckoutSdk::Accounts::PlaceOfBirth.new + pob.country = CheckoutSdk::Common::Country::GB - individual = CheckoutSdk::Accounts::Individual.new + individual = CheckoutSdk::Accounts::RepresentativeIndividual.new individual.first_name = Helpers::DataFactory::FIRST_NAME individual.last_name = Helpers::DataFactory::LAST_NAME - individual.trading_name = "Batman's Super Hero Masks" - individual.registered_address = address - individual.national_tax_id = 'TAX123456' - individual.date_of_birth = birth - individual.identification = identification + individual.email_address = generate_random_email + individual.date_of_birth = dob + individual.place_of_birth = pob + individual.address = address + + identity = CheckoutSdk::Accounts::Document.new + identity.type = CheckoutSdk::Accounts::DocumentType::PASSPORT + identity.front = identity_file.id + representative_documents = CheckoutSdk::Accounts::RepresentativeDocuments.new + representative_documents.identity_verification = identity + + representative = CheckoutSdk::Accounts::Representative.new + representative.individual = individual + representative.roles = [CheckoutSdk::Accounts::EntityRoles::UBO] + representative.documents = representative_documents + + doi = CheckoutSdk::Accounts::DateOfIncorporation.new + doi.month = 6 + doi.year = 2015 + + company = CheckoutSdk::Accounts::Company.new + company.trading_name = "Batman's Super Hero Masks" + company.business_type = CheckoutSdk::Accounts::BusinessType::INDIVIDUAL_OR_SOLE_PROPRIETORSHIP + company.date_of_incorporation = doi + company.principal_address = address + company.representatives = [representative] + + processing_details = CheckoutSdk::Accounts::ProcessingDetails.new + processing_details.settlement_country = 'GB' + processing_details.target_countries = ['GB'] + processing_details.annual_processing_volume = 1_000_000 + processing_details.average_transaction_value = 5_000 + processing_details.highest_transaction_value = 25_000 + processing_details.currency = CheckoutSdk::Common::Currency::USD + + bank_statement = CheckoutSdk::Accounts::BankVerification.new + bank_statement.type = CheckoutSdk::Accounts::BankVerificationType::BANK_STATEMENT + bank_statement.front = bank_file.id + documents = CheckoutSdk::Accounts::OnboardSubEntityDocuments.new + documents.bank_verification = bank_statement request = CheckoutSdk::Accounts::OnboardEntity.new - request.reference = reference || SecureRandom.uuid + request.reference = reference request.contact_details = contact_details request.profile = profile - request.individual = individual + request.company = company + request.processing_details = processing_details + request.documents = documents request end @@ -347,25 +472,28 @@ def build_payment_instrument(file) document.type = 'bank_statement' document.file_id = file.id - instrument_details = CheckoutSdk::Accounts::InstrumentDetailsFasterPayments.new - instrument_details.account_number = '12334454' - instrument_details.bank_code = '050389' + # A USD ACH account: USD is the only currency in the sandbox platform's scope. The sandbox rejects + # account_type checking (instrument_details_account_type_invalid), although the spec lists it. + instrument_details = CheckoutSdk::Accounts::InstrumentDetailsAch.new + instrument_details.account_number = '123456789' + instrument_details.routing_number = '026009593' + instrument_details.account_type = 'savings' request = CheckoutSdk::Accounts::PaymentInstrumentRequest.new - request.label = 'Barclays' + request.label = 'Main account' request.type = CheckoutSdk::Common::InstrumentType::BANK_ACCOUNT - request.currency = CheckoutSdk::Common::Currency::GBP - request.country = CheckoutSdk::Common::Country::GB + request.currency = CheckoutSdk::Common::Currency::USD + request.country = CheckoutSdk::Common::Country::US request.default = false request.document = document request.instrument_details = instrument_details request end -def upload_file_accounts(sdk) +def upload_file_accounts(sdk, purpose = CheckoutSdk::Accounts::FilePurpose::BANK_VERIFICATION) request = CheckoutSdk::Accounts::FileRequest.new request.file = './spec/resources/checkout.jpeg' - request.purpose = 'bank_verification' + request.purpose = purpose sdk.accounts.upload_file(request) end diff --git a/spec/checkout_sdk/accounts/accounts_schema_version_spec.rb b/spec/checkout_sdk/accounts/accounts_schema_version_spec.rb index 079c9a8..d7fcc87 100644 --- a/spec/checkout_sdk/accounts/accounts_schema_version_spec.rb +++ b/spec/checkout_sdk/accounts/accounts_schema_version_spec.rb @@ -5,6 +5,7 @@ let(:api_client_mock) { double('api_client') } let(:files_client_mock) { double('files_client') } let(:configuration_mock) { double('configuration') } + let(:entity_id) { 'ent_ovpg62ssyywodc4veodhelfrpv' } let(:client) do CheckoutSdk::Accounts::AccountsClient.new(api_client_mock, files_client_mock, configuration_mock) end @@ -31,33 +32,33 @@ it 'get_entity sends the default schema_version 3.0' do expect(api_client_mock).to receive(:invoke_get) do |path, auth, params, headers| - expect(path).to eq('accounts/entities/ent_1') + expect(path).to eq("accounts/entities/#{entity_id}") expect(params).to be_nil expect(headers.accept).to eq('application/json;schema_version=3.0') 'r' end - expect(client.get_entity('ent_1')).to eq('r') + expect(client.get_entity(entity_id)).to eq('r') end it 'update_entity sends the default schema_version 3.0' do req = CheckoutSdk::Accounts::OnboardEntity.new expect(api_client_mock).to receive(:invoke_put) do |path, auth, body, headers| - expect(path).to eq('accounts/entities/ent_1') + expect(path).to eq("accounts/entities/#{entity_id}") expect(body).to eq(req) expect(headers.accept).to eq('application/json;schema_version=3.0') 'r' end - expect(client.update_entity('ent_1', req)).to eq('r') + expect(client.update_entity(entity_id, req)).to eq('r') end it 'get_entity_requirements sends the default schema_version 3.0' do expect(api_client_mock).to receive(:invoke_get) do |path, _auth, params, headers| - expect(path).to eq('accounts/entities/ent_1/requirements') + expect(path).to eq("accounts/entities/#{entity_id}/requirements") expect(params).to be_nil expect(headers.accept).to eq('application/json;schema_version=3.0') 'r' end - expect(client.get_entity_requirements('ent_1')).to eq('r') + expect(client.get_entity_requirements(entity_id)).to eq('r') end it 'honors a schema_version override' do @@ -65,7 +66,7 @@ expect(headers.accept).to eq('application/json;schema_version=2.0') 'r' end - expect(client.get_entity('ent_1', '2.0')).to eq('r') + expect(client.get_entity(entity_id, '2.0')).to eq('r') end end end diff --git a/spec/checkout_sdk/accounts/accounts_v3_serialization_spec.rb b/spec/checkout_sdk/accounts/accounts_v3_serialization_spec.rb index e6360b0..0e77f5c 100644 --- a/spec/checkout_sdk/accounts/accounts_v3_serialization_spec.rb +++ b/spec/checkout_sdk/accounts/accounts_v3_serialization_spec.rb @@ -5,30 +5,44 @@ def serialize(object) CheckoutSdk::JsonSerializer.to_custom_hash(object) end - it 'serializes ProcessingDetails with payments/ach' do - ach = CheckoutSdk::Accounts::ProcessingDetailsAch.new - ach.annual_ach_volume = 1_000_000 - ach.average_ach_transaction_size = 5_000 - ach.estimated_monthly_credit_volume = 100_000 - ach.average_credit_amount = 5_000 - payments = CheckoutSdk::Accounts::ProcessingDetailsPayments.new - payments.ach = ach + # US ISV Seller (3.0) processing details: payments and average_order_fulfillment_time, and no + # settlement_country or highest_transaction_value. + it 'serializes the US ISV Seller ProcessingDetails with payments/ach' do + expect(serialize(isv_processing_details)).to eq( + 'annual_processing_volume' => 1000, + 'average_transaction_value' => 2000, + 'average_order_fulfillment_time' => 3, + 'target_countries' => ['US'], + 'currency' => 'USD', + 'payments' => { + 'ach' => { + 'annual_ach_volume' => 100_000, + 'average_ach_transaction_size' => 5000, + 'estimated_monthly_credit_volume' => 50_000, + 'average_credit_amount' => 2500 + } + } + ) + end + + # EEA, GB and US Company Full and Sole Trader Full (3.0) processing details. + it 'serializes the Full (3.0) ProcessingDetails with settlement_country and highest_transaction_value' do details = CheckoutSdk::Accounts::ProcessingDetails.new + details.settlement_country = CheckoutSdk::Common::Country::GB + details.target_countries = [CheckoutSdk::Common::Country::GB, CheckoutSdk::Common::Country::FR] details.annual_processing_volume = 1_000_000 - details.average_order_fulfillment_time = 3 + details.average_transaction_value = 2_000 details.highest_transaction_value = 25_000 details.currency = CheckoutSdk::Common::Currency::GBP - details.settlement_country = 'GB' - details.target_countries = ['GB'] - details.payments = payments - hash = serialize(details) - - expect(hash['average_order_fulfillment_time']).to eq(3) - expect(hash['payments']['ach']['annual_ach_volume']).to eq(1_000_000) - expect(hash['payments']['ach']['average_ach_transaction_size']).to eq(5_000) - expect(hash['payments']['ach']['estimated_monthly_credit_volume']).to eq(100_000) - expect(hash['payments']['ach']['average_credit_amount']).to eq(5_000) + expect(serialize(details)).to eq( + 'settlement_country' => 'GB', + 'target_countries' => %w[GB FR], + 'annual_processing_volume' => 1_000_000, + 'average_transaction_value' => 2_000, + 'highest_transaction_value' => 25_000, + 'currency' => 'GBP' + ) end it 'serializes AgreedTerms' do @@ -48,24 +62,26 @@ def serialize(object) expect(hash['version']).to eq('1.0') end - it 'serializes Company v3.0 fields' do + # is_registered_company exists only on US ISV Seller Sole Trader (3.0), where the only allowed value is false. + it 'serializes the US ISV Seller Sole Trader Company fields' do doi = CheckoutSdk::Accounts::DateOfIncorporation.new doi.day = 1 doi.month = 6 doi.year = 2010 company = CheckoutSdk::Accounts::Company.new - company.legal_name = 'Super Hero Masks Inc.' - company.business_type = CheckoutSdk::Accounts::BusinessType::LIMITED_COMPANY + company.business_type = CheckoutSdk::Accounts::BusinessType::INDIVIDUAL_OR_SOLE_PROPRIETORSHIP + company.trading_name = 'Super Hero Masks' company.additional_trading_names = ['SHM'] - company.is_registered_company = true + company.is_registered_company = false company.date_of_incorporation = doi - hash = serialize(company) - - expect(hash['additional_trading_names']).to eq(['SHM']) - expect(hash['is_registered_company']).to eq(true) - expect(hash['business_type']).to eq('limited_company') - expect(hash['date_of_incorporation']).to eq('day' => 1, 'month' => 6, 'year' => 2010) + expect(serialize(company)).to eq( + 'business_type' => 'individual_or_sole_proprietorship', + 'trading_name' => 'Super Hero Masks', + 'additional_trading_names' => ['SHM'], + 'is_registered_company' => false, + 'date_of_incorporation' => { 'day' => 1, 'month' => 6, 'year' => 2010 } + ) end it 'serializes a v3.0 Representative with nested individual, citizenships and national_id_type' do @@ -77,7 +93,7 @@ def serialize(object) individual.last_name = 'Doe' individual.citizenships = [citizenship] individual.national_id_type = CheckoutSdk::Accounts::NationalIdType::SSN - individual.national_id_number = 'AB123456C' + individual.national_id_number = '123456789' representative = CheckoutSdk::Accounts::Representative.new representative.individual = individual representative.company_position = CheckoutSdk::Accounts::CompanyPosition::CEO @@ -101,20 +117,776 @@ def serialize(object) it 'serializes the financial_statements document' do fs = CheckoutSdk::Accounts::FinancialStatements.new fs.type = CheckoutSdk::Accounts::FinancialStatementsType::FINANCIAL_STATEMENTS - fs.front = 'file_00000000000000000000000000' + fs.front = 'file_yys6klxrua2y5cwgvudd2frkro' documents = CheckoutSdk::Accounts::OnboardSubEntityDocuments.new documents.financial_statements = fs hash = serialize(documents) expect(hash['financial_statements']).to eq('type' => 'financial_statements', - 'front' => 'file_00000000000000000000000000') + 'front' => 'file_yys6klxrua2y5cwgvudd2frkro') end + # Value sets checked against the union of every onboarding variant in the spec. it 'exposes the complete enum value sets' do - expect(CheckoutSdk::Accounts::BusinessType.constants.size).to eq(19) - expect(CheckoutSdk::Accounts::EntityRoles.constants.size).to eq(5) - expect(CheckoutSdk::Accounts::CompanyPosition.constants.size).to eq(11) - expect(CheckoutSdk::Accounts::NationalIdType.constants.size).to eq(7) + a = CheckoutSdk::Accounts + values = ->(mod) { mod.constants.map { |c| mod.const_get(c) } } + expect(values.call(a::DocumentType)).to contain_exactly( + 'passport', 'national_identity_card', 'driving_license', 'citizen_card', 'residence_permit', 'electoral_id' + ) + expect(values.call(a::CompanyVerificationType)).to contain_exactly('incorporation_document', + 'articles_of_association') + expect(values.call(a::ArticlesOfAssociationType)).to contain_exactly('memorandum_of_association', + 'articles_of_association') + expect(values.call(a::BusinessType)).to contain_exactly( + 'individual_or_sole_proprietorship', 'general_partnership', 'limited_partnership', + 'scottish_limited_partnership', 'public_limited_company', 'limited_company', 'limited_liability_corporation', + 'private_corporation', 'publicly_traded_corporation', 'professional_association', 'unincorporated_association', + 'auto_entrepreneur', 'government_agency', 'non_profit_entity', 'trust', 'club_or_society', + 'regulated_financial_institution', 'cftc_registered_entity', 'sec_registered_entity' + ) + expect(values.call(a::CompanyPosition)).to contain_exactly( + 'ceo', 'cfo', 'coo', 'managing_member', 'general_partner', 'president', 'vice_president', 'treasurer', + 'other_senior_management', 'other_executive_officer', 'other_non_executive_non_senior' + ) + expect(values.call(a::NationalIdType)).to contain_exactly( + 'ssn', 'itin', 'passport', 'driving_license', 'national_id_card', 'residence_permit', 'other' + ) + expect(values.call(a::EntityRoles)).to contain_exactly( + 'ubo', 'legal_representative', 'authorised_signatory', 'director', 'control_person' + ) + end + + # Regression: EEA Sole Trader (3.0) needs proof_of_residential_address and proof_of_registration on + # the representative, with bank_verification alone at the top level. Neither could be expressed before. + it 'serializes the EEA Sole Trader representative documents' do + identity = CheckoutSdk::Accounts::Document.new + identity.type = CheckoutSdk::Accounts::DocumentType::PASSPORT + identity.front = 'file_identityverificationaaaaaa' + residential = CheckoutSdk::Accounts::ProofOfResidentialAddress.new + residential.type = CheckoutSdk::Accounts::ProofOfResidentialAddressType::PROOF_OF_ADDRESS + residential.front = 'file_proofofresidentialaddressa' + registration = CheckoutSdk::Accounts::ProofOfRegistration.new + registration.type = CheckoutSdk::Accounts::ProofOfRegistrationType::EXTRACT_FROM_TRADE_REGISTER + registration.front = 'file_proofofregistrationaaaaaaa' + rep_documents = CheckoutSdk::Accounts::RepresentativeDocuments.new + rep_documents.identity_verification = identity + rep_documents.proof_of_residential_address = residential + rep_documents.proof_of_registration = registration + representative = CheckoutSdk::Accounts::Representative.new + representative.roles = [CheckoutSdk::Accounts::EntityRoles::UBO] + representative.documents = rep_documents + company = CheckoutSdk::Accounts::Company.new + company.business_type = CheckoutSdk::Accounts::BusinessType::INDIVIDUAL_OR_SOLE_PROPRIETORSHIP + company.representatives = [representative] + bank = CheckoutSdk::Accounts::BankVerification.new + bank.type = CheckoutSdk::Accounts::BankVerificationType::BANK_STATEMENT + bank.front = 'file_bankverificationaaaaaaaaaa' + documents = CheckoutSdk::Accounts::OnboardSubEntityDocuments.new + documents.bank_verification = bank + request = CheckoutSdk::Accounts::OnboardEntity.new + request.reference = 'ref_sole_trader' + request.company = company + request.documents = documents + + hash = serialize(request) + + expect(hash['company']['representatives'][0]['documents']).to eq( + 'identity_verification' => { 'type' => 'passport', 'front' => 'file_identityverificationaaaaaa' }, + 'proof_of_residential_address' => { 'type' => 'proof_of_address', 'front' => 'file_proofofresidentialaddressa' }, + 'proof_of_registration' => { 'type' => 'extract_from_trade_register', + 'front' => 'file_proofofregistrationaaaaaaa' } + ) + expect(hash['documents']).to eq( + 'bank_verification' => { 'type' => 'bank_statement', 'front' => 'file_bankverificationaaaaaaaaaa' } + ) + # Key-level check on the JSON body, so a naming change cannot pass silently. + body = hash.to_json + expect(body).to include('"proof_of_residential_address":{') + expect(body).to include('"proof_of_registration":{') + end + + # On the EEA, GB and US Company Full (3.0) and Sole Trader Full (3.0) variants the API rejects any key + # on company.representatives[].documents it does not define (additionalProperties: false), so an + # attribute added here by mistake would fail the request. + it 'declares only the representative document keys the API accepts' do + setters = CheckoutSdk::Accounts::RepresentativeDocuments.public_instance_methods(false).grep(/=$/) + expect(setters.map { |m| m.to_s.chomp('=') }).to contain_exactly( + 'identity_verification', 'certified_authorised_signatory', 'proof_of_residential_address', 'proof_of_registration' + ) + end + + it 'serializes the certified authorised signatory with type and front only' do + signatory = CheckoutSdk::Accounts::CertifiedAuthorisedSignatory.new + signatory.type = CheckoutSdk::Accounts::CertifiedAuthorisedSignatoryType::POWER_OF_ATTORNEY + signatory.front = 'file_signatoryaaaaaaaaaaaaaaaaa' + documents = CheckoutSdk::Accounts::RepresentativeDocuments.new + documents.certified_authorised_signatory = signatory + + expect(serialize(documents)).to eq( + 'certified_authorised_signatory' => { 'type' => 'power_of_attorney', + 'front' => 'file_signatoryaaaaaaaaaaaaaaaaa' } + ) + end + + # Unset attributes are omitted; an attribute assigned nil is sent as null. + it 'omits unset representative documents and sends nil as null' do + registration = CheckoutSdk::Accounts::ProofOfRegistration.new + registration.type = CheckoutSdk::Accounts::ProofOfRegistrationType::OTHER + registration.front = 'file_proofofregistrationaaaaaaa' + documents = CheckoutSdk::Accounts::RepresentativeDocuments.new + documents.proof_of_registration = registration + + expect(serialize(documents)).to eq( + 'proof_of_registration' => { 'type' => 'other', 'front' => 'file_proofofregistrationaaaaaaa' } + ) + documents.identity_verification = nil + expect(serialize(documents)).to include('identity_verification' => nil) + end + + # Every attribute of OnboardSubEntityDocuments, so a naming change on any key cannot pass silently. + it 'serializes every top-level documents attribute' do + file = 'file_aaaaaaaaaaaaaaaaaaaaaaaaaa' + build = lambda do |klass, type = nil| + document = klass.new + document.type = type unless type.nil? + document.front = file + document + end + a = CheckoutSdk::Accounts + documents = a::OnboardSubEntityDocuments.new + documents.identity_verification = build.call(a::Document, a::DocumentType::PASSPORT) + documents.company_verification = build.call(a::CompanyVerification, + a::CompanyVerificationType::INCORPORATION_DOCUMENT) + documents.articles_of_association = build.call(a::ArticlesOfAssociation, + a::ArticlesOfAssociationType::ARTICLES_OF_ASSOCIATION) + documents.bank_verification = build.call(a::BankVerification, a::BankVerificationType::BANK_STATEMENT) + documents.shareholder_structure = build.call(a::ShareholderStructure, + a::ShareholderStructureType::CERTIFIED_SHAREHOLDER_STRUCTURE) + documents.proof_of_legality = build.call(a::ProofOfLegality, a::ProofOfLegalityType::PROOF_OF_LEGALITY) + documents.proof_of_principal_address = build.call(a::ProofOfPrincipalAddress, + a::ProofOfPrincipalAddressType::PROOF_OF_ADDRESS) + documents.additional_document1 = build.call(a::AdditionalDocument) + documents.additional_document2 = build.call(a::AdditionalDocument) + documents.additional_document3 = build.call(a::AdditionalDocument) + documents.tax_verification = build.call(a::TaxVerification, a::TaxVerificationType::EIN_LETTER) + documents.financial_verification = build.call(a::FinancialVerification, + a::FinancialVerificationType::FINANCIAL_STATEMENT) + documents.financial_statements = build.call(a::FinancialStatements, + a::FinancialStatementsType::FINANCIAL_STATEMENTS) + + expect(serialize(documents)).to eq( + 'identity_verification' => { 'type' => 'passport', 'front' => file }, + 'company_verification' => { 'type' => 'incorporation_document', 'front' => file }, + 'articles_of_association' => { 'type' => 'articles_of_association', 'front' => file }, + 'bank_verification' => { 'type' => 'bank_statement', 'front' => file }, + 'shareholder_structure' => { 'type' => 'certified_shareholder_structure', 'front' => file }, + 'proof_of_legality' => { 'type' => 'proof_of_legality', 'front' => file }, + 'proof_of_principal_address' => { 'type' => 'proof_of_address', 'front' => file }, + 'additional_document1' => { 'front' => file }, + 'additional_document2' => { 'front' => file }, + 'additional_document3' => { 'front' => file }, + 'tax_verification' => { 'type' => 'ein_letter', 'front' => file }, + 'financial_verification' => { 'type' => 'financial_statement', 'front' => file }, + 'financial_statements' => { 'type' => 'financial_statements', 'front' => file } + ) + end + + # Regression: both classes declared attr_reader only, so setting either attribute raised NoMethodError. + it 'allows setting ProofOfPrincipalAddress and FinancialVerification' do + address = CheckoutSdk::Accounts::ProofOfPrincipalAddress.new + address.type = CheckoutSdk::Accounts::ProofOfPrincipalAddressType::PROOF_OF_ADDRESS + address.front = 'file_aaaaaaaaaaaaaaaaaaaaaaaaaa' + verification = CheckoutSdk::Accounts::FinancialVerification.new + verification.type = CheckoutSdk::Accounts::FinancialVerificationType::FINANCIAL_STATEMENT + verification.front = 'file_aaaaaaaaaaaaaaaaaaaaaaaaaa' + + expect(serialize(address)).to eq('type' => 'proof_of_address', 'front' => 'file_aaaaaaaaaaaaaaaaaaaaaaaaaa') + expect(serialize(verification)).to eq('type' => 'financial_statement', 'front' => 'file_aaaaaaaaaaaaaaaaaaaaaaaaaa') + end + + # EEA and GB Company Full (3.0) allow a representative that is a company: + # { company: { legal_name, trading_name, registered_address }, ownership_percentage }. + it 'serializes a controlling company representative' do + address = CheckoutSdk::Common::Address.new + address.address_line1 = '1 Main Street' + address.city = 'London' + address.zip = 'W1T 4TJ' + address.country = CheckoutSdk::Common::Country::GB + company = CheckoutSdk::Accounts::Company.new + company.legal_name = 'Parent Holdings Ltd' + company.trading_name = 'Parent Holdings' + company.registered_address = address + representative = CheckoutSdk::Accounts::Representative.new + representative.company = company + representative.ownership_percentage = 60 + + expect(serialize(representative)).to eq( + 'company' => { + 'legal_name' => 'Parent Holdings Ltd', + 'trading_name' => 'Parent Holdings', + 'registered_address' => { 'address_line1' => '1 Main Street', 'city' => 'London', 'zip' => 'W1T 4TJ', + 'country' => 'GB' } + }, + 'ownership_percentage' => 60 + ) + end + + it 'serializes the v2.0 representative middle name' do + representative = CheckoutSdk::Accounts::Representative.new + representative.first_name = 'John' + representative.middle_name = 'Paul' + representative.last_name = 'Doe' + + expect(serialize(representative)).to eq('first_name' => 'John', 'middle_name' => 'Paul', 'last_name' => 'Doe') + end + + # The US ISV Seller variants (3.0) require both addresses. + it 'serializes the US ISV Seller email addresses with the PCI compliance contact' do + email_addresses = CheckoutSdk::Accounts::EntityEmailAddresses.new + email_addresses.primary = 'admin@example.com' + email_addresses.pci_compliance_contact = 'pci@example.com' + + expect(serialize(email_addresses)).to eq('primary' => 'admin@example.com', + 'pci_compliance_contact' => 'pci@example.com') + end + + # upload_file and upload_entity_file send the purpose value on the wire. + it 'exposes every onboarding upload purpose' do + purpose = CheckoutSdk::Accounts::FilePurpose + expect(purpose.constants.map { |c| purpose.const_get(c) }).to contain_exactly( + 'additional_document', 'articles_of_association', 'bank_verification', 'certified_authorised_signatory', + 'company_ownership', 'company_verification', 'financial_verification', 'identity_verification', + 'proof_of_legality', 'proof_of_principal_address', 'shareholder_structure', 'tax_verification', + 'proof_of_residential_address', 'proof_of_registration' + ) + end + + it 'exposes the representative document types' do + expect(CheckoutSdk::Accounts::ProofOfResidentialAddressType::PROOF_OF_ADDRESS).to eq('proof_of_address') + expect(CheckoutSdk::Accounts::ProofOfRegistrationType::EXTRACT_FROM_TRADE_REGISTER) + .to eq('extract_from_trade_register') + expect(CheckoutSdk::Accounts::ProofOfRegistrationType::OTHER).to eq('other') + expect(CheckoutSdk::Accounts::CertifiedAuthorisedSignatoryType::POWER_OF_ATTORNEY).to eq('power_of_attorney') + end + + def isv_address + address = CheckoutSdk::Common::Address.new + address.address_line1 = '123 Main Street' + address.city = 'San Francisco' + address.state = 'CA' + address.zip = '94105' + address.country = CheckoutSdk::Common::Country::US + address + end + + def isv_representative_individual(first_name, last_name, national_id_number, birth, phone_number) + a = CheckoutSdk::Accounts + individual = a::RepresentativeIndividual.new + individual.first_name = first_name + individual.last_name = last_name + individual.email_address = "#{first_name.downcase}.#{last_name.downcase}@example.com" + individual.national_id_type = a::NationalIdType::SSN + individual.national_id_number = national_id_number + individual.date_of_birth = a::DateOfBirth.new + individual.date_of_birth.day, individual.date_of_birth.month, individual.date_of_birth.year = birth + individual.place_of_birth = a::PlaceOfBirth.new + individual.place_of_birth.country = CheckoutSdk::Common::Country::US + individual.citizenships = [a::Citizenship.new] + individual.citizenships[0].country = CheckoutSdk::Common::Country::US + individual.phone = a::Phone.new + individual.phone.country_code = CheckoutSdk::Common::Country::US + individual.phone.number = phone_number + individual.address = isv_address + individual + end + + def isv_processing_details + a = CheckoutSdk::Accounts + details = a::ProcessingDetails.new + details.annual_processing_volume = 1000 + details.average_transaction_value = 2000 + details.average_order_fulfillment_time = 3 + details.target_countries = [CheckoutSdk::Common::Country::US] + details.currency = CheckoutSdk::Common::Currency::USD + details.payments = a::ProcessingDetailsPayments.new + details.payments.ach = a::ProcessingDetailsAch.new + details.payments.ach.annual_ach_volume = 100_000 + details.payments.ach.average_ach_transaction_size = 5000 + details.payments.ach.estimated_monthly_credit_volume = 50_000 + details.payments.ach.average_credit_amount = 2500 + details + end + + # Everything the two US ISV Seller (3.0) swagger examples share apart from the company. + def isv_onboard_entity(reference, signer_name, primary_email, url) + a = CheckoutSdk::Accounts + request = a::OnboardEntity.new + request.reference = reference + request.agreed_terms = a::AgreedTerms.new + request.agreed_terms.date = '2026-07-02T10:30:00.0000000+00:00' + request.agreed_terms.ip_address = '8.8.8.8' + request.agreed_terms.name = signer_name + request.agreed_terms.email = primary_email + request.agreed_terms.version = 'cko-platform-terms-1.0.0' + request.seller_category = 'cat_retail_001' + request.processing_details = isv_processing_details + request.contact_details = a::ContactDetails.new + request.contact_details.phone = a::Phone.new + request.contact_details.phone.number = '4155678900' + request.contact_details.phone.country_code = CheckoutSdk::Common::Country::US + request.contact_details.email_addresses = a::EntityEmailAddresses.new + request.contact_details.email_addresses.primary = primary_email + request.contact_details.email_addresses.pci_compliance_contact = 'pci.contact@example.com' + request.profile = a::Profile.new + request.profile.urls = [url] + request.profile.mccs = ['5551'] + request.profile.holding_currencies = [CheckoutSdk::Common::Currency::USD] + request.profile.default_holding_currency = CheckoutSdk::Common::Currency::USD + request + end + + def date_of_incorporation(day, month, year) + doi = CheckoutSdk::Accounts::DateOfIncorporation.new + doi.day = day + doi.month = month + doi.year = year + doi + end + + # The USISVSellerCompany3-0 swagger example, verbatim. + it 'serializes the US ISV Seller Company (3.0) swagger example' do + payload = JSON.parse(<<~JSON) + { + "reference": "isv-seller-example001", + "agreed_terms": { + "date": "2026-07-02T10:30:00.0000000+00:00", + "ip_address": "8.8.8.8", + "name": "Toby Arden", + "email": "toby.arden@example.com", + "version": "cko-platform-terms-1.0.0" + }, + "seller_category": "cat_retail_001", + "processing_details": { + "annual_processing_volume": 1000, + "average_transaction_value": 2000, + "average_order_fulfillment_time": 3, + "target_countries": ["US"], + "currency": "USD", + "payments": { + "ach": { + "annual_ach_volume": 100000, + "average_ach_transaction_size": 5000, + "estimated_monthly_credit_volume": 50000, + "average_credit_amount": 2500 + } + } + }, + "contact_details": { + "phone": {"number": "4155678900", "country_code": "US"}, + "email_addresses": { + "primary": "toby.arden@example.com", + "pci_compliance_contact": "pci.contact@example.com" + } + }, + "profile": { + "urls": ["https://www.isv-seller-example.com"], + "mccs": ["5551"], + "holding_currencies": ["USD"], + "default_holding_currency": "USD" + }, + "company": { + "business_registration_number": "12-3456789", + "business_type": "private_corporation", + "legal_name": "ISV Seller Example Inc", + "trading_name": "ISV Seller Example", + "registered_address": { + "address_line1": "123 Main Street", + "city": "San Francisco", + "state": "CA", + "zip": "94105", + "country": "US" + }, + "principal_address": { + "address_line1": "123 Main Street", + "city": "San Francisco", + "state": "CA", + "zip": "94105", + "country": "US" + }, + "date_of_incorporation": {"year": 2025, "month": 10, "day": 1}, + "representatives": [ + { + "roles": ["ubo", "control_person"], + "ownership_percentage": 25, + "company_position": "ceo", + "individual": { + "first_name": "Toby", + "last_name": "Arden", + "email_address": "toby.arden@example.com", + "national_id_type": "ssn", + "national_id_number": "123456789", + "date_of_birth": {"day": 15, "month": 1, "year": 1990}, + "place_of_birth": {"country": "US"}, + "citizenships": [{"country": "US"}], + "phone": {"country_code": "US", "number": "4155678901"}, + "address": { + "address_line1": "123 Main Street", + "city": "San Francisco", + "state": "CA", + "zip": "94105", + "country": "US" + } + } + }, + { + "roles": ["authorised_signatory"], + "individual": { + "first_name": "Alex", + "last_name": "Morgan", + "email_address": "alex.morgan@example.com", + "national_id_type": "ssn", + "national_id_number": "987654321", + "date_of_birth": {"day": 22, "month": 6, "year": 1985}, + "place_of_birth": {"country": "US"}, + "citizenships": [{"country": "US"}], + "phone": {"country_code": "US", "number": "4155678902"}, + "address": { + "address_line1": "123 Main Street", + "city": "San Francisco", + "state": "CA", + "zip": "94105", + "country": "US" + } + } + } + ] + } + } + JSON + a = CheckoutSdk::Accounts + ceo = a::Representative.new + ceo.roles = [a::EntityRoles::UBO, a::EntityRoles::CONTROL_PERSON] + ceo.ownership_percentage = 25 + ceo.company_position = a::CompanyPosition::CEO + ceo.individual = isv_representative_individual('Toby', 'Arden', '123456789', [15, 1, 1990], '4155678901') + signatory = a::Representative.new + signatory.roles = [a::EntityRoles::AUTHORISED_SIGNATORY] + signatory.individual = isv_representative_individual('Alex', 'Morgan', '987654321', [22, 6, 1985], '4155678902') + company = a::Company.new + company.business_registration_number = '12-3456789' + company.business_type = a::BusinessType::PRIVATE_CORPORATION + company.legal_name = 'ISV Seller Example Inc' + company.trading_name = 'ISV Seller Example' + company.registered_address = isv_address + company.principal_address = isv_address + company.date_of_incorporation = date_of_incorporation(1, 10, 2025) + company.representatives = [ceo, signatory] + request = isv_onboard_entity('isv-seller-example001', 'Toby Arden', 'toby.arden@example.com', + 'https://www.isv-seller-example.com') + request.company = company + + expect(JSON.parse(serialize(request).to_json)).to eq(payload) + end + + # The USISVSellerSoleTrader3-0 swagger example, verbatim. + it 'serializes the US ISV Seller Sole Trader (3.0) swagger example' do + payload = JSON.parse(<<~JSON) + { + "reference": "isv-sole-trader-example001", + "agreed_terms": { + "date": "2026-07-02T10:30:00.0000000+00:00", + "ip_address": "8.8.8.8", + "name": "Hannah Bret", + "email": "hannah.bret@example.com", + "version": "cko-platform-terms-1.0.0" + }, + "seller_category": "cat_retail_001", + "processing_details": { + "annual_processing_volume": 1000, + "average_transaction_value": 2000, + "average_order_fulfillment_time": 3, + "target_countries": ["US"], + "currency": "USD", + "payments": { + "ach": { + "annual_ach_volume": 100000, + "average_ach_transaction_size": 5000, + "estimated_monthly_credit_volume": 50000, + "average_credit_amount": 2500 + } + } + }, + "contact_details": { + "phone": {"number": "4155678900", "country_code": "US"}, + "email_addresses": { + "primary": "hannah.bret@example.com", + "pci_compliance_contact": "pci.contact@example.com" + } + }, + "profile": { + "urls": ["https://www.isv-sole-trader-example.com"], + "mccs": ["5551"], + "holding_currencies": ["USD"], + "default_holding_currency": "USD" + }, + "company": { + "business_type": "individual_or_sole_proprietorship", + "is_registered_company": false, + "trading_name": "Hannah's Goods", + "date_of_incorporation": {"year": 2025, "month": 10, "day": 1}, + "principal_address": { + "address_line1": "123 Main Street", + "city": "San Francisco", + "state": "CA", + "zip": "94105", + "country": "US" + }, + "representatives": [ + { + "roles": ["ubo"], + "ownership_percentage": 100, + "individual": { + "first_name": "Hannah", + "last_name": "Bret", + "email_address": "hannah.bret@example.com", + "national_id_type": "ssn", + "national_id_number": "123456789", + "date_of_birth": {"day": 15, "month": 1, "year": 1990}, + "place_of_birth": {"country": "US"}, + "citizenships": [{"country": "US"}], + "phone": {"country_code": "US", "number": "4155678901"}, + "address": { + "address_line1": "123 Main Street", + "city": "San Francisco", + "state": "CA", + "zip": "94105", + "country": "US" + } + } + } + ] + } + } + JSON + a = CheckoutSdk::Accounts + owner = a::Representative.new + owner.roles = [a::EntityRoles::UBO] + owner.ownership_percentage = 100 + owner.individual = isv_representative_individual('Hannah', 'Bret', '123456789', [15, 1, 1990], '4155678901') + company = a::Company.new + company.business_type = a::BusinessType::INDIVIDUAL_OR_SOLE_PROPRIETORSHIP + company.is_registered_company = false + company.trading_name = "Hannah's Goods" + company.date_of_incorporation = date_of_incorporation(1, 10, 2025) + company.principal_address = isv_address + company.representatives = [owner] + request = isv_onboard_entity('isv-sole-trader-example001', 'Hannah Bret', 'hannah.bret@example.com', + 'https://www.isv-sole-trader-example.com') + request.company = company + + expect(JSON.parse(serialize(request).to_json)).to eq(payload) + end + + it 'serializes ContactDetails with phone, email addresses and invitee' do + a = CheckoutSdk::Accounts + contact = a::ContactDetails.new + contact.phone = a::Phone.new + contact.phone.country_code = CheckoutSdk::Common::Country::US + contact.phone.number = '4155678900' + contact.email_addresses = a::EntityEmailAddresses.new + contact.email_addresses.primary = 'admin@example.com' + contact.email_addresses.pci_compliance_contact = 'pci@example.com' + contact.invitee = a::Invitee.new + contact.invitee.email = 'invitee@example.com' + + expect(serialize(contact)).to eq( + 'phone' => { 'country_code' => 'US', 'number' => '4155678900' }, + 'email_addresses' => { 'primary' => 'admin@example.com', 'pci_compliance_contact' => 'pci@example.com' }, + 'invitee' => { 'email' => 'invitee@example.com' } + ) + end + + # EEA Company Full (3.0) is the only variant with regulatory_licence_number. + it 'serializes the EEA Company Full (3.0) Company fields and omits the deprecated document' do + address = CheckoutSdk::Common::Address.new + address.address_line1 = '1 Rue de Rivoli' + address.city = 'Paris' + address.zip = '75001' + address.country = CheckoutSdk::Common::Country::FR + company = CheckoutSdk::Accounts::Company.new + company.business_registration_number = '552100554' + company.business_type = CheckoutSdk::Accounts::BusinessType::LIMITED_COMPANY + company.legal_name = 'Super Hero Masks SARL' + company.trading_name = 'Super Hero Masks' + company.principal_address = address + company.registered_address = address + company.regulatory_licence_number = 'LIC-12345' + company.date_of_incorporation = date_of_incorporation(1, 6, 2010) + + hash = serialize(company) + expected_address = { 'address_line1' => '1 Rue de Rivoli', 'city' => 'Paris', 'zip' => '75001', 'country' => 'FR' } + expect(hash).to eq( + 'business_registration_number' => '552100554', + 'business_type' => 'limited_company', + 'legal_name' => 'Super Hero Masks SARL', + 'trading_name' => 'Super Hero Masks', + 'principal_address' => expected_address, + 'registered_address' => expected_address, + 'regulatory_licence_number' => 'LIC-12345', + 'date_of_incorporation' => { 'day' => 1, 'month' => 6, 'year' => 2010 } + ) + expect(hash).not_to have_key('document') + end + + # financial_details exists on the EEA and US Company Full and Lite (2.0) variants. + it 'serializes the v2.0 Company financial_details with every EntityFinancialDetails field' do + financial = CheckoutSdk::Accounts::EntityFinancialDetails.new + financial.annual_processing_volume = 1_200_000 + financial.average_transaction_value = 5_000 + financial.highest_transaction_value = 25_000 + financial.currency = CheckoutSdk::Common::Currency::EUR + company = CheckoutSdk::Accounts::Company.new + company.legal_name = 'Super Hero Masks SARL' + company.financial_details = financial + + expect(serialize(company)).to eq( + 'legal_name' => 'Super Hero Masks SARL', + 'financial_details' => { + 'annual_processing_volume' => 1_200_000, + 'average_transaction_value' => 5_000, + 'highest_transaction_value' => 25_000, + 'currency' => 'EUR' + } + ) + end + + it 'serializes every RepresentativeIndividual field' do + a = CheckoutSdk::Accounts + individual = isv_representative_individual('John', 'Doe', '123456789', [5, 5, 1990], '4155678901') + individual.middle_name = 'Paul' + individual.citizenships[0].type = 'citizenship' + + expect(serialize(individual)).to eq( + 'first_name' => 'John', + 'middle_name' => 'Paul', + 'last_name' => 'Doe', + 'date_of_birth' => { 'day' => 5, 'month' => 5, 'year' => 1990 }, + 'place_of_birth' => { 'country' => 'US' }, + 'citizenships' => [{ 'type' => 'citizenship', 'country' => 'US' }], + 'national_id_type' => 'ssn', + 'national_id_number' => '123456789', + 'email_address' => 'john.doe@example.com', + 'phone' => { 'country_code' => 'US', 'number' => '4155678901' }, + 'address' => { 'address_line1' => '123 Main Street', 'city' => 'San Francisco', 'state' => 'CA', + 'zip' => '94105', 'country' => 'US' } + ) + expect(a::RepresentativeIndividual.public_instance_methods(false).grep(/=$/).size).to eq(11) + end + + it 'serializes the v3.0 Representative id' do + individual = CheckoutSdk::Accounts::RepresentativeIndividual.new + individual.first_name = 'John' + individual.last_name = 'Doe' + representative = CheckoutSdk::Accounts::Representative.new + representative.id = 'rep_6kr6pq3fbvtpjhrxmsrioxk52x' + representative.individual = individual + representative.roles = [CheckoutSdk::Accounts::EntityRoles::LEGAL_REPRESENTATIVE] + + expect(serialize(representative)).to eq( + 'id' => 'rep_6kr6pq3fbvtpjhrxmsrioxk52x', + 'individual' => { 'first_name' => 'John', 'last_name' => 'Doe' }, + 'roles' => ['legal_representative'] + ) + end + + # identification is on the US Company (2.0) representative, place_of_birth on the EEA Company Full (2.0) + # one; the v2.0 phone carries the number only. + it 'serializes the remaining v2.0 Representative fields' do + a = CheckoutSdk::Accounts + birth = a::DateOfBirth.new + birth.day = 5 + birth.month = 5 + birth.year = 1990 + phone = a::Phone.new + phone.number = '4155678901' + us = a::Representative.new + us.address = isv_address + us.identification = a::Identification.new + us.identification.national_id_number = '123456789' + us.phone = phone + us.date_of_birth = birth + eea = a::Representative.new + eea.phone = phone + eea.date_of_birth = birth + eea.place_of_birth = a::PlaceOfBirth.new + eea.place_of_birth.country = CheckoutSdk::Common::Country::FR + + expect(serialize(us)).to eq( + 'address' => { 'address_line1' => '123 Main Street', 'city' => 'San Francisco', 'state' => 'CA', + 'zip' => '94105', 'country' => 'US' }, + 'identification' => { 'national_id_number' => '123456789' }, + 'phone' => { 'number' => '4155678901' }, + 'date_of_birth' => { 'day' => 5, 'month' => 5, 'year' => 1990 } + ) + expect(serialize(eea)).to eq( + 'phone' => { 'number' => '4155678901' }, + 'date_of_birth' => { 'day' => 5, 'month' => 5, 'year' => 1990 }, + 'place_of_birth' => { 'country' => 'FR' } + ) + end + + # The nine v2.0 individual properties across the variants: identification and financial_details are + # US Sole Trader (2.0) only, place_of_birth EEA Sole Trader (2.0) only. + it 'serializes every v2.0 Individual field and omits the deprecated ones when unset' do + a = CheckoutSdk::Accounts + birth = a::DateOfBirth.new + birth.day = 5 + birth.month = 5 + birth.year = 1990 + us = a::Individual.new + us.first_name = 'John' + us.middle_name = 'Paul' + us.last_name = 'Doe' + us.trading_name = 'John Doe Masks' + us.registered_address = isv_address + us.date_of_birth = birth + us.identification = a::Identification.new + us.identification.national_id_number = '123456789' + us.financial_details = a::EntityFinancialDetails.new + us.financial_details.annual_processing_volume = 120_000 + us.financial_details.average_transaction_value = 500 + us.financial_details.highest_transaction_value = 2_500 + us.financial_details.currency = CheckoutSdk::Common::Currency::USD + eea = a::Individual.new + eea.first_name = 'Jean' + eea.last_name = 'Dupont' + eea.place_of_birth = a::PlaceOfBirth.new + eea.place_of_birth.country = CheckoutSdk::Common::Country::FR + + us_hash = serialize(us) + + expect(us_hash).to eq( + 'first_name' => 'John', + 'middle_name' => 'Paul', + 'last_name' => 'Doe', + 'trading_name' => 'John Doe Masks', + 'registered_address' => { 'address_line1' => '123 Main Street', 'city' => 'San Francisco', 'state' => 'CA', + 'zip' => '94105', 'country' => 'US' }, + 'date_of_birth' => { 'day' => 5, 'month' => 5, 'year' => 1990 }, + 'identification' => { 'national_id_number' => '123456789' }, + 'financial_details' => { 'annual_processing_volume' => 120_000, 'average_transaction_value' => 500, + 'highest_transaction_value' => 2_500, 'currency' => 'USD' } + ) + expect(serialize(eea)).to eq('first_name' => 'Jean', 'last_name' => 'Dupont', + 'place_of_birth' => { 'country' => 'FR' }) + expect(us_hash.keys).not_to include('legal_name', 'national_tax_id') + end + + it 'serializes Identification to the national_id_number only' do + identification = CheckoutSdk::Accounts::Identification.new + identification.national_id_number = '123456789' + + expect(serialize(identification)).to eq('national_id_number' => '123456789') end end diff --git a/spec/checkout_sdk/accounts/reserve_rules_members_spec.rb b/spec/checkout_sdk/accounts/reserve_rules_members_spec.rb index d5b3c34..7001335 100644 --- a/spec/checkout_sdk/accounts/reserve_rules_members_spec.rb +++ b/spec/checkout_sdk/accounts/reserve_rules_members_spec.rb @@ -3,6 +3,11 @@ let(:api_client_mock) { double('api_client') } let(:files_client_mock) { double('files_client') } let(:configuration_mock) { double('configuration') } + let(:entity_id) { 'ent_ovpg62ssyywodc4veodhelfrpv' } + let(:user_id) { 'usr_nlmuxfhamdp6jbjnxy6c4yg6jt' } + let(:reserve_rule_id) { 'rsv_ajm6t2mhntqmacybvmzkyjh5lw' } + let(:instrument_id) { 'ppi_d5i6yil5666tyrglkvnjuhwon3' } + let(:file_id) { 'file_6lbss42ezvoufcb2beo76rvwly' } let(:client) do CheckoutSdk::Accounts::AccountsClient.new(api_client_mock, files_client_mock, configuration_mock) end @@ -16,24 +21,24 @@ it 'POSTs to accounts/entities/{id}/reserve-rules' do req = CheckoutSdk::Accounts::ReserveRuleCreateRequest.new expect(api_client_mock).to receive(:invoke_post) - .with('accounts/entities/ent_1/reserve-rules', 'secret_key', req).and_return('r') - expect(client.add_reserve_rule('ent_1', req)).to eq('r') + .with("accounts/entities/#{entity_id}/reserve-rules", 'secret_key', req).and_return('r') + expect(client.add_reserve_rule(entity_id, req)).to eq('r') end end describe '#query_reserve_rules' do it 'GETs accounts/entities/{id}/reserve-rules' do expect(api_client_mock).to receive(:invoke_get) - .with('accounts/entities/ent_1/reserve-rules', 'secret_key').and_return('r') - expect(client.query_reserve_rules('ent_1')).to eq('r') + .with("accounts/entities/#{entity_id}/reserve-rules", 'secret_key').and_return('r') + expect(client.query_reserve_rules(entity_id)).to eq('r') end end describe '#get_reserve_rule' do it 'GETs accounts/entities/{id}/reserve-rules/{rid}' do expect(api_client_mock).to receive(:invoke_get) - .with('accounts/entities/ent_1/reserve-rules/rsv_1', 'secret_key').and_return('r') - expect(client.get_reserve_rule('ent_1', 'rsv_1')).to eq('r') + .with("accounts/entities/#{entity_id}/reserve-rules/#{reserve_rule_id}", 'secret_key').and_return('r') + expect(client.get_reserve_rule(entity_id, reserve_rule_id)).to eq('r') end end @@ -42,38 +47,38 @@ req = CheckoutSdk::Accounts::ReserveRuleUpdateRequest.new etag = 'W/"3a-fXqMK..."' expect(api_client_mock).to receive(:invoke_put) do |path, auth, body, headers| - expect(path).to eq('accounts/entities/ent_1/reserve-rules/rsv_1') + expect(path).to eq("accounts/entities/#{entity_id}/reserve-rules/#{reserve_rule_id}") expect(auth).to eq('secret_key') expect(body).to eq(req) expect(headers).to be_a(CheckoutSdk::Common::Headers) expect(headers.if_match).to eq(etag) 'r' end - expect(client.update_reserve_rule('ent_1', 'rsv_1', etag, req)).to eq('r') + expect(client.update_reserve_rule(entity_id, reserve_rule_id, etag, req)).to eq('r') end it 'omits the Headers container when no etag is provided' do req = CheckoutSdk::Accounts::ReserveRuleUpdateRequest.new expect(api_client_mock).to receive(:invoke_put) - .with('accounts/entities/ent_1/reserve-rules/rsv_1', 'secret_key', req, nil) + .with("accounts/entities/#{entity_id}/reserve-rules/#{reserve_rule_id}", 'secret_key', req, nil) .and_return('r') - expect(client.update_reserve_rule('ent_1', 'rsv_1', nil, req)).to eq('r') + expect(client.update_reserve_rule(entity_id, reserve_rule_id, nil, req)).to eq('r') end it 'omits the Headers container when etag is an empty string' do req = CheckoutSdk::Accounts::ReserveRuleUpdateRequest.new expect(api_client_mock).to receive(:invoke_put) - .with('accounts/entities/ent_1/reserve-rules/rsv_1', 'secret_key', req, nil) + .with("accounts/entities/#{entity_id}/reserve-rules/#{reserve_rule_id}", 'secret_key', req, nil) .and_return('r') - expect(client.update_reserve_rule('ent_1', 'rsv_1', '', req)).to eq('r') + expect(client.update_reserve_rule(entity_id, reserve_rule_id, '', req)).to eq('r') end end describe '#get_sub_entity_members' do it 'GETs accounts/entities/{id}/members' do expect(api_client_mock).to receive(:invoke_get) - .with('accounts/entities/ent_1/members', 'secret_key').and_return('r') - expect(client.get_sub_entity_members('ent_1')).to eq('r') + .with("accounts/entities/#{entity_id}/members", 'secret_key').and_return('r') + expect(client.get_sub_entity_members(entity_id)).to eq('r') end end @@ -81,30 +86,53 @@ it 'PUTs accounts/entities/{id}/members/{userId} with the required body' do body = {} expect(api_client_mock).to receive(:invoke_put) - .with('accounts/entities/ent_1/members/usr_1', 'secret_key', body).and_return('r') - expect(client.reinvite_sub_entity_member('ent_1', 'usr_1', body)).to eq('r') + .with("accounts/entities/#{entity_id}/members/#{user_id}", 'secret_key', body).and_return('r') + expect(client.reinvite_sub_entity_member(entity_id, user_id, body)).to eq('r') end it 'raises when called without a body (required by swagger)' do - expect { client.reinvite_sub_entity_member('ent_1', 'usr_1') } + expect { client.reinvite_sub_entity_member(entity_id, user_id) } .to raise_error(ArgumentError, /wrong number of arguments/) end end describe '#upload_entity_file' do - it 'submits multipart to entities/{id}/files via files_client' do + it 'posts JSON to entities/{id}/files via files_client' do req = CheckoutSdk::Accounts::EntityFilesRequest.new - expect(files_client_mock).to receive(:submit_file) - .with('entities/ent_1/files', 'secret_key', req).and_return('r') - expect(client.upload_entity_file('ent_1', req)).to eq('r') + expect(files_client_mock).to receive(:invoke_post) + .with("entities/#{entity_id}/files", 'secret_key', req).and_return('r') + expect(client.upload_entity_file(entity_id, req)).to eq('r') end end describe '#get_entity_file' do it 'GETs entities/{id}/files/{fileId} via files_client' do expect(files_client_mock).to receive(:invoke_get) - .with('entities/ent_1/files/file_1', 'secret_key').and_return('r') - expect(client.get_entity_file('ent_1', 'file_1')).to eq('r') + .with("entities/#{entity_id}/files/#{file_id}", 'secret_key').and_return('r') + expect(client.get_entity_file(entity_id, file_id)).to eq('r') + end + end + + describe '#update_payment_instrument' do + let(:path) { "accounts/entities/#{entity_id}/payment-instruments/#{instrument_id}" } + + it 'PATCHes the request and sends headers.if_match as the If-Match header' do + req = CheckoutSdk::Accounts::UpdatePaymentInstrumentRequest.new + req.label = 'Renamed account' + req.headers = CheckoutSdk::Common::Headers.new + req.headers.if_match = '"Y3Y9MCZydj0w"' + expect(api_client_mock).to receive(:invoke_patch).with(path, 'secret_key', req, req.headers).and_return('r') + expect(client.update_payment_instrument(entity_id, instrument_id, req)).to eq('r') + end + + it 'accepts a Hash request and builds the If-Match header from it' do + req = { label: 'Renamed account', headers: { if_match: '"Y3Y9MCZydj0w"' } } + expect(api_client_mock).to receive(:invoke_patch) do |called_path, auth, body, headers| + expect([called_path, auth, body]).to eq([path, 'secret_key', req]) + expect(headers.if_match).to eq('"Y3Y9MCZydj0w"') + 'r' + end + expect(client.update_payment_instrument(entity_id, instrument_id, req)).to eq('r') end end end