diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb index 1eac5a69b0d..48d938acd6b 100644 --- a/lib/active_merchant/billing/gateways/worldpay.rb +++ b/lib/active_merchant/billing/gateways/worldpay.rb @@ -111,6 +111,8 @@ def credit(money, payment_method, options = {}) payment_details = payment_details(payment_method, options) if options[:fast_fund_credit] fast_fund_credit_request(money, payment_method, payment_details.merge(credit: true, **options)) + elsif options[:account_funding_transaction] + aft_request(money, payment_method, payment_details.merge(**options)) else credit_request(money, payment_method, payment_details.merge(credit: true, **options)) end @@ -148,7 +150,8 @@ def scrub(transcript) gsub(%r(()\d+()), '\1[FILTERED]\2'). gsub(%r(()[^<]+()), '\1[FILTERED]\2'). gsub(%r(()\d+()), '\1[FILTERED]\2'). - gsub(%r(()[^<]+()), '\1[FILTERED]\2') + gsub(%r(()[^<]+()), '\1[FILTERED]\2'). + gsub(%r(()\d+(<\/accountReference>)), '\1[FILTERED]\2') end private @@ -189,6 +192,10 @@ def fast_fund_credit_request(money, payment_method, options) commit('fast_credit', build_fast_fund_credit_request(money, payment_method, options), :ok, 'PUSH_APPROVED', options) end + def aft_request(money, payment_method, options) + commit('funding_transfer_transaction', build_aft_request(money, payment_method, options), :ok, 'AUTHORISED', options) + end + def store_request(credit_card, options) commit('store', build_store_request(credit_card, options), options) end @@ -400,6 +407,66 @@ def build_fast_fund_credit_request(money, payment_method, options) end end + def build_aft_request(money, payment_method, options) + build_request do |xml| + xml.submit do + xml.order order_tag_attributes(options) do + xml.description(options[:description].blank? ? 'Account Funding Transaction' : options[:description]) + add_amount(xml, money, options) + add_order_content(xml, options) + add_payment_method(xml, money, payment_method, options) + add_shopper(xml, options) + add_sub_merchant_data(xml, options[:sub_merchant_data]) if options[:sub_merchant_data] + add_aft_data(xml, payment_method, options) + end + end + end + end + + def add_aft_data(xml, payment_method, options) + xml.fundingTransfer 'type' => options[:aft_type], 'category' => 'PULL_FROM_CARD' do + xml.paymentPurpose options[:aft_payment_purpose] # Must be included for the recipient for following countries, otherwise optional: Argentina, Bangladesh, Chile, Columbia, Jordan, Mexico, Thailand, UAE, India cross-border + xml.fundingParty 'type' => 'sender' do + xml.accountReference options[:aft_sender_account_reference], 'accountType' => options[:aft_sender_account_type] + xml.fullName do + xml.first options.dig(:aft_sender_full_name, :first) + xml.middle options.dig(:aft_sender_full_name, :middle) + xml.last options.dig(:aft_sender_full_name, :last) + end + xml.fundingAddress do + xml.address1 options.dig(:aft_sender_funding_address, :address1) + xml.address2 options.dig(:aft_sender_funding_address, :address2) + xml.postalCode options.dig(:aft_sender_funding_address, :postal_code) + xml.city options.dig(:aft_sender_funding_address, :city) + xml.state options.dig(:aft_sender_funding_address, :state) + xml.countryCode options.dig(:aft_sender_funding_address, :country_code) + end + end + xml.fundingParty 'type' => 'recipient' do + xml.accountReference options[:aft_recipient_account_reference], 'accountType' => options[:aft_recipient_account_type] + xml.fullName do + xml.first options.dig(:aft_recipient_full_name, :first) + xml.middle options.dig(:aft_recipient_full_name, :middle) + xml.last options.dig(:aft_recipient_full_name, :last) + end + xml.fundingAddress do + xml.address1 options.dig(:aft_recipient_funding_address, :address1) + xml.address2 options.dig(:aft_recipient_funding_address, :address2) + xml.postalCode options.dig(:aft_recipient_funding_address, :postal_code) + xml.city options.dig(:aft_recipient_funding_address, :city) + xml.state options.dig(:aft_recipient_funding_address, :state) + xml.countryCode options.dig(:aft_recipient_funding_address, :country_code) + end + if options[:aft_recipient_funding_data] + xml.fundingData do + add_date_element(xml, 'birthDate', options[:aft_recipient_funding_data][:birth_date]) + xml.telephoneNumber options.dig(:aft_recipient_funding_data, :telephone_number) + end + end + end + end + end + def add_payment_details_for_ff_credit(xml, payment_method, options) xml.paymentDetails do xml.tag! 'FF_DISBURSE-SSL' do diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb index 07540d478e7..f74e0372ea1 100644 --- a/test/remote/gateways/remote_worldpay_test.rb +++ b/test/remote/gateways/remote_worldpay_test.rb @@ -147,6 +147,50 @@ def setup transaction_id: '123456789', eci: '05' ) + + @aft_options = { + account_funding_transaction: true, + aft_type: 'A', + aft_payment_purpose: '01', + aft_sender_account_type: '02', + aft_sender_account_reference: '4111111111111112', + aft_sender_full_name: { + first: 'First', + middle: 'Middle', + last: 'Sender' + }, + aft_sender_funding_address: { + address1: '123 Sender St', + address2: 'Apt 1', + postal_code: '12345', + city: 'Senderville', + state: 'NC', + country_code: 'US' + }, + aft_recipient_account_type: '03', + aft_recipient_account_reference: '4111111111111111', + aft_recipient_full_name: { + first: 'First', + middle: 'Middle', + last: 'Recipient' + }, + aft_recipient_funding_address: { + address1: '123 Recipient St', + address2: 'Apt 1', + postal_code: '12345', + city: 'Recipientville', + state: 'NC', + country_code: 'US' + }, + aft_recipient_funding_data: { + telephone_number: '123456789', + birth_date: { + day_of_month: '01', + month: '01', + year: '1980' + } + } + } end def test_successful_purchase @@ -921,6 +965,34 @@ def test_successful_mastercard_credit_on_cft_gateway assert_equal 'SUCCESS', credit.message end + def test_successful_visa_account_funding_transfer + credit = @gateway.credit(@amount, @credit_card, @options.merge(@aft_options)) + assert_success credit + assert_equal 'SUCCESS', credit.message + end + + def test_successful_visa_account_funding_transfer_via_token + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + credit = @gateway.credit(@amount, store.authorization, @options.merge(@aft_options)) + assert_success credit + assert_equal 'SUCCESS', credit.message + end + + def test_failed_visa_account_funding_transfer + credit = @gateway.credit(@amount, credit_card('4111111111111111', name: 'REFUSED'), @options.merge(@aft_options)) + assert_failure credit + assert_equal 'REFUSED', credit.message + end + + def test_failed_visa_account_funding_transfer_acquirer_error + credit = @gateway.credit(@amount, credit_card('4111111111111111', name: 'ACQERROR'), @options.merge(@aft_options)) + assert_failure credit + assert_equal 'ACQUIRER ERROR', credit.message + assert_equal '20', credit.error_code + end + # These three fast_fund_credit tests are currently failing with the message: Disbursement transaction not supported # It seems that the current sandbox setup does not support testing this. diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb index 9a12ae35728..ae60b8d8d68 100644 --- a/test/unit/gateways/worldpay_test.rb +++ b/test/unit/gateways/worldpay_test.rb @@ -135,6 +135,50 @@ def setup }] } } + + @aft_options = { + account_funding_transaction: true, + aft_type: 'A', + aft_payment_purpose: '01', + aft_sender_account_type: '02', + aft_sender_account_reference: '4111111111111112', + aft_sender_full_name: { + first: 'First', + middle: 'Middle', + last: 'Sender' + }, + aft_sender_funding_address: { + address1: '123 Sender St', + address2: 'Apt 1', + postal_code: '12345', + city: 'Senderville', + state: 'NC', + country_code: 'US' + }, + aft_recipient_account_type: '03', + aft_recipient_account_reference: '4111111111111111', + aft_recipient_full_name: { + first: 'First', + middle: 'Middle', + last: 'Recipient' + }, + aft_recipient_funding_address: { + address1: '123 Recipient St', + address2: 'Apt 1', + postal_code: '12345', + city: 'Recipientville', + state: 'NC', + country_code: 'US' + }, + aft_recipient_funding_data: { + telephone_number: '123456789', + birth_date: { + day_of_month: '01', + month: '01', + year: '1980' + } + } + } end def test_payment_type_for_network_card @@ -698,6 +742,16 @@ def test_successful_mastercard_credit assert_equal 'f25257d251b81fb1fd9c210973c941ff', response.authorization end + def test_successful_visa_account_funding_transaction + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options.merge(@aft_options)) + end.check_request do |_endpoint, data, _headers| + assert_match(//, data) + end.respond_with(successful_visa_credit_response) + assert_success response + assert_equal '3d4187536044bd39ad6a289c4339c41c', response.authorization + end + def test_description stub_comms do @gateway.authorize(@amount, @credit_card, @options) @@ -1178,6 +1232,10 @@ def test_transcript_scrubbing_on_network_token assert_equal network_token_transcript_scrubbed, @gateway.scrub(network_token_transcript) end + def test_transcript_scrubbing_on_aft + assert_equal aft_transcript_scrubbed, @gateway.scrub(aft_transcript) + end + def test_3ds_version_1_request stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option(version: '1.0.2', xid: 'xid'))) @@ -2345,6 +2403,148 @@ def scrubbed_transcript TRANSCRIPT end + def aft_transcript + <<~TRANSCRIPT + + + + Account Funding Transaction + + + + 4111111111111111 + + + + Longbob Longsen + 123 + + + + wow@example.com + + + + + + + 01 + + 4111111111111112 + + First + Middle + Sender + + + 123 Sender St + Apt 1 + 12345 + Senderville + NC + US + + + + 4111111111111111 + + First + Middle + Recipient + + + 123 Recipient St + Apt 1 + 12345 + Recipientville + NC + US + + + + + + 123456789 + + + + + + + TRANSCRIPT + end + + def aft_transcript_scrubbed + <<~TRANSCRIPT + + + + Account Funding Transaction + + + + [FILTERED] + + + + Longbob Longsen + [FILTERED] + + + + wow@example.com + + + + + + + 01 + + [FILTERED] + + First + Middle + Sender + + + 123 Sender St + Apt 1 + 12345 + Senderville + NC + US + + + + [FILTERED] + + First + Middle + Recipient + + + 123 Recipient St + Apt 1 + 12345 + Recipientville + NC + US + + + + + + 123456789 + + + + + + + TRANSCRIPT + end + def network_token_transcript <<~RESPONSE @@ -2472,4 +2672,35 @@ def failed_store_response RESPONSE end + + def successful_aft_response + <<~RESPONSE + + + + + + + VISA_CREDIT-SSL + + AUTHORISED + + + + N/A + + + + 4111********1111 + + + 060720116005062 + + + + + + + RESPONSE + end end