diff --git a/lib/checkout_sdk/payments/payments.rb b/lib/checkout_sdk/payments/payments.rb index 6b7b907..9a901d1 100644 --- a/lib/checkout_sdk/payments/payments.rb +++ b/lib/checkout_sdk/payments/payments.rb @@ -240,6 +240,7 @@ require 'checkout_sdk/payments/setups/payment_setup_device_os' require 'checkout_sdk/payments/setups/payment_setup_payment_method_initialization' require 'checkout_sdk/payments/setups/payment_setup_payment_method_status' +require 'checkout_sdk/payments/setups/payment_setup_payment_type' require 'checkout_sdk/payments/setups/stablecoin_payment_method' require 'checkout_sdk/payments/setups/payment_setups_client' diff --git a/lib/checkout_sdk/payments/setups/account_funding_transaction_identification.rb b/lib/checkout_sdk/payments/setups/account_funding_transaction_identification.rb index 8261931..f746e7c 100644 --- a/lib/checkout_sdk/payments/setups/account_funding_transaction_identification.rb +++ b/lib/checkout_sdk/payments/setups/account_funding_transaction_identification.rb @@ -2,11 +2,20 @@ module CheckoutSdk module Payments + # Sender identification details of an Account Funding Transaction on a Payment Setup. + # Maps swagger `PaymentSetup.account_funding_transaction.sender.identification`. + # # @!attribute type - # @return [String] {AccountFundingTransactionIdentificationType} + # The type of identification used to identify the sender. + # [Optional] + # @return [String] One of {AccountFundingTransactionIdentificationType}. # @!attribute number + # The identification number. + # [Optional] # @return [String] # @!attribute issuing_country + # The two-letter ISO country code of the country that issued the identification. + # [Optional] # @return [String] {CheckoutSdk::Common::Country} class AccountFundingTransactionIdentification attr_accessor :type, diff --git a/lib/checkout_sdk/payments/setups/account_funding_transaction_identification_type.rb b/lib/checkout_sdk/payments/setups/account_funding_transaction_identification_type.rb index 5ed9969..d943fa4 100644 --- a/lib/checkout_sdk/payments/setups/account_funding_transaction_identification_type.rb +++ b/lib/checkout_sdk/payments/setups/account_funding_transaction_identification_type.rb @@ -2,14 +2,15 @@ module CheckoutSdk module Payments - # Identification document type used by the sender of an Account Funding Transaction - # on a Payment Setup. + # The type of identification used to identify the sender of an Account Funding Transaction on a + # Payment Setup. Maps swagger `PaymentSetup.account_funding_transaction.sender.identification.type`. module AccountFundingTransactionIdentificationType + # A driving license. DRIVING_LICENSE = 'driving_license' + # A passport. PASSPORT = 'passport' + # A national identity document. NATIONAL_ID = 'national_id' - TAX_ID = 'tax_id' - OTHER = 'other' end end end diff --git a/lib/checkout_sdk/payments/setups/account_funding_transaction_recipient.rb b/lib/checkout_sdk/payments/setups/account_funding_transaction_recipient.rb index 855e319..5c32569 100644 --- a/lib/checkout_sdk/payments/setups/account_funding_transaction_recipient.rb +++ b/lib/checkout_sdk/payments/setups/account_funding_transaction_recipient.rb @@ -2,17 +2,45 @@ module CheckoutSdk module Payments + # Account funding transaction recipient details on a Payment Setup. + # Maps swagger `PaymentSetup.account_funding_transaction.recipient`. + # # @!attribute first_name + # The recipient's first name. + # [Optional] + # max 50 characters # @return [String] # @!attribute last_name + # The recipient's last name. + # [Optional] + # max 50 characters # @return [String] # @!attribute account_number + # Any identifier like part of the PAN (first six digits and last four digits), an IBAN, an internal + # account number, or a phone number related to the primary recipient's account. + # [Optional] + # max 34 characters # @return [String] # @!attribute account_type + # The type of the recipient's account. + # [Optional] + # @deprecated Not part of the Recipient schema of the Payment Setups account funding transaction; the + # API ignores it on requests. There is no replacement. Will be removed in the next major version. # @return [String] # @!attribute date_of_birth - # @return [String] Format: YYYY-MM-DD + # Date of birth of the recipient (yyyy-mm-dd). + # [Optional] + # Format: date + # @return [String] # @!attribute address + # The recipient's address. Maps swagger `PaymentSetupAddress`; all keys are optional: + # - `address_line1`: The first line of the address. max 200 characters + # - `address_line2`: The second line of the address. max 200 characters + # - `city`: The address city. max 50 characters + # - `state`: The address state or county. max 50 characters + # - `zip`: The postal or zip code. max 50 characters + # - `country`: The address country, in ISO 3166-1 alpha-2 format. max 2 characters + # [Optional] # @return [CheckoutSdk::Common::Address] class AccountFundingTransactionRecipient attr_accessor :first_name, diff --git a/lib/checkout_sdk/payments/setups/account_funding_transaction_sender.rb b/lib/checkout_sdk/payments/setups/account_funding_transaction_sender.rb index 349a184..328ecd7 100644 --- a/lib/checkout_sdk/payments/setups/account_funding_transaction_sender.rb +++ b/lib/checkout_sdk/payments/setups/account_funding_transaction_sender.rb @@ -2,19 +2,51 @@ module CheckoutSdk module Payments + # Account funding transaction sender details on a Payment Setup. + # Maps swagger `PaymentSetup.account_funding_transaction.sender`. + # # @!attribute first_name + # The sender's first name. + # [Optional] + # @deprecated Not part of the Sender schema of the Payment Setups account funding transaction; the + # API ignores it on requests. There is no replacement. Will be removed in the next major version. # @return [String] # @!attribute last_name + # The sender's last name. + # [Optional] + # @deprecated Not part of the Sender schema of the Payment Setups account funding transaction; the + # API ignores it on requests. There is no replacement. Will be removed in the next major version. # @return [String] # @!attribute date_of_birth - # @return [String] Format: YYYY-MM-DD + # Date of birth of the sender (yyyy-mm-dd). + # [Optional] + # Format: date + # @return [String] # @!attribute country_of_birth + # The sender's country of birth, as an ISO 3166-1 alpha-2 code. + # [Optional] + # @deprecated Not part of the Sender schema of the Payment Setups account funding transaction; the + # API ignores it on requests. There is no replacement. Will be removed in the next major version. # @return [String] {CheckoutSdk::Common::Country} # @!attribute nationality + # The sender's nationality, as an ISO 3166-1 alpha-2 code. + # [Optional] + # @deprecated Not part of the Sender schema of the Payment Setups account funding transaction; the + # API ignores it on requests. There is no replacement. Will be removed in the next major version. # @return [String] {CheckoutSdk::Common::Country} # @!attribute address + # The sender's address. + # [Optional] + # @deprecated Not part of the Sender schema of the Payment Setups account funding transaction; the + # API ignores it on requests. There is no replacement. Will be removed in the next major version. # @return [CheckoutSdk::Common::Address] + # @!attribute reference + # The unique reference for the sender of the payment. + # [Optional] + # @return [String] # @!attribute identification + # Sender identification details. + # [Optional] # @return [AccountFundingTransactionIdentification] class AccountFundingTransactionSender attr_accessor :first_name, @@ -23,6 +55,7 @@ class AccountFundingTransactionSender :country_of_birth, :nationality, :address, + :reference, :identification end end diff --git a/lib/checkout_sdk/payments/setups/bacs_payment_method.rb b/lib/checkout_sdk/payments/setups/bacs_payment_method.rb index bc1f5df..31d9580 100644 --- a/lib/checkout_sdk/payments/setups/bacs_payment_method.rb +++ b/lib/checkout_sdk/payments/setups/bacs_payment_method.rb @@ -2,41 +2,77 @@ module CheckoutSdk module Payments - # Bacs payment method configuration on a Payment Setup. Maps swagger `Bacs`. - # - # @!attribute status - # @return [String] (response-only) Payment method status, one of - # {PaymentSetupPaymentMethodStatus} (e.g. "ready"). - # @!attribute flags - # @return [Array(String)] (response-only) Diagnostic flags returned by the API. - # @!attribute initialization - # @return [String] The initialization type for the payment method. - # @!attribute instrument_id - # @return [String] (response-only) The ID of the Bacs instrument used for the payment. - # @!attribute account_holder - # @return [Hash] The account holder details (`type`, `first_name`, `last_name`, - # `company_name`, `email`). - # @!attribute account_number - # @return [String] The account number of the Bacs Direct Debit account. - # @!attribute bank_code - # @return [String] The bank code (sort code) of the Bacs Direct Debit account. - # @!attribute country - # @return [String] {CheckoutSdk::Common::Country} - # @!attribute currency - # @return [String] {CheckoutSdk::Common::Currency} - # @!attribute allow_partial_match - # @return [TrueClass, FalseClass] Whether to allow a partial match of the account holder details. + # The Bacs payment method's details and configuration on a Payment Setup. Maps swagger `Bacs`. + # Sent under the `payment_methods.bacs` key. class BacsPaymentMethod - attr_accessor :status, - :flags, - :initialization, - :instrument_id, - :account_holder, - :account_number, - :bank_code, - :country, - :currency, - :allow_partial_match + # The payment method status. + # [Optional] + # Read only. + # @return [String] One of {PaymentSetupPaymentMethodStatus}. + attr_accessor :status + + # The list of error codes or indicators that highlight missing or invalid information. + # [Optional] + # Read only. + # @return [Array(String)] + attr_accessor :flags + + # The initialization state of the payment method. + # When you create a Payment Setup, this defaults to `disabled`. + # On update the API replaces the payment methods, so send `enabled` again to keep the method enabled. + # The specification does not state this; it is the API's behaviour. + # [Optional] + # Default: disabled + # @return [String] One of {PaymentSetupPaymentMethodInitialization}. + attr_accessor :initialization + + # The ID of the Bacs instrument used for the payment. + # [Optional] + # Read only. + # @return [String] + attr_accessor :instrument_id + + # The account holder details. + # [Optional] + # Keys: + # - `type` (String): The type of account holder. Enum: "individual" "corporate". + # - `first_name` (String): The first name of the account holder. + # - `last_name` (String): The last name of the account holder. + # - `company_name` (String): The legal name of a registered company that holds the account. + # - `email` (String): The email address of the account holder. + # @return [Hash] + attr_accessor :account_holder + + # The account number of the Bacs Direct Debit account. + # [Optional] + # Write only. + # @return [String] + attr_accessor :account_number + + # The sort code of the Bacs Direct Debit account. + # [Optional] + # Write only. + # @return [String] + attr_accessor :bank_code + + # The account's country, as an ISO 3166-1 alpha-2 code. + # [Optional] + # min 2 characters, max 2 characters + # @return [String] {CheckoutSdk::Common::Country} + attr_accessor :country + + # The account holder's account currency. + # [Optional] + # @return [String] {CheckoutSdk::Common::Currency} + attr_accessor :currency + + # Indicates whether the Bacs instrument is created when account validation returns a partial + # match. When `true`, the instrument is created on a partial match; when `false`, instrument + # creation fails on a partial match. + # [Optional] + # Default: false + # @return [TrueClass, FalseClass] + attr_accessor :allow_partial_match end end end diff --git a/lib/checkout_sdk/payments/setups/cash_app_payment_method.rb b/lib/checkout_sdk/payments/setups/cash_app_payment_method.rb index a0938f6..cc9cdcc 100644 --- a/lib/checkout_sdk/payments/setups/cash_app_payment_method.rb +++ b/lib/checkout_sdk/payments/setups/cash_app_payment_method.rb @@ -18,7 +18,6 @@ class CashAppPaymentMethod # The payment method status. # [Optional] # Read only. - # Enum: "unavailable" "action_required" "ready" "initialization_required" "invalid" # @return [String] One of {PaymentSetupPaymentMethodStatus}. attr_accessor :status @@ -30,9 +29,10 @@ class CashAppPaymentMethod # The initialization state of the payment method. # When you create a Payment Setup, this defaults to `disabled`. + # On update the API replaces the payment methods, so send `enabled` again to keep the method enabled. + # The specification does not state this; it is the API's behaviour. # [Optional] - # Default: "disabled" - # Enum: "disabled" "enabled" + # Default: disabled # @return [String] One of {PaymentSetupPaymentMethodInitialization}. attr_accessor :initialization diff --git a/lib/checkout_sdk/payments/setups/payment_setup_payment_type.rb b/lib/checkout_sdk/payments/setups/payment_setup_payment_type.rb new file mode 100644 index 0000000..8f36f47 --- /dev/null +++ b/lib/checkout_sdk/payments/setups/payment_setup_payment_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module CheckoutSdk + module Payments + # The type of payment on a Payment Setup. Maps swagger `PaymentSetup.payment_type`. + # + # The Payment Setups API declares these values in lowercase, unlike {PaymentType}, which is the + # capitalized enum of the payments API. Use this module for the :payment_type key of a Payment + # Setup request. When :payment_type is not sent, the API uses "regular". + module PaymentSetupPaymentType + REGULAR = 'regular' + RECURRING = 'recurring' + MOTO = 'moto' + INSTALLMENT = 'installment' + PAY_LATER = 'pay_later' + UNSCHEDULED = 'unscheduled' + end + end +end diff --git a/lib/checkout_sdk/payments/setups/payment_setups_client.rb b/lib/checkout_sdk/payments/setups/payment_setups_client.rb index d451fa4..791836f 100644 --- a/lib/checkout_sdk/payments/setups/payment_setups_client.rb +++ b/lib/checkout_sdk/payments/setups/payment_setups_client.rb @@ -32,6 +32,11 @@ def initialize(api_client, configuration) # {PaymentSetupRisk}), convert it first with # CheckoutSdk::JsonSerializer.to_custom_hash(object); a typed object nested directly in the # Hash is not serialized and is sent as "#". + # :payment_type - String, the type of payment, one of {PaymentSetupPaymentType} (lowercase, + # unlike {PaymentType}): "regular", "recurring", "moto", "installment", "pay_later" or + # "unscheduled". Default "regular". You must provide it for card payments in which the + # cardholder is not present, for example a recurring payment or a mail order/telephone order + # (MOTO) payment. # :payment_methods may include :cashapp (the key is the single lowercase word `cashapp`), a Hash # with the keys documented on {CashAppPaymentMethod}; you send :initialization ("disabled" or # "enabled") and :customer_profile_sharing (Boolean). @@ -128,6 +133,11 @@ def create_payment_setup(payment_setups_request) # {PaymentSetupRisk}), convert it first with # CheckoutSdk::JsonSerializer.to_custom_hash(object); a typed object nested directly in the # Hash is not serialized and is sent as "#". + # :payment_type - String, the type of payment, one of {PaymentSetupPaymentType} (lowercase, + # unlike {PaymentType}): "regular", "recurring", "moto", "installment", "pay_later" or + # "unscheduled". Default "regular". You must provide it for card payments in which the + # cardholder is not present, for example a recurring payment or a mail order/telephone order + # (MOTO) payment. # :payment_methods may include :cashapp (the key is the single lowercase word `cashapp`), a Hash # with the keys documented on {CashAppPaymentMethod}; you send :initialization ("disabled" or # "enabled") and :customer_profile_sharing (Boolean). diff --git a/spec/checkout_sdk/payments/setups/payment_setup_payment_type_serialization_spec.rb b/spec/checkout_sdk/payments/setups/payment_setup_payment_type_serialization_spec.rb new file mode 100644 index 0000000..23c8881 --- /dev/null +++ b/spec/checkout_sdk/payments/setups/payment_setup_payment_type_serialization_spec.rb @@ -0,0 +1,289 @@ +# frozen_string_literal: true + +RSpec.describe 'Payment Setups payment_type and account funding transaction serialization' do + let(:api_client) do + configuration = double('CheckoutConfiguration', + http_client: Faraday.new, + multipart_http_client: Faraday.new, + logger: Logger.new(nil)) + CheckoutSdk::ApiClient.new(configuration, 'https://api.sandbox.checkout.com') + end + + def wire(request) + JSON.parse(CheckoutSdk::JsonSerializer.to_custom_hash(request).to_json) + end + + def parse_json(body) + response = double('Response', status: 200, body: body, headers: { 'Content-Type' => 'application/json' }) + api_client.send(:parse_response, response) + end + + def constant_values(mod) + mod.constants.map { |c| mod.const_get(c) } + end + + describe CheckoutSdk::Payments::PaymentSetupPaymentType do + it 'holds exactly the six lowercase values of the spec' do + expect(constant_values(described_class)) + .to contain_exactly('regular', 'recurring', 'moto', 'installment', 'pay_later', 'unscheduled') + end + + { + REGULAR: 'regular', + RECURRING: 'recurring', + MOTO: 'moto', + INSTALLMENT: 'installment', + PAY_LATER: 'pay_later', + UNSCHEDULED: 'unscheduled' + }.each do |constant, value| + it "sends #{constant} as \"#{value}\" under the payment_type key" do + request = { amount: 1000, currency: 'GBP', payment_type: described_class.const_get(constant) } + + expect(wire(request)).to eq({ 'amount' => 1000, 'currency' => 'GBP', 'payment_type' => value }) + end + end + + it 'is passed unchanged to the API on create and update' do + api_client_mock = double('api_client') + configuration_mock = double('configuration', credentials: double('credentials', get_authorization: 'sk')) + client = CheckoutSdk::Payments::PaymentSetupsClient.new(api_client_mock, configuration_mock) + request = { payment_type: described_class::PAY_LATER } + + expect(api_client_mock).to receive(:invoke_post).with('payments/setups', 'sk', request) + expect(api_client_mock).to receive(:invoke_put).with('payments/setups/ps_1', 'sk', request) + + client.create_payment_setup(request) + client.update_payment_setup('ps_1', request) + expect(wire(request)).to eq({ 'payment_type' => 'pay_later' }) + end + + it 'reads payment_type from the swagger example response' do + response = parse_json('{"id":"ps_2Un4WvKHNwAdrXDqTWOvGOgICcr","payment_type":"regular"}') + + expect(response.payment_type).to eq(described_class::REGULAR) + end + end + + describe CheckoutSdk::Payments::AccountFundingTransactionIdentificationType do + it 'holds exactly the three values of the spec' do + expect(constant_values(described_class)).to contain_exactly('passport', 'driving_license', 'national_id') + end + + it 'no longer defines TAX_ID and OTHER, which the API rejects' do + expect(described_class.const_defined?(:TAX_ID)).to be(false) + expect(described_class.const_defined?(:OTHER)).to be(false) + end + end + + describe CheckoutSdk::Payments::AccountFundingTransactionIdentification do + def identification + identification = described_class.new + identification.type = CheckoutSdk::Payments::AccountFundingTransactionIdentificationType::PASSPORT + identification.number = '1234567890' + identification.issuing_country = CheckoutSdk::Common::Country::US + identification + end + + it 'serializes every field to its swagger key' do + expect(wire(identification)).to eq({ 'type' => 'passport', 'number' => '1234567890', 'issuing_country' => 'US' }) + end + + it 'sends each identification type under sender.identification.type' do + %w[PASSPORT DRIVING_LICENSE NATIONAL_ID].each do |constant| + value = CheckoutSdk::Payments::AccountFundingTransactionIdentificationType.const_get(constant) + request = { account_funding_transaction: { sender: { identification: { type: value } } } } + + expect(wire(request).dig('account_funding_transaction', 'sender', 'identification', 'type')).to eq(value) + end + end + + it 'round trips every field through the API client' do + response = parse_json(CheckoutSdk::JsonSerializer.to_custom_hash(identification).to_json) + + expect(response.type).to eq('passport') + expect(response.number).to eq('1234567890') + expect(response.issuing_country).to eq('US') + end + + it 'reads the swagger example values' do + body = '{"account_funding_transaction":{"sender":{"identification":' \ + '{"type":"driving_license","number":"1234567890","issuing_country":"US"}}}}' + + read = parse_json(body).account_funding_transaction.sender.identification + + expect(read.type).to eq(CheckoutSdk::Payments::AccountFundingTransactionIdentificationType::DRIVING_LICENSE) + expect(read.number).to eq('1234567890') + expect(read.issuing_country).to eq('US') + end + end + + describe CheckoutSdk::Payments::AccountFundingTransactionSender do + def sender + sender = described_class.new + sender.reference = '0fc88634-78c1-4286-9cb6-6ca358730c8c' + sender.date_of_birth = '2000-01-01' + sender + end + + it 'serializes reference to the reference key' do + expect(wire(sender)).to eq({ 'reference' => '0fc88634-78c1-4286-9cb6-6ca358730c8c', + 'date_of_birth' => '2000-01-01' }) + end + + it 'sends reference under account_funding_transaction.sender.reference' do + aft = CheckoutSdk::Payments::PaymentSetupAccountFundingTransaction.new + aft.sender = sender + + expect(wire(aft).dig('sender', 'reference')).to eq('0fc88634-78c1-4286-9cb6-6ca358730c8c') + end + + it 'round trips reference through the API client' do + response = parse_json(CheckoutSdk::JsonSerializer.to_custom_hash(sender).to_json) + + expect(response.reference).to eq('0fc88634-78c1-4286-9cb6-6ca358730c8c') + expect(response.date_of_birth).to eq('2000-01-01') + end + + it 'reads reference from the swagger example' do + body = '{"account_funding_transaction":{"sender":{"date_of_birth":"2000-01-01T00:00:00.0000000",' \ + '"reference":"0fc88634-78c1-4286-9cb6-6ca358730c8c"}}}' + + read = parse_json(body).account_funding_transaction.sender + + expect(read.reference).to eq('0fc88634-78c1-4286-9cb6-6ca358730c8c') + expect(read.date_of_birth).to eq('2000-01-01T00:00:00.0000000') + end + + it 'round trips all eight attributes through the API client' do + address = CheckoutSdk::Common::Address.new + address.city = 'London' + identification = CheckoutSdk::Payments::AccountFundingTransactionIdentification.new + identification.type = CheckoutSdk::Payments::AccountFundingTransactionIdentificationType::PASSPORT + identification.number = '1234567890' + identification.issuing_country = CheckoutSdk::Common::Country::US + full = described_class.new + full.first_name = 'John' + full.last_name = 'Smith' + full.date_of_birth = '2000-01-01' + full.country_of_birth = CheckoutSdk::Common::Country::GB + full.nationality = CheckoutSdk::Common::Country::GB + full.address = address + full.reference = '0fc88634-78c1-4286-9cb6-6ca358730c8c' + full.identification = identification + + read = parse_json(CheckoutSdk::JsonSerializer.to_custom_hash(full).to_json) + + expect(read.first_name).to eq('John') + expect(read.last_name).to eq('Smith') + expect(read.date_of_birth).to eq('2000-01-01') + expect(read.country_of_birth).to eq('GB') + expect(read.nationality).to eq('GB') + expect(read.address.city).to eq('London') + expect(read.reference).to eq('0fc88634-78c1-4286-9cb6-6ca358730c8c') + expect(read.identification.type).to eq('passport') + expect(read.identification.number).to eq('1234567890') + expect(read.identification.issuing_country).to eq('US') + end + + it 'reads the full swagger example of the sender' do + body = '{"account_funding_transaction":{"sender":{"date_of_birth":"2000-01-01T00:00:00.0000000",' \ + '"reference":"0fc88634-78c1-4286-9cb6-6ca358730c8c",' \ + '"identification":{"number":"1234567890","issuing_country":"US"}}}}' + + read = parse_json(body).account_funding_transaction.sender + + expect(read.date_of_birth).to eq('2000-01-01T00:00:00.0000000') + expect(read.reference).to eq('0fc88634-78c1-4286-9cb6-6ca358730c8c') + expect(read.identification.number).to eq('1234567890') + expect(read.identification.issuing_country).to eq('US') + end + end + + describe CheckoutSdk::Payments::AccountFundingTransactionRecipient do + def recipient + address = CheckoutSdk::Common::Address.new + address.address_line1 = '10 Canterbury Road' + address.address_line2 = 'Flat 2' + address.city = 'London' + address.state = 'Greater London' + address.zip = 'NE1 1KO' + address.country = CheckoutSdk::Common::Country::GB + recipient = described_class.new + recipient.first_name = 'John' + recipient.last_name = 'Smith' + recipient.account_number = '5555554444' + recipient.account_type = 'checking' + recipient.date_of_birth = '2000-01-01' + recipient.address = address + recipient + end + + it 'serializes all six attributes to their swagger keys' do + expect(wire(recipient)).to eq({ 'first_name' => 'John', 'last_name' => 'Smith', + 'account_number' => '5555554444', 'account_type' => 'checking', + 'date_of_birth' => '2000-01-01', + 'address' => { 'address_line1' => '10 Canterbury Road', + 'address_line2' => 'Flat 2', 'city' => 'London', + 'state' => 'Greater London', 'zip' => 'NE1 1KO', + 'country' => 'GB' } }) + end + + it 'round trips all six attributes through the API client' do + read = parse_json(CheckoutSdk::JsonSerializer.to_custom_hash(recipient).to_json) + + expect(read.first_name).to eq('John') + expect(read.last_name).to eq('Smith') + expect(read.account_number).to eq('5555554444') + expect(read.account_type).to eq('checking') + expect(read.date_of_birth).to eq('2000-01-01') + expect(read.address.address_line1).to eq('10 Canterbury Road') + expect(read.address.address_line2).to eq('Flat 2') + expect(read.address.city).to eq('London') + expect(read.address.state).to eq('Greater London') + expect(read.address.zip).to eq('NE1 1KO') + expect(read.address.country).to eq('GB') + end + + it 'reads the swagger example values of the recipient' do + body = '{"account_funding_transaction":{"recipient":{"date_of_birth":"2000-01-01T00:00:00.0000000",' \ + '"address":{"address_line1":"10 Canterbury Road","address_line2":"Flat 2","city":"London",' \ + '"state":"Greater London","zip":"NE1 1KO","country":"GB"}}}}' + + read = parse_json(body).account_funding_transaction.recipient + + expect(read.date_of_birth).to eq('2000-01-01T00:00:00.0000000') + expect(read.address.address_line1).to eq('10 Canterbury Road') + expect(read.address.address_line2).to eq('Flat 2') + expect(read.address.city).to eq('London') + expect(read.address.state).to eq('Greater London') + expect(read.address.zip).to eq('NE1 1KO') + expect(read.address.country).to eq('GB') + end + end + + describe 'deprecated account funding transaction fields' do + it 'still serializes the deprecated sender fields under their keys, next to the spec fields' do + address = CheckoutSdk::Common::Address.new + address.city = 'London' + sender = CheckoutSdk::Payments::AccountFundingTransactionSender.new + sender.first_name = 'John' + sender.last_name = 'Smith' + sender.country_of_birth = CheckoutSdk::Common::Country::GB + sender.nationality = CheckoutSdk::Common::Country::GB + sender.address = address + sender.reference = 'sender-ref' + + expect(wire(sender)).to eq({ 'first_name' => 'John', 'last_name' => 'Smith', 'country_of_birth' => 'GB', + 'nationality' => 'GB', 'address' => { 'city' => 'London' }, + 'reference' => 'sender-ref' }) + end + + it 'still serializes the deprecated recipient account_type under its key, next to the spec fields' do + recipient = CheckoutSdk::Payments::AccountFundingTransactionRecipient.new + recipient.account_type = 'checking' + recipient.account_number = '5555554444' + + expect(wire(recipient)).to eq({ 'account_type' => 'checking', 'account_number' => '5555554444' }) + end + end +end diff --git a/spec/checkout_sdk/payments/setups/payment_setups_helper.rb b/spec/checkout_sdk/payments/setups/payment_setups_helper.rb index ca88064..ea48626 100644 --- a/spec/checkout_sdk/payments/setups/payment_setups_helper.rb +++ b/spec/checkout_sdk/payments/setups/payment_setups_helper.rb @@ -9,7 +9,7 @@ def create_payment_setup_request(**opts) processing_channel_id: ENV.fetch('CHECKOUT_PROCESSING_CHANNEL_ID'), amount: amount, currency: currency, - payment_type: 'Regular', + payment_type: CheckoutSdk::Payments::PaymentSetupPaymentType::REGULAR, reference: "TEST-REF-#{SecureRandom.uuid[0..5]}", description: 'Integration test payment setup', payment_methods: { @@ -65,7 +65,7 @@ def create_update_payment_setup_request(**opts) processing_channel_id: processing_channel_id, amount: amount, currency: currency, - payment_type: 'Regular', + payment_type: CheckoutSdk::Payments::PaymentSetupPaymentType::REGULAR, reference: "TEST-REF-#{SecureRandom.uuid[0..5]}", payment_methods: { klarna: { @@ -103,6 +103,26 @@ def create_update_payment_setup_request(**opts) request end + # Returns the first name in available_payment_methods whose payment_methods entry has the status + # ready, or nil when no payment method is ready to confirm. + def ready_payment_method_name(payment_setup) + Array(payment_setup.available_payment_methods).find do |name| + method = payment_setup.payment_methods&.[](name) + method&.status == CheckoutSdk::Payments::PaymentSetupPaymentMethodStatus::READY + end + end + + # Confirms the first available payment method of a setup that has none ready, and expects the + # API to answer 422 payment_method_not_ready. + def expect_payment_method_not_ready(id, payment_setup) + name = Array(payment_setup.available_payment_methods).first || 'card' + expect { default_sdk.payments_setups.confirm_payment_setup(id, name) } + .to raise_error(CheckoutSdk::CheckoutApiException) do |e| + expect(e.http_metadata.status_code).to eq(422) + expect(e.error_details[:error_codes]).to include('payment_method_not_ready') + end + end + def create_payment_setup(**opts) amount = opts.fetch(:amount, 1000) currency = opts.fetch(:currency, 'USD') diff --git a/spec/checkout_sdk/payments/setups/payment_setups_integration_spec.rb b/spec/checkout_sdk/payments/setups/payment_setups_integration_spec.rb index 42c4abe..175fd62 100644 --- a/spec/checkout_sdk/payments/setups/payment_setups_integration_spec.rb +++ b/spec/checkout_sdk/payments/setups/payment_setups_integration_spec.rb @@ -112,32 +112,25 @@ describe '.confirm_payment_setup' do context 'when confirming a payment setup' do - it 'confirms payment setup successfully' do - # Create a payment setup first + it 'confirms a ready payment method, or returns payment_method_not_ready when none is ready' do create_response = create_payment_setup(amount: 1000) - - # Get payment setup to obtain payment method options get_response = @api.payments_setups.get_payment_setup(create_response.id) - - # Skip if no payment method options are available (sandbox limitation) - skip 'No payment method options available in sandbox' if get_response.payment_method_options.nil? || get_response.payment_method_options.empty? - - # Get the first payment method option ID - payment_method_name = get_response.payment_method_options.first['type'] - - # Confirm the payment setup - response = @api.payments_setups.confirm_payment_setup(create_response.id, payment_method_name) - - assert_response(response, %w[id - action_id - amount - currency - status - processed_on - _links]) - expect(response.amount).to eq(1000) - expect(response.currency).to eq(CheckoutSdk::Common::Currency::USD) - expect(response.status).not_to be nil + payment_method_name = ready_payment_method_name(get_response) + + if payment_method_name.nil? + expect_payment_method_not_ready(create_response.id, get_response) + else + response = @api.payments_setups.confirm_payment_setup(create_response.id, payment_method_name) + + assert_response(response, %w[id + amount + currency + reference + payment_methods]) + expect(response.id).to eq(create_response.id) + expect(response.amount).to eq(1000) + expect(response.currency).to eq(CheckoutSdk::Common::Currency::USD) + end end end @@ -187,14 +180,15 @@ expect(get_response.id).to eq(create_response.id) expect(get_response.description).to eq('Updated in workflow test') - # Step 4: Attempt to confirm (might skip if no options available in sandbox) - unless get_response.payment_method_options.nil? || get_response.payment_method_options.empty? - payment_method_name = get_response.payment_method_options.first['type'] - + # Step 4: Confirm a ready payment method; with none ready the API returns payment_method_not_ready + payment_method_name = ready_payment_method_name(get_response) + if payment_method_name.nil? + expect_payment_method_not_ready(create_response.id, get_response) + else confirm_response = @api.payments_setups.confirm_payment_setup(create_response.id, payment_method_name) - expect(confirm_response.id).not_to be nil + expect(confirm_response.id).to eq(create_response.id) expect(confirm_response.amount).to eq(1000) - expect(confirm_response.status).not_to be nil + expect(confirm_response.payment_methods).not_to be nil end end end diff --git a/spec/checkout_sdk/payments/setups/setup_payment_methods_serialization_spec.rb b/spec/checkout_sdk/payments/setups/setup_payment_methods_serialization_spec.rb index 31c5b12..f01caf6 100644 --- a/spec/checkout_sdk/payments/setups/setup_payment_methods_serialization_spec.rb +++ b/spec/checkout_sdk/payments/setups/setup_payment_methods_serialization_spec.rb @@ -28,6 +28,91 @@ expect(hash['currency']).to eq('GBP') expect(hash['allow_partial_match']).to be(true) end + + describe 'response parsing' do + let(:api_client) do + configuration = double('CheckoutConfiguration', + http_client: Faraday.new, + multipart_http_client: Faraday.new, + logger: Logger.new(nil)) + CheckoutSdk::ApiClient.new(configuration, 'https://api.sandbox.checkout.com') + end + + def parse_json(body) + response = double('Response', status: 200, body: body, headers: { 'Content-Type' => 'application/json' }) + api_client.send(:parse_response, response) + end + + it 'round trips every field through the API client' do + pm = described_class.new + pm.status = CheckoutSdk::Payments::PaymentSetupPaymentMethodStatus::READY + pm.flags = %w[flag_a] + pm.initialization = CheckoutSdk::Payments::PaymentSetupPaymentMethodInitialization::ENABLED + pm.instrument_id = 'src_wkq7552u245upl5h75x24554xy' + pm.account_holder = { 'type' => 'individual', 'first_name' => 'John', 'last_name' => 'Smith', + 'company_name' => 'Example Ltd', 'email' => 'john.smith@example.com' } + pm.account_number = '12345678' + pm.bank_code = '200000' + pm.country = CheckoutSdk::Common::Country::GB + pm.currency = CheckoutSdk::Common::Currency::GBP + pm.allow_partial_match = true + + read = parse_json(CheckoutSdk::JsonSerializer.to_custom_hash(pm).to_json) + + expect(read.status).to eq('ready') + expect(read.flags).to eq(%w[flag_a]) + expect(read.initialization).to eq('enabled') + expect(read.instrument_id).to eq('src_wkq7552u245upl5h75x24554xy') + expect(read.account_holder.type).to eq('individual') + expect(read.account_holder.first_name).to eq('John') + expect(read.account_holder.last_name).to eq('Smith') + expect(read.account_holder.company_name).to eq('Example Ltd') + expect(read.account_holder.email).to eq('john.smith@example.com') + expect(read.account_number).to eq('12345678') + expect(read.bank_code).to eq('200000') + expect(read.country).to eq('GB') + expect(read.currency).to eq('GBP') + expect(read.allow_partial_match).to be(true) + end + + it 'reads the swagger example values from a Payment Setup response' do + body = '{"payment_methods":{"bacs":{"instrument_id":"src_wkq7552u245upl5h75x24554xy",' \ + '"account_holder":{"email":"john.smith@example.com"}}}}' + + read = parse_json(body).payment_methods.bacs + + expect(read.instrument_id).to eq('src_wkq7552u245upl5h75x24554xy') + expect(read.account_holder.email).to eq('john.smith@example.com') + end + end + end + + describe 'BacsPaymentMethod initialization' do + it 'sends each initialization constant under the initialization key' do + { 'DISABLED' => 'disabled', 'ENABLED' => 'enabled' }.each do |constant, value| + pm = CheckoutSdk::Payments::BacsPaymentMethod.new + pm.initialization = CheckoutSdk::Payments::PaymentSetupPaymentMethodInitialization.const_get(constant) + + expect(CheckoutSdk::JsonSerializer.to_custom_hash(pm)).to eq({ 'initialization' => value }) + end + end + + it 'does not send initialization when it is not set, leaving the API default' do + pm = CheckoutSdk::Payments::BacsPaymentMethod.new + pm.account_number = '12345678' + + expect(CheckoutSdk::JsonSerializer.to_custom_hash(pm)).to eq({ 'account_number' => '12345678' }) + end + + it 'is sent under the literal bacs key' do + pm = CheckoutSdk::Payments::BacsPaymentMethod.new + pm.initialization = CheckoutSdk::Payments::PaymentSetupPaymentMethodInitialization::ENABLED + request = { payment_methods: { bacs: CheckoutSdk::JsonSerializer.to_custom_hash(pm) } } + + json = CheckoutSdk::JsonSerializer.to_custom_hash(request).to_json + + expect(json).to eq('{"payment_methods":{"bacs":{"initialization":"enabled"}}}') + end end describe CheckoutSdk::Payments::CardPresentPaymentMethod do @@ -53,6 +138,16 @@ expect(hash['store_for_future_use']).to be(true) expect(hash['name']).to eq('John Smith') end + + it 'is sent unchanged under the literal card_present key' do + pm = described_class.new + pm.store_for_future_use = true + request = { payment_methods: { card_present: CheckoutSdk::JsonSerializer.to_custom_hash(pm) } } + + json = CheckoutSdk::JsonSerializer.to_custom_hash(request).to_json + + expect(json).to eq('{"payment_methods":{"card_present":{"store_for_future_use":true}}}') + end end describe CheckoutSdk::Payments::PayByBankPaymentMethod do