swiss-crm-activemerchant 1.0.15 → 1.0.16

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: b4b25e1ce49fa6ae8a7f34cf69a6ef0e2833e8fbba7ac055c067a99b538a1a1a
4
- data.tar.gz: 1ea976368a71e0c2491a0e1712d71d0bae8be93bd693075820724e82ef598dfc
3
+ metadata.gz: 689115a82a2c414cd14f55bdfb36605a971ac74105006e1eb97c627954d0248d
4
+ data.tar.gz: 18b84a790ab9b91f6c57df713e641f19281dab80fe16673787162768dd0e83a6
5
5
  SHA512:
6
- metadata.gz: 2b98441c3a1a1394cc2e0db7f358a49e7af3bf6e07dbd5f9a7ae580eb73ced7644c4b40a9f30f58b2b00ba0ac5d3b660f72c4ca60529c4c3628b50d282489e01
7
- data.tar.gz: dc47c37a5913a712b032b457e4ac7772f6a586215f8620324b14ea413bb1a5f60735b2920ca0384fba395cd3dc73d197de827fc05e92615ab7adb3fb879926fb
6
+ metadata.gz: 30c4373f1d2b64c1c32819b3b530053173ba6e6c1a82ef7d455f32881b574aa82c5093bc0d42941e414db771b0c0ffe226ed6f034199119b0c416625ae9300ff
7
+ data.tar.gz: ef8f011e4cf68dc6674257cb58e212d6cbcaef81fbe615dbb676790dd493e0df354ad0ab6e355748d1dc1e7523c6e815d5d4da74a58ca5b13ed4804d1f8521d2
@@ -0,0 +1,345 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class FinixGateway < Gateway
4
+ include Empty
5
+
6
+ SUCCESS_CODES = [200, 201].freeze
7
+
8
+ SOFT_DECLINE_CODES = %w[
9
+ INSUFFICIENT_FUNDS
10
+ EXCEEDS_APPROVAL_LIMIT
11
+ CARD_VELOCITY_EXCEEDED
12
+ PROCESSOR_TIMEOUT
13
+ TRANSACTION_NOT_ALLOWED
14
+ ].freeze
15
+
16
+ COUNTRY_CODE = { 'US' => 'USA', 'CA' => 'CAN' }.freeze
17
+
18
+ API_VERSION = '2018-01-01'.freeze
19
+ BASIC_AUTH_PREFIX = 'Basic '.freeze
20
+ CONTENT_TYPE = 'application/json'.freeze
21
+
22
+ self.test_url = 'https://finix.sandbox-payments-api.com'
23
+ self.live_url = 'https://www.finixpayments.com'
24
+ self.supported_countries = %w[US CA]
25
+ self.supported_cardtypes = %i[visa master american_express discover]
26
+ self.default_currency = 'USD'
27
+ self.money_format = :cents
28
+ self.homepage_url = 'https://finixpayments.com'
29
+ self.display_name = 'Finix'
30
+
31
+ def initialize(options = {})
32
+ requires!(options, :username, :password, :merchant_id)
33
+ super
34
+ @username = options[:username]
35
+ @password = options[:password]
36
+ @merchant_id = options[:merchant_id]
37
+ end
38
+
39
+ def purchase(amount, payment_source, options = {})
40
+ post = {}
41
+ add_invoice(post, amount, options)
42
+ add_payment_source(post, payment_source, options)
43
+ add_idempotency_id(post, options)
44
+
45
+ verification_data = options[:verification_data] || {}
46
+ commit('transaction', post, verification_data, options)
47
+ end
48
+
49
+ def refund(amount, authorization, options = {})
50
+ post = {}
51
+ add_refund_data(post, amount, authorization, options)
52
+
53
+ commit('reversals', post, options)
54
+ end
55
+
56
+ private
57
+
58
+ def add_payment_source(post, payment_source, options)
59
+ return attach_existing_instrument(post, payment_source, options) if existing_customer?(options)
60
+
61
+ identity = create_new_identity(options)
62
+ return identity unless identity.success?
63
+
64
+ attach_new_instrument(post, identity.authorization, payment_source, options)
65
+ end
66
+
67
+ def existing_customer?(options)
68
+ options[:customer_identity].present?
69
+ end
70
+
71
+ def attach_existing_instrument(post, payment_source, options)
72
+ instrument_id = payment_source[:finix_payment_instrument_id]
73
+
74
+ if instrument_id.present?
75
+ attach_existing_instrument_data(post, instrument_id, options)
76
+ else
77
+ create_and_attach_new_instrument(post, payment_source, options)
78
+ end
79
+ end
80
+
81
+ def attach_existing_instrument_data(post, instrument_id, options)
82
+ payment_instrument = fetch_payment_instrument(instrument_id)
83
+
84
+ options[:verification_data] = {
85
+ avs_result: payment_instrument.dig('address_verification'),
86
+ cvv_result: payment_instrument.dig('security_code_verification')
87
+ }
88
+
89
+ post[:instrument_id] = instrument_id
90
+ end
91
+
92
+ def create_and_attach_new_instrument(post, payment_source, options)
93
+ instrument = create_payment_instrument(options[:customer_identity], payment_source, options)
94
+
95
+ return instrument unless instrument.success?
96
+
97
+ post[:instrument_id] = instrument.params['id']
98
+ end
99
+
100
+ def create_new_identity(options)
101
+ identity = create_identity(options)
102
+ options[:identity_id] = identity.authorization if identity.success?
103
+ identity
104
+ end
105
+
106
+ def attach_new_instrument(post, identity_id, payment_source, options)
107
+ instrument = create_payment_instrument(identity_id, payment_source, options)
108
+ return instrument unless instrument.success?
109
+
110
+ options[:verification_data] = {
111
+ avs_result: instrument.params['address_verification'],
112
+ cvv_result: instrument.params['security_code_verification']
113
+ }
114
+
115
+ post[:instrument_id] = instrument.params['id']
116
+ end
117
+
118
+ def create_identity(options)
119
+ post = {}
120
+ add_identity_data(post, options)
121
+
122
+ commit('identities', post, nil, options)
123
+ end
124
+
125
+ def create_payment_instrument(identity_id, payment_source, options)
126
+ post = {}
127
+ post[:identity] = identity_id
128
+ add_payment_method(post, payment_source, options)
129
+ add_address(post, options)
130
+
131
+ commit('payment_instruments', post, nil, options)
132
+ end
133
+
134
+ def fetch_payment_instrument(payment_instrument_id)
135
+ raw_response = ssl_get("#{url}/payment_instruments/#{payment_instrument_id}", headers)
136
+ response = parse(raw_response)
137
+ end
138
+
139
+ def add_refund_data(post, amount, authorization, options)
140
+ post[:amount] = amount
141
+ post[:transaction_id] = authorization
142
+ add_idempotency_id(post, options) if options['idempotency_id'].present?
143
+ end
144
+
145
+ def add_idempotency_id(post, options)
146
+ post[:idempotency_id] = options['idempotency_id']
147
+ end
148
+
149
+ def add_identity_data(post, options)
150
+ billing = options[:billing_address] || {}
151
+ first_name, last_name = split_names(billing[:name])
152
+
153
+ post[:type] = 'BUSINESS'
154
+ post[:identity_roles] = ['BUYER']
155
+ post[:entity] = {
156
+ first_name: first_name,
157
+ last_name: last_name,
158
+ email: options[:email],
159
+ phone: billing[:phone],
160
+ personal_address: build_address(billing)
161
+ }
162
+ end
163
+
164
+ def add_payment_method(post, payment_source, options)
165
+ billing = options[:billing_address] || {}
166
+ post[:type] = 'TOKEN'
167
+ post[:token] = payment_source[:gateway_payment_profile_id]
168
+ end
169
+
170
+ def add_invoice(post, amount, options)
171
+ post[:amount] = amount
172
+ post[:currency] = options[:currency] || default_currency
173
+ post[:merchant] = @merchant_id
174
+ post[:tags] = {
175
+ purpose: 'sale',
176
+ order_id: options[:order_id],
177
+ customer_id: options[:customer_id]
178
+ }.compact
179
+ end
180
+
181
+ def add_address(post, options)
182
+ billing = options[:billing_address] || {}
183
+ post[:address] = build_address(billing)
184
+ end
185
+
186
+ def add_reference(post, authorization)
187
+ post[:transaction_id] = authorization
188
+ end
189
+
190
+ def build_address(data)
191
+ {
192
+ line1: data[:address1],
193
+ city: data[:city],
194
+ region: data[:state],
195
+ postal_code: data[:zip].to_s.gsub(/\s+/, ''),
196
+ country: COUNTRY_CODE[data[:country]] || data[:country]
197
+ }
198
+ end
199
+
200
+ def commit(action, params, verification_data = nil, options = {})
201
+ request_url = build_request_url(action, params)
202
+ payload = build_payload(action, params)
203
+ raw_response = ssl_post(request_url, payload.to_json, headers)
204
+ response = parse(raw_response).merge('customer_identity' => options[:identity_id]).compact
205
+ succeeded = ['identities', 'payment_instruments'].include?(action) ?
206
+ profile_created_successful?(response) :
207
+ success_from(response)
208
+
209
+ Response.new(
210
+ succeeded,
211
+ message_from(succeeded, response),
212
+ response,
213
+ error_code: error_code_from(succeeded, response),
214
+ authorization: authorization_from(response),
215
+ avs_result: AVSResult.new(code: verification_data&.dig(:avs_result)),
216
+ cvv_result: CVVResult.new(verification_data&.dig(:cvv_result)),
217
+ test: test?,
218
+ response_type: response_type(response['state']),
219
+ response_http_code: @response_http_code,
220
+ request_endpoint: request_url,
221
+ request_method: :post,
222
+ request_body: payload
223
+ )
224
+ end
225
+
226
+ def build_request_url(action, params)
227
+ case action
228
+ when 'identities'
229
+ "#{url}/identities"
230
+ when 'payment_instruments'
231
+ "#{url}/payment_instruments"
232
+ when 'transaction'
233
+ "#{url}/transfers"
234
+ when 'reversals'
235
+ "#{url}/transfers/#{params[:transaction_id]}/reversals"
236
+ end
237
+ end
238
+
239
+ def build_payload(action, params)
240
+ case action
241
+ when 'reversals'
242
+ {
243
+ amount: params[:amount],
244
+ idempotency_id: params[:idempotency_id],
245
+ tags: params[:tags]
246
+ }
247
+ when 'transaction'
248
+ {
249
+ amount: params[:amount],
250
+ currency: params[:currency] || default_currency,
251
+ merchant: @merchant_id,
252
+ source: params[:instrument_id],
253
+ idempotency_id: params[:idempotency_id],
254
+ tags: params[:tags]
255
+ }
256
+ else
257
+ params
258
+ end
259
+ end
260
+
261
+ def headers
262
+ {
263
+ 'Content-Type' => CONTENT_TYPE,
264
+ 'Authorization' => basic_auth_header,
265
+ 'Finix-Version' => API_VERSION
266
+ }
267
+ end
268
+
269
+ def basic_auth_header
270
+ BASIC_AUTH_PREFIX + Base64.strict_encode64("#{@username}:#{@password}")
271
+ end
272
+
273
+ def url
274
+ test? ? test_url : live_url
275
+ end
276
+
277
+ def parse(body)
278
+ JSON.parse(body)
279
+ end
280
+
281
+ def success_from(response)
282
+ response['state'] == 'SUCCEEDED'
283
+ end
284
+
285
+ def profile_created_successful?(response)
286
+ return true if response['id'].present?
287
+
288
+ errors = response.dig('_embedded', 'errors')
289
+ if errors.present?
290
+ return false
291
+ end
292
+
293
+ false
294
+ end
295
+
296
+ def message_from(succeeded, response)
297
+ state = response["state"]
298
+
299
+ case state
300
+ when "FAILED"
301
+ response['failure_message']
302
+ when "PENDING", "SUCCEEDED", "CANCELED"
303
+ state.capitalize
304
+ else
305
+ response.dig('_embedded', 'errors', 0, 'message')
306
+ end
307
+ end
308
+
309
+ def error_code_from(succeeded, response)
310
+ return nil if succeeded
311
+
312
+ if response['_embedded'] && response['_embedded']['errors']
313
+ response['_embedded']['errors'].first['code']
314
+ else
315
+ response['failure_code']
316
+ end
317
+ end
318
+
319
+ def authorization_from(response)
320
+ response['id']
321
+ end
322
+
323
+ def response_type(state)
324
+ case state
325
+ when 'SUCCEEDED' then 0
326
+ when 'FAILED' then 2
327
+ when 'CANCELED', 'DECLINED' then 1
328
+ else 1
329
+ end
330
+ end
331
+
332
+ def split_names(name)
333
+ name_parts = name.to_s.split
334
+ first_name = name_parts.first || ''
335
+ last_name = name_parts[1..]&.join(' ') || ''
336
+ [first_name, last_name]
337
+ end
338
+
339
+ def handle_response(response)
340
+ @response_http_code = response.code.to_i
341
+ response.body
342
+ end
343
+ end
344
+ end
345
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.0.15'
2
+ VERSION = '1.0.16'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swiss-crm-activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.15
4
+ version: 1.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-31 00:00:00.000000000 Z
11
+ date: 2025-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -284,6 +284,7 @@ files:
284
284
  - lib/active_merchant/billing/gateways/fat_zebra.rb
285
285
  - lib/active_merchant/billing/gateways/federated_canada.rb
286
286
  - lib/active_merchant/billing/gateways/finansbank.rb
287
+ - lib/active_merchant/billing/gateways/finix.rb
287
288
  - lib/active_merchant/billing/gateways/first_giving.rb
288
289
  - lib/active_merchant/billing/gateways/first_pay.rb
289
290
  - lib/active_merchant/billing/gateways/firstdata_e4.rb
@@ -484,7 +485,7 @@ licenses:
484
485
  - MIT
485
486
  metadata:
486
487
  allowed_push_host: https://rubygems.org
487
- post_install_message:
488
+ post_install_message:
488
489
  rdoc_options: []
489
490
  require_paths:
490
491
  - lib
@@ -499,8 +500,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
499
500
  - !ruby/object:Gem::Version
500
501
  version: '0'
501
502
  requirements: []
502
- rubygems_version: 3.3.7
503
- signing_key:
503
+ rubygems_version: 3.2.3
504
+ signing_key:
504
505
  specification_version: 4
505
506
  summary: Framework and tools for dealing with credit card transactions.
506
507
  test_files: []