Skip to content

Commit

Permalink
Worldpay: Support AFTs (#5154)
Browse files Browse the repository at this point in the history
Account Funding Transactions, which Worldpay refers to as "Pull from
Card"
https://staging-developer.fisglobal.com/apis/wpg/manage/pull-from-card

Remote: (10 unrelated failures on master)
110 tests, 443 assertions, 10 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
90.9091% passed

Unit:
124 tests, 698 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

ECS-3554
  • Loading branch information
curiousepic committed Jun 28, 2024
1 parent 819907d commit 17b9ff8
Show file tree
Hide file tree
Showing 3 changed files with 371 additions and 1 deletion.
69 changes: 68 additions & 1 deletion lib/active_merchant/billing/gateways/worldpay.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -148,7 +150,8 @@ def scrub(transcript)
gsub(%r((<cardNumber>)\d+(</cardNumber>)), '\1[FILTERED]\2').
gsub(%r((<cvc>)[^<]+(</cvc>)), '\1[FILTERED]\2').
gsub(%r((<tokenNumber>)\d+(</tokenNumber>)), '\1[FILTERED]\2').
gsub(%r((<cryptogram>)[^<]+(</cryptogram>)), '\1[FILTERED]\2')
gsub(%r((<cryptogram>)[^<]+(</cryptogram>)), '\1[FILTERED]\2').
gsub(%r((<accountReference accountType="\w+">)\d+(<\/accountReference>)), '\1[FILTERED]\2')
end

private
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions test/remote/gateways/remote_worldpay_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
Loading

0 comments on commit 17b9ff8

Please sign in to comment.