nexio_activemerchant 0.2.6 → 0.3.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c71ca2a7f93f4b56d210272254bb2e5b42f21971a67dcdb4577b7676aeb77963
4
- data.tar.gz: 438164011b498f1d97be2d03e41ddfb5cd5d92c06ef66aa65cfdf7def2baafc9
3
+ metadata.gz: aed1e22c05eb961b28f55d0576219532c0b4077ed15f86bf39e33a45f59a42de
4
+ data.tar.gz: c9729141380772a443e1aca9bed8c88ce76c206dd46b834a9342499f242a3ea7
5
5
  SHA512:
6
- metadata.gz: fbcaea369894ec54d50b0aab65cdfa66fd17c97febbeaadd80b07a6c3b7165e97728a115eecfd60d144c0aefc030682fb6d5331946db14129f966636eb584f8f
7
- data.tar.gz: 418c3d334030134e369d897ac2a4a09b7226af458cc6972b2f90d0de1201250614baecc94b06a3f657ab4c6a52e4fdd5c287a8ef2c4a0f86f0bd62c23ca2d91d
6
+ metadata.gz: 2f8bb6a1e812b6f303a269908f997fd50ce308b219b956dceb74aadeb0f1b03d4f5b319d89af7125552d231b4ae24ae1b3624c6328a8fb0f608d367267f030e0
7
+ data.tar.gz: 88a4222587961c5a8d7d10c15c104aa2a75132e9425e8de7d3be64b9c0264c16fb5625ad0cea1dd8db52b58f7ddcfb7715a7ac044e06643eef36a9a640685440
data/.rubocop.yml CHANGED
@@ -11,6 +11,10 @@
11
11
 
12
12
  Gemspec/RequiredRubyVersion:
13
13
  Enabled: false
14
+ Metrics/AbcSize:
15
+ Enabled: false
16
+ Metrics/CyclomaticComplexity:
17
+ Enabled: false
14
18
  Metrics/ClassLength:
15
19
  Enabled: false
16
20
  Metrics/MethodLength:
@@ -19,3 +23,5 @@ Lint/AssignmentInCondition:
19
23
  Enabled: false
20
24
  Style/GuardClause:
21
25
  Enabled: false
26
+ Style/Documentation:
27
+ Enabled: false
@@ -3,7 +3,7 @@
3
3
  module ActiveMerchant
4
4
  module Billing
5
5
  class EncryptedNexioCard < CreditCard
6
- ALLOWED_CARD_BRANDS = %w(amex discover jcb mastercard visa).freeze
6
+ ALLOWED_CARD_BRANDS = %w[amex discover jcb mastercard visa].freeze
7
7
 
8
8
  attr_accessor :encrypted_number, :own_form, :one_time_token
9
9
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './nexio_base_gateway'
4
+
5
+ module ActiveMerchant
6
+ module Billing
7
+ class NexioApmGateway < NexioBaseGateway
8
+ self.display_name = 'Nexio AMP'
9
+ self.base_path = '/apm/v3'
10
+ self.abstract_class = false
11
+
12
+ OneTimeToken = Struct.new(:token, :iframe_url, :redirect_urls, :button_urls)
13
+
14
+ def generate_token(money, options = {})
15
+ post = build_payload(options)
16
+ add_invoice(post, money, options)
17
+ post[:data][:paymentMethod] = options[:payment_method] if options[:payment_method].present?
18
+ add_order_data(post, options)
19
+ post[:customerRedirectUrl] = options[:callback_url] if options[:callback_url].present?
20
+ post[:processingOptions][:saveRecurringToken] = true if options[:save_token]
21
+ post[:isAuthOnly] = true if options[:is_auth_only]
22
+ resp = commit('token', post)
23
+ return unless resp.success?
24
+
25
+ OneTimeToken.new(
26
+ resp.params['token'],
27
+ resp.params['expressIFrameUrl'],
28
+ map_urls(resp.params['redirectUrls']),
29
+ map_urls(resp.params['buttonIFrameUrls'])
30
+ )
31
+ end
32
+
33
+ def purchase(money, payment, options = {})
34
+ post = build_payload(options)
35
+ add_invoice(post, money, options)
36
+ post[:apm] = { token: payment.is_a?(Spree::PaymentSource) ? payment.gateway_payment_profile_id : payment }
37
+ add_order_data(post, options)
38
+ post[:isAuthOnly] = true if options[:is_auth_only]
39
+ commit('process', post)
40
+ end
41
+
42
+ def authorize(money, payment, options = {})
43
+ purchase(money, payment, options.merge(is_auth_only: true))
44
+ end
45
+
46
+ private
47
+
48
+ def map_urls(list)
49
+ list.each_with_object({}) { |item, acc| acc[item['paymentMethod']] = item['url'] }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,59 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'nexio_activemerchant/transaction'
4
5
 
5
6
  module ActiveMerchant
6
7
  module Billing
7
- class NexioGateway < Gateway
8
+ class NexioBaseGateway < Gateway
8
9
  self.test_url = 'https://api.nexiopaysandbox.com'
9
10
  self.live_url = 'https://api.nexiopay.com'
10
11
 
11
12
  self.supported_countries = %w[CA US]
12
13
  self.default_currency = 'USD'
13
14
  self.supported_cardtypes = %i[visa master american_express discover]
14
-
15
15
  self.homepage_url = 'https://nex.io'
16
- self.display_name = 'Nexio'
17
-
18
- STANDARD_ERROR_CODE_MAPPING = {}.freeze
16
+ self.abstract_class = true
19
17
 
20
- OneTimeToken = Struct.new(:token, :expiration, :fraud_url)
18
+ class_attribute :base_path
19
+ self.base_path = ''
21
20
 
22
21
  def initialize(options = {})
23
22
  requires!(options, :merchant_id, :auth_token)
24
23
  super
25
24
  end
26
25
 
27
- def generate_token(options = {})
28
- post = build_payload(options)
29
- post[:data][:allowedCardTypes] = %w(amex discover jcb mastercard visa)
30
- add_currency(post, options)
31
- add_order_data(post, options)
32
- add_card_data(post, options)
33
- resp = commit('token', post)
34
- return unless resp.success?
35
-
36
- token, expiration, fraud_url = resp.params.values_at('token', 'expiration', 'fraudUrl')
37
- OneTimeToken.new(token, Time.parse(expiration), fraud_url)
38
- end
39
-
40
- def purchase(money, payment, options = {})
41
- post = build_payload(options)
42
- post[:processingOptions] ||= {}
43
- post[:processingOptions][:verboseResponse] = true if test?
44
- post[:processingOptions][:customerRedirectUrl] = options[:three_d_callback_url] if options.key?(:three_d_callback_url)
45
- post[:processingOptions][:check3ds] = options[:three_d_secure]
46
- post[:processingOptions][:paymentType] = options[:payment_type] if options.key?(:payment_type)
47
- add_invoice(post, money, options)
48
- add_payment(post, payment, options)
49
- add_order_data(post, options)
50
- commit('process', post)
51
- end
52
-
53
- def authorize(money, payment, options = {})
54
- purchase(money, payment, options.merge(payload: options.fetch(:payload, {}).merge(isAuthOnly: true)))
55
- end
56
-
57
26
  def capture(money, authorization, _options = {})
58
27
  commit('capture', { id: authorization, data: { amount: amount(money).to_f } })
59
28
  end
@@ -67,13 +36,6 @@ module ActiveMerchant
67
36
  commit('void', { id: authorization })
68
37
  end
69
38
 
70
- def verify(credit_card, options = {})
71
- MultiResponse.run(:use_first_response) do |r|
72
- r.process { authorize(100, credit_card, options) }
73
- r.process(:ignore_result) { void(r.authorization, options) }
74
- end
75
- end
76
-
77
39
  def supports_scrubbing?
78
40
  false
79
41
  end
@@ -82,28 +44,19 @@ module ActiveMerchant
82
44
  transcript
83
45
  end
84
46
 
85
- def store(payment, options = {})
86
- post = build_payload(options)
87
- add_card_details(post, payment, options)
88
- add_currency(post, options)
89
- add_order_data(post, options)
90
- resp = commit('saveCard', post)
91
- return unless resp.success?
92
-
93
- resp.params.fetch('token', {}).fetch('token', nil)
94
- end
95
-
96
- def set_webhooks(data)
47
+ def setup_webhooks(data)
97
48
  post = { merchantId: options[:merchant_id].to_s }
98
49
  if data.is_a?(String)
99
50
  post[:webhooks] = {
100
51
  TRANSACTION_AUTHORIZED: { url: data },
101
- TRANSACTION_CAPTURED: { url: data }
52
+ TRANSACTION_CAPTURED: { url: data },
53
+ TRANSACTION_SETTLED: { url: data }
102
54
  }
103
55
  else
104
56
  webhooks = {}
105
57
  webhooks[:TRANSACTION_AUTHORIZED] = { url: data[:authorized] } if data.key?(:authorized)
106
58
  webhooks[:TRANSACTION_CAPTURED] = { url: data[:captured] } if data.key?(:captured)
59
+ webhooks[:TRANSACTION_SETTLED] = { url: data[:settled] } if data.key?(:settled)
107
60
  post[:webhooks] = webhooks
108
61
  end
109
62
  commit('webhook', post)
@@ -114,12 +67,24 @@ module ActiveMerchant
114
67
  end
115
68
 
116
69
  def get_transaction(id)
117
- parse(ssl_get(action_url("/transaction/v3/paymentId/#{id}"), base_headers))
118
- rescue ResponseError => e
70
+ data = parse(ssl_get(action_url("/transaction/v3/paymentId/#{id}"), base_headers))
71
+ ::NexioActivemerchant::Transaction.new(data)
72
+ rescue ResponseError
73
+ nil
119
74
  end
120
75
 
121
76
  private
122
77
 
78
+ def build_payload(params)
79
+ result = params.fetch(:payload, {}).deep_dup
80
+ result[:data] ||= {}
81
+ result[:data][:customer] ||= {}
82
+ result[:processingOptions] ||= {}
83
+ result[:processingOptions][:merchantId] ||= options[:merchant_id]
84
+ result[:processingOptions][:verboseResponse] = true if test?
85
+ result
86
+ end
87
+
123
88
  def add_invoice(post, money, options)
124
89
  post[:data][:amount] = amount(money).to_f
125
90
  add_currency(post, options)
@@ -134,11 +99,13 @@ module ActiveMerchant
134
99
  case customer
135
100
  when String
136
101
  post[:data][:customer][:email] = customer
102
+ post[:data][:customer][:customerRef] = customer
137
103
  when Hash
138
104
  post[:data][:customer].merge!({
139
105
  firstName: customer[:first_name],
140
106
  lastName: customer[:last_name],
141
- email: customer[:email]
107
+ email: customer[:email],
108
+ customerRef: customer[:email]
142
109
  })
143
110
  end
144
111
  end
@@ -172,74 +139,14 @@ module ActiveMerchant
172
139
  def add_address(post, data, prefix)
173
140
  return post if data.blank?
174
141
 
175
- post[:data][:customer].merge!({
176
- "#{prefix}AddressOne": data[:address1],
177
- "#{prefix}AddressTwo": data[:address2],
178
- "#{prefix}City": data[:city],
179
- "#{prefix}Country": data[:country],
180
- "#{prefix}Phone": data[:phone],
181
- "#{prefix}Postal": data[:zip],
182
- "#{prefix}State": data[:state]
183
- })
184
- end
185
-
186
- def add_payment(post, payment, options)
187
- post[:tokenex] = token_from(payment)
188
- if payment.is_a?(Spree::CreditCard)
189
- post[:card] = {
190
- cardHolderName: payment.name,
191
- cardType: payment.brand
192
- }.merge!(post.fetch(:card, {}))
193
- end
194
- post[:processingOptions] ||= {}
195
- post[:processingOptions][:merchantId] = self.options[:merchant_id].to_s
196
- post[:processingOptions][:saveCardToken] = options[:save_credit_card] if options.key?(:save_credit_card)
197
- end
198
-
199
- def token_from(payment)
200
- return { token: payment } if payment.is_a?(String)
201
-
202
142
  {
203
- token: payment.gateway_payment_profile_id,
204
- lastFour: payment.last_digits,
205
- cardType: payment.brand
206
- }
207
- end
208
-
209
- def add_card_data(post, options)
210
- if card = options[:card]
211
- post[:card] = {
212
- cardHolderName: card[:name],
213
- expirationMonth: card[:month],
214
- expirationYear: card[:year]
215
- }
143
+ AddressOne: :address1, AddressTwo: :address2, City: :city,
144
+ Country: :country, Phone: :phone, Postal: :zip, State: :state
145
+ }.each do |suffix, key|
146
+ post[:data][:customer]["#{prefix}#{suffix}"] = data[key]
216
147
  end
217
148
  end
218
149
 
219
- def add_card_details(post, payment, _options)
220
- if payment.is_a?(EncryptedNexioCard)
221
- raise ArgumentError, 'The provided card is invalid' unless payment.valid?
222
-
223
- post[:card] = {
224
- cardHolderName: payment.name,
225
- encryptedNumber: payment.encrypted_number,
226
- expirationMonth: payment.month,
227
- expirationYear: payment.short_year,
228
- cardType: payment.brand,
229
- securityCode: payment.verification_value
230
- }
231
- post[:token] = payment.one_time_token
232
- else
233
- raise ArgumentError, "Only #{EncryptedNexioCard} payment method is supported to store cards"
234
- end
235
- end
236
-
237
- def parse(body)
238
- JSON.parse(body)
239
- rescue StandardError
240
- {}
241
- end
242
-
243
150
  def commit(action, parameters)
244
151
  payload = parse(ssl_post(commit_action_url(action, parameters), post_data(action, parameters), base_headers))
245
152
 
@@ -265,29 +172,23 @@ module ActiveMerchant
265
172
  )
266
173
  end
267
174
 
268
- def response_status(action, payload)
269
- case action
270
- when 'process' then authorization_from(payload).present?
271
- else
272
- true
273
- end
175
+ def post_data(_action, post = {})
176
+ JSON.dump(post)
274
177
  end
275
178
 
276
- def authorization_from(payload)
277
- payload.fetch('id', nil)
278
- end
279
-
280
- def post_data(_action, parameters = {})
281
- { merchantId: options[:merchant_id] }.merge(parameters).to_json
179
+ def parse(body)
180
+ JSON.parse(body)
181
+ rescue StandardError
182
+ {}
282
183
  end
283
184
 
284
185
  def commit_action_url(action, _parameters)
285
186
  path = case action
286
- when 'webhook' then '/webhook/v3/config'
287
- when 'secret' then '/webhook/v3/secret'
288
- else
289
- "/pay/v3/#{action}"
290
- end
187
+ when 'webhook' then '/webhook/v3/config'
188
+ when 'secret' then '/webhook/v3/secret'
189
+ else
190
+ "#{self.class.base_path}/#{action}"
191
+ end
291
192
  action_url(path)
292
193
  end
293
194
 
@@ -295,6 +196,24 @@ module ActiveMerchant
295
196
  "#{test? ? test_url : live_url}#{path}"
296
197
  end
297
198
 
199
+ def base_headers(custom = {})
200
+ { Authorization: "Basic #{options[:auth_token]}" }.merge(custom)
201
+ end
202
+
203
+ SUCCESS_PROCESS_STATUSES = %w[authOnlyPending authorizedPending pending authOnly settled].freeze
204
+
205
+ def response_status(action, payload)
206
+ case action
207
+ when 'process' then SUCCESS_PROCESS_STATUSES.include?(payload['transactionStatus'])
208
+ else
209
+ true
210
+ end
211
+ end
212
+
213
+ def authorization_from(payload)
214
+ payload.fetch('id', nil)
215
+ end
216
+
298
217
  def build_avs_result(data)
299
218
  return if data.blank?
300
219
 
@@ -306,14 +225,6 @@ module ActiveMerchant
306
225
 
307
226
  CVVResult.new(data.fetch('gatewayMessage', {}).fetch('cvvresponse', nil))
308
227
  end
309
-
310
- def build_payload(params)
311
- { data: { customer: {} } }.merge!(params.fetch(:payload, {}))
312
- end
313
-
314
- def base_headers(custom = {})
315
- { Authorization: "Basic #{options[:auth_token]}" }
316
- end
317
228
  end
318
229
  end
319
230
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './nexio_base_gateway'
4
+
5
+ module ActiveMerchant
6
+ module Billing
7
+ class NexioGateway < NexioBaseGateway
8
+ self.display_name = 'Nexio'
9
+ self.base_path = '/pay/v3'
10
+ self.abstract_class = false
11
+
12
+ OneTimeToken = Struct.new(:token, :expiration, :fraud_url)
13
+
14
+ def generate_token(options = {})
15
+ post = build_payload(options)
16
+ post[:data][:allowedCardTypes] = %w[amex discover jcb mastercard visa]
17
+ add_currency(post, options)
18
+ add_order_data(post, options)
19
+ add_card_data(post, options)
20
+ resp = commit('token', post)
21
+ return unless resp.success?
22
+
23
+ token, expiration, fraud_url = resp.params.values_at('token', 'expiration', 'fraudUrl')
24
+ OneTimeToken.new(token, Time.parse(expiration), fraud_url)
25
+ end
26
+
27
+ def purchase(money, payment, options = {})
28
+ post = build_payload(options)
29
+ add_invoice(post, money, options)
30
+ add_payment(post, payment, options)
31
+ add_order_data(post, options)
32
+ commit('process', post)
33
+ end
34
+
35
+ def authorize(money, payment, options = {})
36
+ purchase(money, payment, options.merge(payload: options.fetch(:payload, {}).merge(isAuthOnly: true)))
37
+ end
38
+
39
+ def verify(credit_card, options = {})
40
+ MultiResponse.run(:use_first_response) do |r|
41
+ r.process { authorize(100, credit_card, options) }
42
+ r.process(:ignore_result) { void(r.authorization, options) }
43
+ end
44
+ end
45
+
46
+ def store(payment, options = {})
47
+ post = build_payload(options)
48
+ post[:merchantId] ||= options[:merchant_id]
49
+ add_card_details(post, payment, options)
50
+ add_currency(post, options)
51
+ add_order_data(post, options)
52
+ resp = commit('saveCard', post)
53
+ return unless resp.success?
54
+
55
+ resp.params.fetch('token', {}).fetch('token', nil)
56
+ end
57
+
58
+ private
59
+
60
+ def add_payment(post, payment, options)
61
+ post[:tokenex] = token_from(payment)
62
+ if payment.is_a?(Spree::CreditCard)
63
+ post[:card] = {
64
+ cardHolderName: payment.name,
65
+ cardType: payment.brand
66
+ }.merge!(post.fetch(:card, {}))
67
+ end
68
+ post[:processingOptions][:saveCardToken] = options[:save_credit_card] if options.key?(:save_credit_card)
69
+ if options[:three_d_callback_url].present?
70
+ post[:processingOptions][:customerRedirectUrl] = options[:three_d_callback_url]
71
+ end
72
+ post[:processingOptions][:check3ds] = options[:three_d_secure]
73
+ post[:processingOptions][:paymentType] = options[:payment_type] if options[:payment_type].present?
74
+ end
75
+
76
+ def token_from(payment)
77
+ return { token: payment } if payment.is_a?(String)
78
+
79
+ {
80
+ token: payment.gateway_payment_profile_id,
81
+ lastFour: payment.last_digits,
82
+ cardType: payment.brand
83
+ }
84
+ end
85
+
86
+ def add_card_data(post, options)
87
+ if card = options[:card]
88
+ post[:card] = {
89
+ cardHolderName: card[:name],
90
+ expirationMonth: card[:month],
91
+ expirationYear: card[:year]
92
+ }
93
+ end
94
+ end
95
+
96
+ def add_card_details(post, payment, _options)
97
+ if payment.is_a?(EncryptedNexioCard)
98
+ raise ArgumentError, 'The provided card is invalid' unless payment.valid?
99
+
100
+ post[:card] = {
101
+ cardHolderName: payment.name,
102
+ encryptedNumber: payment.encrypted_number,
103
+ expirationMonth: payment.month,
104
+ expirationYear: payment.short_year,
105
+ cardType: payment.brand,
106
+ securityCode: payment.verification_value
107
+ }
108
+ post[:token] = payment.one_time_token
109
+ else
110
+ raise ArgumentError, "Only #{EncryptedNexioCard} payment method is supported to store cards"
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NexioActivemerchant
4
+ Transaction = Struct.new(:data) do
5
+ def status
6
+ data['transactionStatus']
7
+ end
8
+
9
+ def amount
10
+ data['amount'].to_d
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NexioActivemerchant
4
- VERSION = '0.2.6'
4
+ VERSION = '0.3.1'
5
5
  end
@@ -3,7 +3,8 @@
3
3
  require 'activemerchant'
4
4
  require 'active_merchant/billing/rails'
5
5
  require 'active_merchant/billing/encrypted_nexio_card'
6
- require 'active_merchant/billing/gateways/nexio'
6
+ require 'active_merchant/billing/gateways/nexio_gateway'
7
+ require 'active_merchant/billing/gateways/nexio_apm_gateway'
7
8
  require 'nexio_activemerchant/version'
8
9
 
9
10
  module NexioActivemerchant
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexio_activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Whitespectre
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-08 00:00:00.000000000 Z
11
+ date: 2021-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemerchant
@@ -57,8 +57,11 @@ files:
57
57
  - bin/console
58
58
  - bin/setup
59
59
  - lib/active_merchant/billing/encrypted_nexio_card.rb
60
- - lib/active_merchant/billing/gateways/nexio.rb
60
+ - lib/active_merchant/billing/gateways/nexio_apm_gateway.rb
61
+ - lib/active_merchant/billing/gateways/nexio_base_gateway.rb
62
+ - lib/active_merchant/billing/gateways/nexio_gateway.rb
61
63
  - lib/nexio_activemerchant.rb
64
+ - lib/nexio_activemerchant/transaction.rb
62
65
  - lib/nexio_activemerchant/version.rb
63
66
  - nexio_activemerchant.gemspec
64
67
  homepage: https://github.com/whitespectre/nexio_activemerchant