Skip to content

Commit

Permalink
Datatrans: Add TPV
Browse files Browse the repository at this point in the history
Summary:
_________________________
Include Store and Unstore Methods in datatrans
to support Third Party Token.

[SER-1395](https://spreedly.atlassian.net/browse/SER-1395)

Tests
_________________________

Remote Test:
-------------------------
Finished in 31.477035 seconds.
25 tests, 72 assertions, 0 failures, 0 errors, 0 pendings, 1 omissions, 0 notifications
100% passed

Unit Tests:
-------------------------
Finished in 0.115603 seconds.
29 tests, 165 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

Rubocop
-------------------------
798 files inspected, no offenses detected
  • Loading branch information
Gustavo Sanmartin authored and Gustavo Sanmartin committed Jul 17, 2024
1 parent 957dd75 commit cf1d53d
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 22 deletions.
75 changes: 60 additions & 15 deletions lib/active_merchant/billing/gateways/datatrans.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class DatatransGateway < Gateway
self.test_url = 'https://api.sandbox.datatrans.com/v1/transactions/'
self.live_url = 'https://api.datatrans.com/v1/transactions/'
self.test_url = 'https://api.sandbox.datatrans.com/v1/'
self.live_url = 'https://api.datatrans.com/v1/'

self.supported_countries = %w(CH GR US) # to confirm the countries supported.
self.default_currency = 'CHF'
Expand Down Expand Up @@ -72,6 +72,28 @@ def void(authorization, options = {})
commit('cancel', post, { transaction_id: transaction_id })
end

def store(payment_method, options = {})
exp_year = format(payment_method.year, :two_digits)
exp_month = format(payment_method.month, :two_digits)

post = {
requests: [
{
type: 'CARD',
pan: payment_method.number,
expiryMonth: exp_month,
expiryYear: exp_year
}
]
}
commit('tokenize', post, { expiry_month: exp_month, expiry_year: exp_year })
end

def unstore(authorization, options = {})
data_alias = authorization.split('|')[2]
commit('delete_alias', {}, { alias_id: data_alias }, :delete)
end

def supports_scrubbing?
true
end
Expand All @@ -86,10 +108,23 @@ def scrub(transcript)
private

def add_payment_method(post, payment_method)
card = build_card(payment_method)
card = {}
if payment_method.is_a? String
card = {
type: 'ALIAS',
alias: payment_method.split('|')[2]
}
exp_month = payment_method.split('|')[3]
exp_year = payment_method.split('|')[4]
else
card = build_card(payment_method)
exp_month = format(payment_method.month, :two_digits)
exp_year = format(payment_method.year, :two_digits)
end

post[:card] = {
expiryMonth: format(payment_method.month, :two_digits),
expiryYear: format(payment_method.year, :two_digits)
expiryMonth: exp_month,
expiryYear: exp_year
}.merge(card)
end

Expand Down Expand Up @@ -157,15 +192,15 @@ def add_currency_amount(post, money, options)
post[:amount] = amount(money)
end

def commit(action, post, options = {})
response = parse(ssl_post(url(action, options), post.to_json, headers))
def commit(action, post, options = {}, method = :post)
response = parse(ssl_request(method, url(action, options), post.to_json, headers))
succeeded = success_from(action, response)

Response.new(
succeeded,
message_from(succeeded, response),
response,
authorization: authorization_from(response),
authorization: authorization_from(response, action, options),
test: test?,
error_code: error_code_from(response)
)
Expand Down Expand Up @@ -196,26 +231,36 @@ def headers
def url(endpoint, options = {})
case endpoint
when 'settle', 'credit', 'cancel'
"#{test? ? test_url : live_url}#{options[:transaction_id]}/#{endpoint}"
"#{test? ? test_url : live_url}transactions/#{options[:transaction_id]}/#{endpoint}"
when 'tokenize'
"#{test? ? test_url : live_url}aliases/#{endpoint}"
when 'delete_alias'
"#{test? ? test_url : live_url}aliases/#{options[:alias_id]}"
else
"#{test? ? test_url : live_url}#{endpoint}"
"#{test? ? test_url : live_url}transactions/#{endpoint}"
end
end

def success_from(action, response)
case action
when 'authorize', 'credit'
true if response.include?('transactionId') && response.include?('acquirerAuthorizationCode')
response.include?('transactionId') && response.include?('acquirerAuthorizationCode')
when 'settle', 'cancel'
true if response.dig('response_code') == 204
response.dig('response_code') == 204
when 'tokenize'
response['responses'][0].include?('alias') && response['overview']['failed'] == 0
when 'delete_alias'
response.dig('response_code') == 204
else
false
end
end

def authorization_from(response)
auth = [response['transactionId'], response['acquirerAuthorizationCode']].join('|')
return auth unless auth == '|'
def authorization_from(response, action, options)
token_array = [response.dig('responses', 0, 'alias'), options[:expiry_month], options[:expiry_year]] if action == 'tokenize'

auth = [response['transactionId'], response['acquirerAuthorizationCode'], token_array].join('|')
return auth unless auth == '||'
end

def message_from(succeeded, response)
Expand Down
30 changes: 29 additions & 1 deletion test/remote/gateways/remote_datatrans_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def setup
@gateway = DatatransGateway.new(fixtures(:datatrans))

@amount = 756
@credit_card = credit_card('4242424242424242', verification_value: '123', first_name: 'John', last_name: 'Smith', month: 6, year: 2025)
@credit_card = credit_card('4242424242424242', verification_value: '123', first_name: 'John', last_name: 'Smith', month: 6, year: Time.now.year + 1)
@bad_amount = 100000 # anything grather than 500 EUR
@credit_card_frictionless = credit_card('4000001000000018', verification_value: '123', first_name: 'John', last_name: 'Smith', month: 6, year: 2025)

Expand Down Expand Up @@ -183,6 +183,34 @@ def test_successful_void
assert_equal response.authorization, nil
end

def test_successful_store_purchase_unstore_flow
store = @gateway.store(@credit_card, @options)
assert_success store
assert_include store.params, 'overview'
assert_equal store.params['overview'], { 'total' => 1, 'successful' => 1, 'failed' => 0 }
assert store.params['responses'].is_a?(Array)
assert_include store.params['responses'][0], 'alias'
assert_equal store.params['responses'][0]['maskedCC'], '424242xxxxxx4242'
assert_include store.params['responses'][0], 'fingerprint'

purchase = @gateway.purchase(@amount, store.authorization, @options)
assert_success purchase
assert_include purchase.params, 'transactionId'

# second purchase to validate multiple use token
second_purchase = @gateway.purchase(@amount, store.authorization, @options)
assert_success second_purchase

unstore = @gateway.unstore(store.authorization, @options)
assert_success unstore

# purchase after unstore to validate deletion
response = @gateway.purchase(@amount, store.authorization, @options)
assert_failure response
assert_equal response.error_code, 'INVALID_ALIAS'
assert_equal response.message, 'authorize.card.alias'
end

def test_failed_void_because_captured_transaction
omit("the transaction could take about 20 minutes to
pass from settle to transmited, use a previos
Expand Down
67 changes: 61 additions & 6 deletions test/unit/gateways/datatrans_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def setup
}
})

@transaction_reference = '240214093712238757|093712'
@transaction_reference = '240214093712238757|093712|123alias_token_id123|05|25'

@billing_address = address
@no_country_billing_address = address(country: nil)
Expand Down Expand Up @@ -219,6 +219,43 @@ def test_voids
assert_success response
end

def test_store
response = stub_comms(@gateway, :ssl_request) do
@gateway.store(@credit_card, @options)
end.check_request do |_action, endpoint, data, _headers|
assert_match('aliases/tokenize', endpoint)
parsed_data = JSON.parse(data)
request = parsed_data['requests'][0]
assert_equal('CARD', request['type'])
assert_equal(@credit_card.number, request['pan'])
end.respond_with(successful_store_response)

assert_success response
end

def test_unstore
response = stub_comms(@gateway, :ssl_request) do
@gateway.unstore(@transaction_reference, @options)
end.check_request do |_action, endpoint, data, _headers|
assert_match('aliases/123alias_token_id123', endpoint)
assert_equal data, '{}'
end.respond_with(successful_unstore_response)

assert_success response
end

def test_purchase_with_tpv
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @transaction_reference, @options)
end.check_request do |_action, endpoint, data, _headers|
parsed_data = JSON.parse(data)
common_assertions_authorize_purchase(endpoint, parsed_data)
assert_equal(@transaction_reference.split('|')[2], parsed_data['card']['alias'])
end.respond_with(successful_purchase_response)

assert_success response
end

def test_required_merchant_id_and_password
error = assert_raises ArgumentError do
DatatransGateway.new
Expand Down Expand Up @@ -274,7 +311,7 @@ def test_get_response_message_from_message_user

def test_url_generation_from_action
action = 'test'
assert_equal "#{@gateway.test_url}#{action}", @gateway.send(:url, action)
assert_equal "#{@gateway.test_url}transactions/#{action}", @gateway.send(:url, action)
end

def test_scrub
Expand All @@ -283,10 +320,14 @@ def test_scrub
end

def test_authorization_from
assert_equal '1234|9248', @gateway.send(:authorization_from, { 'transactionId' => '1234', 'acquirerAuthorizationCode' => '9248' })
assert_equal '1234|', @gateway.send(:authorization_from, { 'transactionId' => '1234' })
assert_equal '|9248', @gateway.send(:authorization_from, { 'acquirerAuthorizationCode' => '9248' })
assert_equal nil, @gateway.send(:authorization_from, {})
assert_equal '1234|9248|', @gateway.send(:authorization_from, { 'transactionId' => '1234', 'acquirerAuthorizationCode' => '9248' }, '', {})
assert_equal '1234||', @gateway.send(:authorization_from, { 'transactionId' => '1234' }, '', {})
assert_equal '|9248|', @gateway.send(:authorization_from, { 'acquirerAuthorizationCode' => '9248' }, '', {})
assert_equal nil, @gateway.send(:authorization_from, {}, '', {})
# tes for store
assert_equal '||any_alias|any_month|any_year', @gateway.send(:authorization_from, { 'responses' => [{ 'alias' => 'any_alias' }] }, 'tokenize', { expiry_month: 'any_month', expiry_year: 'any_year' })
# handle nil responses or missing keys
assert_equal '|||any_month|any_year', @gateway.send(:authorization_from, {}, 'tokenize', { expiry_month: 'any_month', expiry_year: 'any_year' })
end

def test_parse
Expand Down Expand Up @@ -314,6 +355,19 @@ def successful_capture_response
'{"response_code": 204}'
end

def successful_store_response
'{
"overview":{"total":1, "successful":1, "failed":0},
"responses":
[{
"type":"CARD",
"alias":"7LHXscqwAAEAAAGQvYQBwc5zIs52AGRs",
"maskedCC":"424242xxxxxx4242",
"fingerprint":"F-dSjBoCMOYxomP49vzhdOYE"
}]
}'
end

def common_assertions_authorize_purchase(endpoint, parsed_data)
assert_match('authorize', endpoint)
assert_equal(@options[:order_id], parsed_data['refno'])
Expand All @@ -324,6 +378,7 @@ def common_assertions_authorize_purchase(endpoint, parsed_data)
alias successful_purchase_response successful_authorize_response
alias successful_refund_response successful_authorize_response
alias successful_void_response successful_capture_response
alias successful_unstore_response successful_capture_response

def pre_scrubbed
<<~PRE_SCRUBBED
Expand Down

0 comments on commit cf1d53d

Please sign in to comment.