Skip to content

Commit

Permalink
Shift4_v2: Inherit securionPay API to enable Shift4v2
Browse files Browse the repository at this point in the history
Description
-------------------------
[SER-653](https://spreedly.atlassian.net/browse/SER-653)
[SER-654](https://spreedly.atlassian.net/browse/SER-654)
[SER-655](https://spreedly.atlassian.net/browse/SER-655)
[SER-662](https://spreedly.atlassian.net/browse/SER-662)

Shift4 purchased Securion Pay and is now using their API, that's why
this commit enable a new shift4_v2 gateway

Unit test
-------------------------
Finished in 0.150258 seconds.
34 tests, 191 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

Remote test
-------------------------
Finished in 28.137188 seconds.
30 tests, 103 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

Rubocop
-------------------------
760 files inspected, no offenses detected
  • Loading branch information
Heavyblade committed Aug 22, 2023
1 parent d8af5bf commit d6c61de
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 13 deletions.
4 changes: 2 additions & 2 deletions lib/active_merchant/billing/gateways/securion_pay.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ def api_request(endpoint, parameters = nil, options = {}, method = nil)
response
end

def json_error(raw_response)
msg = 'Invalid response received from the SecurionPay API.'
def json_error(raw_response, gateway_name = "SecurionPay")
msg = "Invalid response received from the #{gateway_name} API."
msg += " (The raw response returned by the API was #{raw_response.inspect})"
{
'error' => {
Expand Down
53 changes: 53 additions & 0 deletions lib/active_merchant/billing/gateways/shift4_v2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class Shift4V2Gateway < SecurionPayGateway
# same endpont for testing
self.live_url = 'https://api.shift4.com/'
self.display_name = 'Shift4 V2'
self.homepage_url = 'https://dev.shift4.com/us/'

def credit(money, payment, options = {})
post = create_post_for_auth_or_purchase(money, payment, options)
commit('credits', post, options)
end

def create_post_for_auth_or_purchase(money, payment, options)
super.tap do |post|
add_stored_credentials(post, options)
end
end

def add_stored_credentials(post, options)
return unless options[:stored_credential].present?

initiator = options.dig(:stored_credential, :initiator)
reason_type = options.dig(:stored_credential, :reason_type)

post_type = {
['cardholder', 'recurring'] => 'first_recurring',
['merchant', 'recurring'] => 'subsequent_recurring',
['cardholder', 'unscheduled'] => 'customer_initiated',
['merchant', 'installment'] => 'merchant_initiated'
}[[initiator, reason_type]]
post[:type] = post_type if post_type
end

def headers(options = {})
super.tap do |headers|
headers['User-Agent'] = "Shift4/v2 ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
end
end

def scrub(transcript)
super.
gsub(%r((card\[expMonth\]=)\d+), '\1[FILTERED]').
gsub(%r((card\[expYear\]=)\d+), '\1[FILTERED]').
gsub(%r((card\[cardholderName\]=)\w+[^ ]\w+), '\1[FILTERED]')
end

def json_error(raw_response)
super(raw_response, 'Shift4 V2')
end
end
end
end
18 changes: 7 additions & 11 deletions test/remote/gateways/remote_securion_pay_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ def test_successful_store
assert_equal '4242', response.params['cards'][1]['last4']
end

# def test_dump_transcript
# skip("Transcript scrubbing for this gateway has been tested.")
# dump_transcript_and_fail(@gateway, @amount, @credit_card, @options)
# end

def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @credit_card, @options)
Expand Down Expand Up @@ -81,7 +76,7 @@ def test_successful_purchase_with_three_ds_data
def test_unsuccessful_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
assert_match %r{The card was declined for other reason.}, response.message
assert_match %r{The card was declined}, response.message
assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
end

Expand All @@ -104,7 +99,7 @@ def test_failed_authorize
def test_failed_capture
response = @gateway.capture(@amount, 'invalid_authorization_token')
assert_failure response
assert_match %r{Requested Charge does not exist}, response.message
assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end

def test_successful_full_refund
Expand All @@ -116,7 +111,7 @@ def test_successful_full_refund
assert_success refund

assert refund.params['refunded']
assert_equal 0, refund.params['amount']
assert_equal 2000, refund.params['refunds'].first['amount']
assert_equal 1, refund.params['refunds'].size
assert_equal @amount, refund.params['refunds'].map { |r| r['amount'] }.sum

Expand All @@ -130,6 +125,7 @@ def test_successful_partially_refund

first_refund = @gateway.refund(@refund_amount, purchase.authorization)
assert_success first_refund
assert_equal @refund_amount, first_refund.params['refunds'].first['amount']

second_refund = @gateway.refund(@refund_amount, purchase.authorization)
assert_success second_refund
Expand All @@ -143,7 +139,7 @@ def test_successful_partially_refund
def test_unsuccessful_authorize_refund
response = @gateway.refund(@amount, 'invalid_authorization_token')
assert_failure response
assert_match %r{Requested Charge does not exist}, response.message
assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end

def test_unsuccessful_refund
Expand Down Expand Up @@ -173,7 +169,7 @@ def test_successful_void
def test_failed_void
response = @gateway.void('invalid_authorization_token', @options)
assert_failure response
assert_match %r{Requested Charge does not exist}, response.message
assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end

def test_successful_verify
Expand All @@ -185,7 +181,7 @@ def test_successful_verify
def test_failed_verify
response = @gateway.verify(@declined_card, @options)
assert_failure response
assert_match %r{The card was declined for other reason.}, response.message
assert_match %r{The card was declined}, response.message
assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.primary_response.error_code
end

Expand Down
80 changes: 80 additions & 0 deletions test/remote/gateways/remote_shift4_v2_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require 'test_helper'
require_relative 'remote_securion_pay_test'

class RemoteShift4V2Test < RemoteSecurionPayTest
def setup
super
@gateway = Shift4V2Gateway.new(fixtures(:shift4_v2))
end

def test_successful_purchase_third_party_token
auth = @gateway.store(@credit_card, @options)
token = auth.params['defaultCardId']
customer_id = auth.params['id']
response = @gateway.purchase(@amount, token, @options.merge!(customer_id: customer_id))
assert_success response
assert_equal 'Transaction approved', response.message
assert_equal 'foo@example.com', response.params['metadata']['email']
assert_match CHARGE_ID_REGEX, response.authorization
end

def test_unsuccessful_purchase_third_party_token
auth = @gateway.store(@credit_card, @options)
customer_id = auth.params['id']
response = @gateway.purchase(@amount, @invalid_token, @options.merge!(customer_id: customer_id))
assert_failure response
assert_equal "Token 'tok_invalid' does not exist", response.message
end

def test_successful_stored_credentials_first_recurring
stored_credentials = {
initiator: 'cardholder',
reason_type: 'recurring'
}
@options.merge!(stored_credential: stored_credentials)
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction approved', response.message
assert_equal 'first_recurring', response.params['type']
assert_match CHARGE_ID_REGEX, response.authorization
end

def test_successful_stored_credentials_subsequent_recurring
stored_credentials = {
initiator: 'merchant',
reason_type: 'recurring'
}
@options.merge!(stored_credential: stored_credentials)
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction approved', response.message
assert_equal 'subsequent_recurring', response.params['type']
assert_match CHARGE_ID_REGEX, response.authorization
end

def test_successful_stored_credentials_customer_initiated
stored_credentials = {
initiator: 'cardholder',
reason_type: 'unscheduled'
}
@options.merge!(stored_credential: stored_credentials)
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction approved', response.message
assert_equal 'customer_initiated', response.params['type']
assert_match CHARGE_ID_REGEX, response.authorization
end

def test_successful_stored_credentials_merchant_initiated
stored_credentials = {
initiator: 'merchant',
reason_type: 'installment'
}
@options.merge!(stored_credential: stored_credentials)
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction approved', response.message
assert_equal 'merchant_initiated', response.params['type']
assert_match CHARGE_ID_REGEX, response.authorization
end
end
85 changes: 85 additions & 0 deletions test/unit/gateways/shift4_v2_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
require 'test_helper'
require_relative 'securion_pay_test'

class Shift4V2Test < SecurionPayTest
include CommStub

def setup
super
@gateway = Shift4V2Gateway.new(
secret_key: 'pr_test_random_key'
)
end

def test_invalid_raw_response
@gateway.expects(:ssl_request).returns(invalid_json_response)

response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_match(/^Invalid response received from the Shift4 V2 API/, response.message)
end

def test_ensure_does_not_respond_to_credit; end

private

def pre_scrubbed
<<-PRE_SCRUBBED
opening connection to api.shift4.com:443...
opened
starting SSL for api.shift4.com:443...
SSL established
<- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic cHJfdGVzdF9xWk40VlZJS0N5U2ZDZVhDQm9ITzlEQmU6\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.shift4.com\r\nContent-Length: 214\r\n\r\n"
<- "amount=2000&currency=usd&card[number]=4242424242424242&card[expMonth]=9&card[expYear]=2016&card[cvc]=123&card[cardholderName]=Longbob+Longsen&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com"
-> "HTTP/1.1 200 OK\r\n"
-> "Server: cloudflare-nginx\r\n"
-> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n"
-> "Content-Type: application/json;charset=UTF-8\r\n"
-> "Transfer-Encoding: chunked\r\n"
-> "Connection: close\r\n"
-> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n"
-> "CF-RAY: 1f58b1414ca00af6-WAW\r\n"
-> "\r\n"
-> "1f4\r\n"
reading 500 bytes...
-> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}"
read 500 bytes
reading 2 bytes...
-> "\r\n"
read 2 bytes
-> "0\r\n"
-> "\r\n"
Conn close
PRE_SCRUBBED
end

def post_scrubbed
<<-POST_SCRUBBED
opening connection to api.shift4.com:443...
opened
starting SSL for api.shift4.com:443...
SSL established
<- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.shift4.com\r\nContent-Length: 214\r\n\r\n"
<- "amount=2000&currency=usd&card[number]=[FILTERED]&card[expMonth]=[FILTERED]&card[expYear]=[FILTERED]&card[cvc]=[FILTERED]&card[cardholderName]=[FILTERED]&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com"
-> "HTTP/1.1 200 OK\r\n"
-> "Server: cloudflare-nginx\r\n"
-> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n"
-> "Content-Type: application/json;charset=UTF-8\r\n"
-> "Transfer-Encoding: chunked\r\n"
-> "Connection: close\r\n"
-> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n"
-> "CF-RAY: 1f58b1414ca00af6-WAW\r\n"
-> "\r\n"
-> "1f4\r\n"
reading 500 bytes...
-> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}"
read 500 bytes
reading 2 bytes...
-> "\r\n"
read 2 bytes
-> "0\r\n"
-> "\r\n"
Conn close
POST_SCRUBBED
end
end

0 comments on commit d6c61de

Please sign in to comment.