tns_payments 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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