tns_payments 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
data/.env.example ADDED
@@ -0,0 +1,4 @@
1
+ TNS_MERCHANT_ID=<MERCHANT_ID>
2
+ TNS_API_KEY=<API_KEY>
3
+ TNS_3DS_MERCHANT_ID=<3DS_MERCHANT_ID>
4
+ TNS_3DS_API_KEY=<3DS_API_KEY>
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ .env
@@ -1,4 +1,5 @@
1
1
  require 'base64'
2
+ require 'active_support/ordered_hash'
2
3
 
3
4
  module TNSPayments
4
5
  class Connection
@@ -11,7 +12,7 @@ module TNSPayments
11
12
  #
12
13
  # Returns a boolean.
13
14
  def available?
14
- request(:get, '/information', :authorization => false).success?
15
+ AvailableResponse.new(request(:get, '/information')).success?
15
16
  end
16
17
 
17
18
  def initialize options
@@ -34,16 +35,17 @@ module TNSPayments
34
35
  transaction = Transaction.new transaction
35
36
  order_id = transaction.order_id
36
37
  transaction_id = transaction.transaction_id
37
- params = {
38
- 'apiOperation' => 'PAY',
39
- 'cardDetails' => card_details(token),
40
- 'order' => {'reference' => transaction.reference},
41
- 'transaction' => {
42
- 'amount' => transaction.amount.to_s,
43
- 'currency' => transaction.currency,
44
- 'reference' => transaction.reference.to_s
45
- }
46
- }
38
+ order_params = ActiveSupport::OrderedHash.new
39
+ order_params['amount'] = transaction.amount.to_s
40
+ order_params['currency'] = transaction.currency
41
+ order_params['reference'] = transaction.reference
42
+ transaction_params = ActiveSupport::OrderedHash.new
43
+ transaction_params['reference'] = transaction.reference.to_s
44
+ params = ActiveSupport::OrderedHash.new
45
+ params['apiOperation'] = 'PAY'
46
+ params['order'] = order_params
47
+ params['transaction'] = transaction_params
48
+ params.merge!(source_of_funds(token))
47
49
 
48
50
  request :put, "/merchant/#{merchant_id}/order/#{order_id}/transaction/#{transaction_id}", params
49
51
  end
@@ -52,17 +54,16 @@ module TNSPayments
52
54
  #
53
55
  # Returns a Response object.
54
56
  def refund transaction
55
- transaction = Transaction.new transaction
56
- order_id = transaction.order_id.to_i
57
+ transaction = Transaction.new transaction
58
+ order_id = transaction.order_id.to_i
57
59
  transaction_id = transaction.transaction_id
58
- params = {
59
- 'apiOperation' => 'REFUND',
60
- 'transaction' => {
61
- 'amount' => transaction.amount.to_s,
62
- 'currency' => transaction.currency,
63
- 'reference' => transaction.reference.to_s
64
- }
65
- }
60
+ transaction_params = ActiveSupport::OrderedHash.new
61
+ transaction_params['amount'] = transaction.amount.to_s
62
+ transaction_params['currency'] = transaction.currency
63
+ transaction_params['reference'] = transaction.reference.to_s
64
+ params = ActiveSupport::OrderedHash.new
65
+ params['apiOperation'] = 'REFUND'
66
+ params['transaction'] = transaction_params
66
67
 
67
68
  request :put, "/merchant/#{merchant_id}/order/#{order_id}/transaction/#{transaction_id}", params
68
69
  end
@@ -71,9 +72,13 @@ module TNSPayments
71
72
  #
72
73
  # Returns a Response object.
73
74
  def create_credit_card_token token
74
- params = {
75
- 'cardDetails' => card_details(token)
76
- }
75
+ source_of_funds_params = ActiveSupport::OrderedHash.new
76
+ source_of_funds_params['type'] = 'CARD'
77
+ session_params = ActiveSupport::OrderedHash.new
78
+ session_params['id'] = token
79
+ params = ActiveSupport::OrderedHash.new
80
+ params['session'] = session_params
81
+ params['sourceOfFunds'] = source_of_funds_params
77
82
 
78
83
  request :post, "/merchant/#{merchant_id}/token", params
79
84
  end
@@ -92,35 +97,44 @@ module TNSPayments
92
97
  # Returns a Response object.
93
98
  def session_token
94
99
  @session_token ||= begin
95
- response = request :post, "/merchant/#{merchant_id}/session"
96
- if response.success?
97
- response.response.fetch('session')
100
+ result = request :post, "/merchant/#{merchant_id}/session"
101
+ if result.success?
102
+ result.response['session']['id']
98
103
  else
99
- raise SessionTokenException.new, response.explanation
104
+ raise SessionTokenException.new, result.explanation
100
105
  end
101
106
  end
102
107
  end
103
108
 
109
+ # Public: Request to update a session token with card data.
110
+ #
111
+ # Returns a response object.
112
+ def update_session_with_card(token, card)
113
+ source_of_funds_params = ActiveSupport::OrderedHash.new
114
+ source_of_funds_params['type'] = 'CARD'
115
+ source_of_funds_params['provided'] = {'card' => card}
116
+ params = {'sourceOfFunds' => source_of_funds_params}
117
+
118
+ UpdateSessionResponse.new(request(:put, "/merchant/#{merchant_id}/session/#{token}", params))
119
+ end
120
+
104
121
  # Public: Request to check a cardholder's enrollment in the 3DSecure scheme.
105
122
  #
106
123
  # Returns a Response object.
107
124
  def check_enrollment transaction, token, postback_url
108
- params = {
109
- '3DSecure' => {
110
- 'authenticationRedirect' => {
111
- 'pageGenerationMode' => 'CUSTOMIZED',
112
- 'responseUrl' => postback_url
113
- }
114
- },
115
- 'apiOperation' => 'CHECK_3DS_ENROLLMENT',
116
- 'cardDetails' => card_details(token),
117
- 'transaction' => {
118
- 'amount' => transaction.amount.to_s,
119
- 'currency' => transaction.currency
120
- }
121
- }
122
-
123
- request :put, "/merchant/#{merchant_id}/3DSecureId/#{transaction.three_domain_id}", params
125
+ redirect_params = ActiveSupport::OrderedHash.new
126
+ redirect_params['pageGenerationMode'] = 'CUSTOMIZED'
127
+ redirect_params['responseUrl'] = postback_url
128
+ order_params = ActiveSupport::OrderedHash.new
129
+ order_params['amount'] = transaction.amount.to_s
130
+ order_params['currency'] = transaction.currency
131
+ params = ActiveSupport::OrderedHash.new
132
+ params['apiOperation'] = 'CHECK_3DS_ENROLLMENT'
133
+ params['3DSecure'] = {'authenticationRedirect' => redirect_params}
134
+ params['session'] = {'id' => token}
135
+ params['order'] = order_params
136
+
137
+ ThreeDomainSecureResponse.new(request(:put, "/merchant/#{merchant_id}/3DSecureId/#{transaction.three_domain_id}", params))
124
138
  end
125
139
 
126
140
  # Public: Interprets the authentication response returned from the card
@@ -134,21 +148,29 @@ module TNSPayments
134
148
  'apiOperation' => 'PROCESS_ACS_RESULT'
135
149
  }
136
150
 
137
- request :post, "/merchant/#{merchant_id}/3DSecureId/#{three_domain_id}", params
151
+ ThreeDomainSecureResponse.new(request(:post, "/merchant/#{merchant_id}/3DSecureId/#{three_domain_id}", params))
138
152
  end
139
153
 
140
154
  private
141
155
 
142
- def card_details token
143
- {token_key(token) => token}
144
- end
156
+ def source_of_funds(token)
157
+ params = ActiveSupport::OrderedHash.new
158
+ funds = ActiveSupport::OrderedHash.new
159
+ funds['type'] = 'CARD'
160
+
161
+ if token =~ CREDIT_CARD_TOKEN_FORMAT
162
+ funds['token'] = token
163
+ params['sourceOfFunds'] = funds
164
+ else
165
+ params['sourceOfFunds'] = funds
166
+ params['session'] = {'id' => token}
167
+ end
145
168
 
146
- def token_key token
147
- token =~ CREDIT_CARD_TOKEN_FORMAT ? 'cardToken' : 'session'
169
+ params
148
170
  end
149
171
 
150
- def request method, path, options = {}
151
- Request.new(self, method, path, options).perform
172
+ def request(*args)
173
+ Request.new(self, *args).perform
152
174
  end
153
175
  end
154
176
  end
@@ -5,7 +5,7 @@ module TNSPayments
5
5
  class Request
6
6
  attr_accessor :connection, :method, :path, :payload
7
7
 
8
- def initialize connection, method, path, payload = {}
8
+ def initialize connection, method, path, payload = nil
9
9
  self.connection = connection
10
10
  self.method = method
11
11
  self.path = path
@@ -19,7 +19,7 @@ module TNSPayments
19
19
  headers = {}
20
20
  headers[:accept] = '*/*'
21
21
  headers[:content_type] = 'Application/json;charset=UTF-8'
22
- headers[:Authorization] = encode_credentials if authorization
22
+ headers[:Authorization] = encode_credentials
23
23
  headers
24
24
  end
25
25
 
@@ -30,17 +30,17 @@ module TNSPayments
30
30
  args = {
31
31
  :method => method,
32
32
  :url => url,
33
- :payload => payload.to_json,
34
33
  :headers => headers,
35
34
  :timeout => 120,
36
35
  :open_timeout => 30
37
36
  }
37
+ args[:payload] = payload.to_json if payload
38
38
 
39
39
  Response.new RestClient::Request.execute(args)
40
40
  rescue RestClient::Exception => e
41
41
  response = e.response
42
42
 
43
- if response.message == 'Request Timeout'
43
+ if e.is_a?(RestClient::RequestTimeout)
44
44
  response = JSON.generate({:error => {:cause => 'REQUEST_TIMEDOUT'}})
45
45
  end
46
46
 
@@ -49,16 +49,14 @@ module TNSPayments
49
49
 
50
50
  private
51
51
 
52
- def authorization
53
- payload.fetch(:authorization) { true }
54
- end
55
-
56
52
  def encode_credentials
57
- 'Basic ' + Base64.encode64(":#{connection.api_key}")
53
+ credentials = ["merchant.#{connection.merchant_id}:#{connection.api_key}"].pack("m*").delete("\n")
54
+
55
+ "Basic #{credentials}"
58
56
  end
59
57
 
60
58
  def url
61
- "#{connection.host}/api/rest/version/4#{path}"
59
+ "#{connection.host}/api/rest/version/32#{path}"
62
60
  end
63
61
  end
64
62
  end
@@ -1,3 +1,5 @@
1
+ require 'delegate'
2
+
1
3
  module TNSPayments
2
4
  class Response
3
5
  attr_reader :raw_response
@@ -17,13 +19,7 @@ module TNSPayments
17
19
  #
18
20
  # Returns a string.
19
21
  def result
20
- if response.has_key?('status')
21
- response['status'] == 'OPERATING' ? 'SUCCESS' : 'ERROR'
22
- elsif response.has_key?('3DSecure')
23
- 'SUCCESS'
24
- else
25
- response['result']
26
- end
22
+ response.fetch('result') { 'UNKNOWN' }
27
23
  end
28
24
 
29
25
  # Public: Check if the request was successful.
@@ -44,4 +40,28 @@ module TNSPayments
44
40
  end
45
41
  end
46
42
  end
43
+
44
+ class UpdateSessionResponse < SimpleDelegator
45
+ def success?
46
+ update_status == 'SUCCESS'
47
+ end
48
+
49
+ private
50
+
51
+ def update_status
52
+ response['session']['updateStatus']
53
+ end
54
+ end
55
+
56
+ class AvailableResponse < SimpleDelegator
57
+ def success?
58
+ response['status'] == 'OPERATING'
59
+ end
60
+ end
61
+
62
+ class ThreeDomainSecureResponse < SimpleDelegator
63
+ def success?
64
+ response.has_key?("3DSecure")
65
+ end
66
+ end
47
67
  end
@@ -0,0 +1,77 @@
1
+ # https://secure.ap.tnspayments.com/api/documentation/integrationGuidelines/supportedFeatures/testAndGoLive.html?locale=en_US
2
+ module TNSPayments
3
+ module TestSupport
4
+ class CardSession
5
+ SAMPLE_CARDS = {
6
+ :mastercard => {
7
+ :number => "5123456789012346",
8
+ :expiry => {
9
+ :month => "5",
10
+ :year => "17",
11
+ },
12
+ :securityCode => "123",
13
+ },
14
+ :amex => {
15
+ :number => "345678901234564",
16
+ :expiry => {
17
+ :month => "5",
18
+ :year => "17",
19
+ },
20
+ :securityCode => "1234",
21
+ },
22
+ :visa => {
23
+ :number => "4005550000000001",
24
+ :expiry => {
25
+ :month => "5",
26
+ :year => "17",
27
+ },
28
+ :securityCode => "123",
29
+ },
30
+ :visa_enrolled_3ds => {
31
+ :number => "4508750015741019",
32
+ :expiry => {
33
+ :month => "05",
34
+ :year => "17",
35
+ },
36
+ :securityCode => "123",
37
+ },
38
+ :visa_not_enrolled_3ds => {
39
+ :number => "4005550000000001",
40
+ :expiry => {
41
+ :month => "05",
42
+ :year => "17",
43
+ },
44
+ :securityCode => "123",
45
+ },
46
+ }
47
+
48
+ def self.sample_card(card_type = :visa)
49
+ SAMPLE_CARDS.fetch(card_type)
50
+ end
51
+
52
+ def self.sample(card_type = :visa, *args)
53
+ sessionize(sample_card(card_type), *args)
54
+ end
55
+
56
+ def self.sessionize(card, *args)
57
+ session = new(*args)
58
+ session.store(card)
59
+ session.token
60
+ end
61
+
62
+ def initialize(tns)
63
+ @tns = tns
64
+ end
65
+
66
+ def token
67
+ @token ||= @tns.session_token
68
+ end
69
+
70
+ def store(card)
71
+ response = @tns.update_session_with_card(token, card)
72
+ raise response.response.inspect unless response.success?
73
+ response
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,3 +1,3 @@
1
1
  module TNSPayments
2
- VERSION = "0.0.11"
2
+ VERSION = "0.0.12"
3
3
  end
@@ -0,0 +1,112 @@
1
+ ---
2
+ recorded_with: VCR 2.9.3
3
+ http_interactions:
4
+ - request:
5
+ method: post
6
+ uri: https://merchant.<TNS_MERCHANT_ID>:<TNS_API_KEY>@secure.ap.tnspayments.com/api/rest/version/32/merchant/<TNS_MERCHANT_ID>/session
7
+ body:
8
+ string: ""
9
+ headers:
10
+ Accept:
11
+ - "*/*"
12
+ Content-Type:
13
+ - Application/json;charset=UTF-8
14
+ Accept-Encoding:
15
+ - gzip, deflate
16
+ response:
17
+ status:
18
+ code: 201
19
+ message: Created
20
+ headers:
21
+ Content-Length:
22
+ - "149"
23
+ Content-Type:
24
+ - application/json;charset=UTF-8
25
+ Date:
26
+ - Mon, 11 Jan 2016 23:51:04 GMT
27
+ Pragma:
28
+ - no-cache
29
+ Expires:
30
+ - "0"
31
+ Set-Cookie:
32
+ - TS018aacab=01c9ad79a0777676190757f71bce1b77e026c4ec88a6e8088ae37b951e999fdf69ef2438b0; Path=/
33
+ Cache-Control:
34
+ - no-cache, no-store, must-revalidate
35
+ body:
36
+ string: "{\"merchant\":\"<TNS_MERCHANT_ID>\",\"result\":\"SUCCESS\",\"session\":{\"id\":\"SESSION0002414369440E9534413G26\",\"updateStatus\":\"NO_UPDATE\",\"version\":\"c1669f4301\"}}"
37
+ http_version:
38
+ recorded_at: Mon, 11 Jan 2016 23:51:05 GMT
39
+ - request:
40
+ method: put
41
+ uri: https://merchant.<TNS_MERCHANT_ID>:<TNS_API_KEY>@secure.ap.tnspayments.com/api/rest/version/32/merchant/<TNS_MERCHANT_ID>/session/SESSION0002414369440E9534413G26
42
+ body:
43
+ string: "{\"sourceOfFunds\":{\"type\":\"CARD\",\"provided\":{\"card\":{\"number\":\"345678901234564\",\"expiry\":{\"month\":\"5\",\"year\":\"17\"},\"securityCode\":\"1234\"}}}}"
44
+ headers:
45
+ Accept:
46
+ - "*/*"
47
+ Content-Length:
48
+ - "139"
49
+ Content-Type:
50
+ - Application/json;charset=UTF-8
51
+ Accept-Encoding:
52
+ - gzip, deflate
53
+ response:
54
+ status:
55
+ code: 200
56
+ message: OK
57
+ headers:
58
+ Content-Length:
59
+ - "337"
60
+ Content-Type:
61
+ - application/json;charset=UTF-8
62
+ Date:
63
+ - Mon, 11 Jan 2016 23:51:04 GMT
64
+ Pragma:
65
+ - no-cache
66
+ Expires:
67
+ - "0"
68
+ Set-Cookie:
69
+ - TS018aacab=01c9ad79a00b36c4299550473029df7107a8073c81d7d5bcee01337bec979dba1ac9e1adff; Path=/
70
+ Cache-Control:
71
+ - no-cache, no-store, must-revalidate
72
+ body:
73
+ string: "{\"merchant\":\"<TNS_MERCHANT_ID>\",\"session\":{\"id\":\"SESSION0002414369440E9534413G26\",\"updateStatus\":\"SUCCESS\",\"version\":\"1b64e1a902\"},\"sourceOfFunds\":{\"provided\":{\"card\":{\"brand\":\"AMEX\",\"expiry\":{\"month\":\"5\",\"year\":\"17\"},\"fundingMethod\":\"CREDIT\",\"number\":\"345678xxxxx4564\",\"scheme\":\"AMEX\",\"securityCode\":\"xxxx\"}},\"type\":\"CARD\"},\"version\":\"32\"}"
74
+ http_version:
75
+ recorded_at: Mon, 11 Jan 2016 23:51:05 GMT
76
+ - request:
77
+ method: put
78
+ uri: https://merchant.<TNS_MERCHANT_ID>:<TNS_API_KEY>@secure.ap.tnspayments.com/api/rest/version/32/merchant/<TNS_MERCHANT_ID>/order/10000001603/transaction/PAY
79
+ body:
80
+ string: "{\"apiOperation\":\"PAY\",\"order\":{\"amount\":\"100.00\",\"currency\":\"EUR\",\"reference\":\"PAY\"},\"transaction\":{\"reference\":\"PAY\"},\"sourceOfFunds\":{\"type\":\"CARD\"},\"session\":{\"id\":\"SESSION0002414369440E9534413G26\"}}"
81
+ headers:
82
+ Accept:
83
+ - "*/*"
84
+ Content-Length:
85
+ - "202"
86
+ Content-Type:
87
+ - Application/json;charset=UTF-8
88
+ Accept-Encoding:
89
+ - gzip, deflate
90
+ response:
91
+ status:
92
+ code: 201
93
+ message: Created
94
+ headers:
95
+ Content-Length:
96
+ - "1658"
97
+ Content-Type:
98
+ - application/json;charset=UTF-8
99
+ Date:
100
+ - Mon, 11 Jan 2016 23:51:05 GMT
101
+ Pragma:
102
+ - no-cache
103
+ Expires:
104
+ - "0"
105
+ Set-Cookie:
106
+ - TS018aacab=01c9ad79a05005d6ce0661f0ff61ffd29ca1e182415e883508eb3c057212ebcb105ce187bc; Path=/
107
+ Cache-Control:
108
+ - no-cache, no-store, must-revalidate
109
+ body:
110
+ string: "{\"authorizationResponse\":{\"posData\":\"1605S0100130\",\"transactionIdentifier\":\"AmexTidTest\"},\"gatewayEntryPoint\":\"AUTO\",\"merchant\":\"<TNS_MERCHANT_ID>\",\"order\":{\"amount\":100.00,\"creationTime\":\"2016-01-11T23:51:05.330Z\",\"currency\":\"EUR\",\"id\":\"10000001603\",\"reference\":\"PAY\",\"status\":\"CAPTURED\",\"totalAuthorizedAmount\":100.00,\"totalCapturedAmount\":100.00,\"totalRefundedAmount\":0.00},\"response\":{\"acquirerCode\":\"000\",\"acquirerMessage\":\"Successful request\",\"cardSecurityCode\":{\"acquirerCode\":\"N\",\"gatewayCode\":\"NO_MATCH\"},\"gatewayCode\":\"APPROVED\"},\"result\":\"SUCCESS\",\"risk\":{\"response\":{\"gatewayCode\":\"ACCEPTED\",\"review\":{\"decision\":\"NOT_REQUIRED\"},\"rule\":[{\"data\":\"345678\",\"name\":\"MERCHANT_BIN_RANGE\",\"recommendation\":\"NO_ACTION\",\"type\":\"MERCHANT_RULE\"},{\"data\":\"N\",\"name\":\"MERCHANT_CSC\",\"recommendation\":\"NO_ACTION\",\"type\":\"MERCHANT_RULE\"},{\"name\":\"SUSPECT_CARD_LIST\",\"recommendation\":\"NO_ACTION\",\"type\":\"MERCHANT_RULE\"},{\"name\":\"TRUSTED_CARD_LIST\",\"recommendation\":\"NO_ACTION\",\"type\":\"MERCHANT_RULE\"},{\"data\":\"345678\",\"name\":\"MSO_BIN_RANGE\",\"recommendation\":\"NO_ACTION\",\"type\":\"MSO_RULE\"},{\"data\":\"N\",\"name\":\"MSO_CSC\",\"recommendation\":\"NO_ACTION\",\"type\":\"MSO_RULE\"}]}},\"sourceOfFunds\":{\"provided\":{\"card\":{\"brand\":\"AMEX\",\"expiry\":{\"month\":\"5\",\"year\":\"17\"},\"fundingMethod\":\"CREDIT\",\"number\":\"345678xxxxx4564\",\"scheme\":\"AMEX\"}},\"type\":\"CARD\"},\"timeOfRecord\":\"2016-01-11T23:51:05.376Z\",\"transaction\":{\"acquirer\":{\"batch\":1,\"id\":\"AMEXGWS\",\"merchantId\":\"9442891578\"},\"amount\":100.00,\"authorizationCode\":\"029328\",\"currency\":\"EUR\",\"frequency\":\"SINGLE\",\"id\":\"PAY\",\"receipt\":\"16011171\",\"reference\":\"PAY\",\"source\":\"MOTO\",\"terminal\":\"31208585\",\"type\":\"CAPTURE\"},\"version\":\"32\"}"
111
+ http_version:
112
+ recorded_at: Mon, 11 Jan 2016 23:51:06 GMT