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 +4 -4
- data/lib/active_merchant/billing/gateways/finix.rb +345 -0
- data/lib/active_merchant/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 689115a82a2c414cd14f55bdfb36605a971ac74105006e1eb97c627954d0248d
|
4
|
+
data.tar.gz: 18b84a790ab9b91f6c57df713e641f19281dab80fe16673787162768dd0e83a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
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-
|
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
|
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: []
|