Skip to content

Commit

Permalink
Flex Charge: Add support for TPV store (#5120)
Browse files Browse the repository at this point in the history
* Flex Charge: Add support for TPV store

Test summary:
Local:
5898 tests, 79569 assertions, 0 failures, 17 errors, 0 pendings, 0 omissions, 0 notifications
99.7118% passed
Unit:
12 tests, 58 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
13 tests, 34 assertions, 1 failures, 0 errors, 0 pendings, 1 omissions, 0 notifications
91.6667% passed
  • Loading branch information
edgarv09 committed May 20, 2024
1 parent a74cbe1 commit 5278776
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 18 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
* Paymentez: Update success_from method for refunds [almalee24] #5116
* DataTrans: Add ThirdParty 3DS params [gasb150] #5118
* FlexCharge: Add ThirdParty 3DS params [javierpedrozaing] #5121

* FlexCharge: Add support for TPV store [edgarv09] #5120

== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
Expand Down
60 changes: 43 additions & 17 deletions lib/active_merchant/billing/gateways/flex_charge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class FlexChargeGateway < Gateway
authenticate: 'oauth2/token',
purchase: 'evaluate',
sync: 'outcome',
refund: 'orders/%s/refund'
refund: 'orders/%s/refund',
store: 'tokenize'
}

SUCCESS_MESSAGES = %w(APPROVED CHALLENGE SUBMITTED SUCCESS).freeze
Expand Down Expand Up @@ -44,6 +45,25 @@ def refund(money, authorization, options = {})
commit(:refund, { amountToRefund: (money.to_f / 100).round(2) }, authorization)
end

def store(credit_card, options = {})
address = options[:billing_address] || options[:address] || {}
first_name, last_name = address_names(address[:name], credit_card)

post = {
payment_method: {
credit_card: {
first_name: first_name,
last_name: last_name,
month: credit_card.month,
year: credit_card.year,
number: credit_card.number,
verification_value: credit_card.verification_value
}.compact
}
}
commit(:store, post)
end

def supports_scrubbing?
true
end
Expand Down Expand Up @@ -132,23 +152,29 @@ def add_invoice(post, money, credit_card, options)
avsResultCode: options[:avs_result_code],
cvvResultCode: options[:cvv_result_code],
cavvResultCode: options[:cavv_result_code],
cardNotPresent: credit_card.verification_value.blank?
cardNotPresent: credit_card.is_a?(String) ? false : credit_card.verification_value.blank?
}.compact
end

def add_payment_method(post, credit_card, address, options)
post[:paymentMethod] = {
holderName: credit_card.name,
cardType: 'CREDIT',
cardBrand: credit_card.brand&.upcase,
cardCountry: address[:country],
expirationMonth: credit_card.month,
expirationYear: credit_card.year,
cardBinNumber: credit_card.number[0..5],
cardLast4Digits: credit_card.number[-4..-1],
cardNumber: credit_card.number,
Token: false
}.compact
payment_method = case credit_card
when String
{ Token: true, cardNumber: credit_card }
else
{
holderName: credit_card.name,
cardType: 'CREDIT',
cardBrand: credit_card.brand&.upcase,
cardCountry: address[:country],
expirationMonth: credit_card.month,
expirationYear: credit_card.year,
cardBinNumber: credit_card.number[0..5],
cardLast4Digits: credit_card.number[-4..-1],
cardNumber: credit_card.number,
Token: false
}
end
post[:paymentMethod] = payment_method.compact
end

def address_names(address_name, payment_method)
Expand Down Expand Up @@ -222,7 +248,7 @@ def api_request(action, post, authorization = nil)
success_from(response),
message_from(response),
response,
authorization: authorization_from(response),
authorization: authorization_from(action, response),
test: test?,
error_code: error_code_from(response)
)
Expand All @@ -245,8 +271,8 @@ def message_from(response)
response[:title] || response[:responseMessage] || response[:status]
end

def authorization_from(response)
response[:orderSessionKey]
def authorization_from(action, response)
action == :store ? response.dig(:transaction, :payment_method, :token) : response[:orderSessionKey]
end

def error_code_from(response)
Expand Down
8 changes: 8 additions & 0 deletions test/remote/gateways/remote_flex_charge_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ def test_failed_fetch_access_token
assert_match(/400/, error.message)
end

def test_successful_purchase_with_token
store = @gateway.store(@credit_card_cit, {})
assert_success store

response = @gateway.purchase(@amount, store.authorization, @options)
assert_success response
end

def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @credit_card_cit, @cit_options)
Expand Down
87 changes: 87 additions & 0 deletions test/unit/gateways/flex_charge_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ def test_build_request_url_with_id_param
assert_equal @gateway.send(:url, action, id), "#{@gateway.test_url}orders/123/refund"
end

def test_build_request_url_for_store
action = :store
assert_equal @gateway.send(:url, action), "#{@gateway.test_url}tokenize"
end

def test_invalid_instance
error = assert_raises(ArgumentError) { FlexChargeGateway.new }
assert_equal 'Missing required parameter: app_key', error.message
Expand Down Expand Up @@ -194,6 +199,15 @@ def test_address_names_from_credit_card
assert_equal 'Doe', names.last
end

def test_successful_store
response = stub_comms do
@gateway.store(@credit_card, @options)
end.respond_with(successful_access_token_response, successful_store_response)

assert_success response
assert_equal 'd3e10716-6aac-4eb8-a74d-c1a3027f1d96', response.authorization
end

private

def pre_scrubbed
Expand Down Expand Up @@ -398,6 +412,79 @@ def successful_purchase_response
RESPONSE
end

def successful_store_response
<<~RESPONSE
{
"transaction": {
"on_test_gateway": true,
"created_at": "2024-05-14T13:44:25.3179186Z",
"updated_at": "2024-05-14T13:44:25.3179187Z",
"succeeded": true,
"state": null,
"token": null,
"transaction_type": null,
"order_id": null,
"ip": null,
"description": null,
"email": null,
"merchant_name_descriptor": null,
"merchant_location_descriptor": null,
"gateway_specific_fields": null,
"gateway_specific_response_fields": null,
"gateway_transaction_id": null,
"gateway_latency_ms": null,
"amount": 0,
"currency_code": null,
"retain_on_success": null,
"payment_method_added": false,
"message_key": null,
"message": null,
"response": null,
"payment_method": {
"token": "d3e10716-6aac-4eb8-a74d-c1a3027f1d96",
"created_at": "2024-05-14T13:44:25.3179205Z",
"updated_at": "2024-05-14T13:44:25.3179206Z",
"email": null,
"data": null,
"storage_state": null,
"test": false,
"metadata": null,
"last_four_digits": "1111",
"first_six_digits": "41111111",
"card_type": null,
"first_name": "Cure",
"last_name": "Tester",
"month": 9,
"year": 2025,
"address1": null,
"address2": null,
"city": null,
"state": null,
"zip": null,
"country": null,
"phone_number": null,
"company": null,
"full_name": null,
"payment_method_type": null,
"errors": null,
"fingerprint": null,
"verification_value": null,
"number": null
}
},
"cardBinInfo": null,
"success": true,
"result": null,
"status": null,
"statusCode": null,
"errors": [],
"customProperties": {},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwYmE4NGY2ZS03YTllLTQzZjEtYWU2ZC1jNTA4YjQ2NjQyNGEiLCJ1bmlxdWVfbmFtZSI6IjBiYTg0ZjZlLTdhOWUtNDNmMS1hZTZkLWM1MDhiNDY2NDI0YSIsImp0aSI6IjczZTVkOGZiLWYxMDMtNGVlYy1iYTAzLTM2MmY1YjA5MmNkMCIsImlhdCI6IjE3MTU2OTQyNjQ3MDMiLCJhdWQiOlsicGF5bWVudHMiLCJvcmRlcnMiLCJtZXJjaGFudHMiLCJlbGlnaWJpbGl0eS1zZnRwIiwiZWxpZ2liaWxpdHkiLCJjb250YWN0Il0sImN1c3RvbTptaWQiOiJkOWQwYjVmZC05NDMzLTQ0ZDMtODA1MS02M2ZlZTI4NzY4ZTgiLCJuYmYiOjE3MTU2OTQyNjQsImV4cCI6MTcxNTY5NDg2NCwiaXNzIjoiQXBpLUNsaWVudC1TZXJ2aWNlIn0.oB9xtWGthG6tcDie8Q3fXPc1fED8pBAlv8yZQuoiEkA",
"token_expires": 1715694864703
}
RESPONSE
end

def failed_purchase_response
<<~RESPONSE
{
Expand Down

0 comments on commit 5278776

Please sign in to comment.