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