swiss-crm-activemerchant-v2 1.0.25 → 1.0.27

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: bd8c97a821e37c5f68735206a29b4f9633048aa884cad771c148236924215092
4
- data.tar.gz: 8f2a182c09ef9168c09ba8e792338a1ccbee01fbabedd53ac9f0eaff41d6f806
3
+ metadata.gz: 52a3a0245542e5d05468d5564d9bb9f25c2c8de6e480e312049ee3296a39fa0b
4
+ data.tar.gz: 61b9cc3da8c809c990d3e4fe591fe36ec654833a3a1b28a4629946c25b160600
5
5
  SHA512:
6
- metadata.gz: 9cecc2fd882615b8797c67265d2ce9af85a7a9b7ddea35c16411cb53c68c3852eca2471fd44a4cfb92ba8419640be79f1dd71713eb30bdd858f806748c2bf8f6
7
- data.tar.gz: dad2742d5a273f85b5e2d8c8313cd588b25567b86a7ed36cb52805ae064258759c49456890f4ea1ce1944fb671a91de9ef71f5d9eef44cffc558a3c354868b2f
6
+ metadata.gz: 3778575a3d6c5da62bbbbd46b3971da766e9207d05d126dbff9df650656297da9ff84d79b5a2b5288c73e41ac9a9c15fa4070bf8dcbb71c69261000497a6e464
7
+ data.tar.gz: 23809570d3c7203e57e31957ef7c6867c65ac81d81a29c3495c29da1435cce3098d75897033b90f2b453b292029fe396d1636e376cc75362054c6634352f082a
@@ -355,3 +355,4 @@ module ActiveMerchant #:nodoc:
355
355
  end
356
356
  end
357
357
  end
358
+
@@ -0,0 +1,369 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class FlexchargeGateway < Gateway
4
+ include Empty
5
+
6
+ self.test_url = 'https://api-sandbox.flexfactor.io'
7
+ self.live_url = 'https://api.flexfactor.io'
8
+ self.homepage_url = 'https://www.flexcharge.com'
9
+ self.supported_countries = %w[US CA]
10
+ self.supported_cardtypes = %i[visa master american_express discover]
11
+ self.default_currency = 'USD'
12
+ self.money_format = :cents
13
+ self.display_name = 'Flexcharge'
14
+
15
+ def initialize(options = {})
16
+ requires!(options, :api_key, :api_secret, :merchant_id, :token)
17
+ super
18
+ @api_key = options[:api_key]
19
+ @api_secret = options[:api_secret]
20
+ @mid = options[:merchant_id]
21
+ @tokenization_key = options[:token]
22
+ @site_id = options[:site_id]
23
+ @response_http_code = nil
24
+ end
25
+
26
+ def purchase(amount, payment_source, options = {})
27
+ token_response = get_access_token
28
+ return token_response unless token_response.success?
29
+
30
+ tokenize_response = tokenize_card(payment_source, options)
31
+ return tokenize_response unless tokenize_response.success?
32
+
33
+ post = {}
34
+ add_invoice(post, amount, options)
35
+ add_payment_method(post, tokenize_response.params['paymentMethod'])
36
+ add_customer_data(post, options)
37
+ add_billing_address(post, payment_source, options['billing_address'])
38
+ add_merchant_data(post, options)
39
+ add_idempotency_key(post, options)
40
+
41
+ evaluate_response = commit('evaluate', post, token_response.authorization, options)
42
+ handle_evaluate_response(evaluate_response, amount, options)
43
+ end
44
+
45
+ def capture(amount, authorization, options = {})
46
+ token_response = get_access_token
47
+ return token_response unless token_response.success?
48
+
49
+ post = {}
50
+ add_capture_data(post, amount, authorization, options)
51
+ add_idempotency_key(post, options)
52
+
53
+ commit('capture', post, token_response.authorization, options)
54
+ end
55
+
56
+ def refund(amount, authorization, options = {})
57
+ token_response = get_access_token
58
+ return token_response unless token_response.success?
59
+
60
+ post = {}
61
+ add_refund_data(post, amount)
62
+
63
+ commit('refund', post, token_response.authorization, options.merge(order_id: authorization))
64
+ end
65
+
66
+ def verify_credentials
67
+ response = get_access_token
68
+ response.success?
69
+ end
70
+
71
+ private
72
+
73
+ def tokenize_card(payment_source, options)
74
+ uri = build_tokenization_uri
75
+ payload = build_tokenization_payload(payment_source, options)
76
+
77
+ begin
78
+ raw_response = ssl_post(uri, payload.to_json, tokenization_headers)
79
+ parsed = parse(raw_response)
80
+ process_tokenization_response(parsed, payment_source)
81
+ rescue ResponseError => e
82
+ handle_response_error(e)
83
+ end
84
+ end
85
+
86
+ def build_tokenization_uri
87
+ base_url = test? ? test_url : live_url
88
+ "#{base_url}/v1/tokenize?mid=#{@mid}&environment=#{@tokenization_key}"
89
+ end
90
+
91
+ def build_tokenization_payload(payment_source, options)
92
+ {
93
+ payment_method: {
94
+ email: options[:email],
95
+ credit_card: {
96
+ first_name: payment_source.first_name,
97
+ last_name: payment_source.last_name,
98
+ number: payment_source.number,
99
+ verification_value: payment_source.verification_value,
100
+ month: payment_source.month.to_s,
101
+ year: payment_source.year.to_s
102
+ }
103
+ }
104
+ }
105
+ end
106
+
107
+ def process_tokenization_response(parsed, payment_source)
108
+ token = parsed.dig("transaction", "payment_method", "token")
109
+ success = token.present?
110
+ payment_method = build_payment_method_data(payment_source, token)
111
+
112
+ Response.new(
113
+ success,
114
+ success ? 'Tokenization successful' : 'Tokenization failed',
115
+ parsed.merge('paymentMethod' => payment_method),
116
+ test: test?,
117
+ authorization: token
118
+ )
119
+ end
120
+
121
+ def build_payment_method_data(payment_source, token)
122
+ {
123
+ holderName: "#{payment_source.first_name} #{payment_source.last_name}",
124
+ cardType: "CREDIT",
125
+ expirationMonth: payment_source.month.to_i,
126
+ expirationYear: payment_source.year.to_i,
127
+ cardBinNumber: payment_source.bin_card,
128
+ cardLast4Digits: payment_source.last_digits,
129
+ cardNumber: token,
130
+ token: true
131
+ }
132
+ end
133
+
134
+ def handle_evaluate_response(evaluate_response, amount, options)
135
+ return evaluate_response unless evaluate_response.success?
136
+
137
+ status = evaluate_response.params['status']
138
+
139
+ case status
140
+ when 'CAPTUREREQUIRED'
141
+ process_capture_required(evaluate_response, amount, options)
142
+ else
143
+ evaluate_response
144
+ end
145
+ end
146
+
147
+ def process_capture_required(evaluate_response, amount, options)
148
+ capture_response = capture(amount, evaluate_response.authorization, options)
149
+ merged_params = evaluate_response.params.merge('capture' => capture_response.params)
150
+
151
+ Response.new(
152
+ capture_response.success?,
153
+ capture_response.message,
154
+ merged_params,
155
+ test: test?,
156
+ authorization: capture_response.authorization
157
+ )
158
+ end
159
+
160
+ def add_invoice(post, amount, options)
161
+ post[:transaction] = {
162
+ amount: amount,
163
+ currency: options[:currency],
164
+ id: options[:order_id],
165
+ dynamicDescriptor: options[:descriptor],
166
+ transactionType: 'CAPTURE'
167
+ }
168
+ end
169
+
170
+ def add_payment_method(post, payment_method)
171
+ post[:paymentMethod] = payment_method
172
+ end
173
+
174
+ def add_customer_data(post, options)
175
+ post[:payer] = {
176
+ email: options[:email]
177
+ }
178
+ end
179
+
180
+ def add_billing_address(post, payment_source, billing_address)
181
+ post[:billingInformation] = {
182
+ firstName: payment_source.first_name,
183
+ lastName: payment_source.last_name,
184
+ country: billing_address["country"],
185
+ countryCode: billing_address["country"],
186
+ addressLine1: billing_address["address1"],
187
+ city: billing_address["city"],
188
+ state: billing_address["state"],
189
+ zipcode: billing_address["zip"]
190
+ }.compact
191
+ end
192
+
193
+ def add_merchant_data(post, options)
194
+ post[:orderId] = options[:order_id]
195
+ post[:mid] = @mid
196
+ post[:isDeclined] = true
197
+ post[:siteId] = @site_id
198
+ end
199
+
200
+ def add_capture_data(post, amount, authorization, options)
201
+ post[:idempotencyKey] = options[:idempotency_id]
202
+ post[:orderId] = authorization
203
+ post[:amount] = amount
204
+ post[:currency] = options[:currency]
205
+ end
206
+
207
+ def add_refund_data(post, amount)
208
+ post[:amountToRefund] = amount.to_f / 100
209
+ end
210
+
211
+ def add_idempotency_key(post, options)
212
+ post[:idempotencyKey] = options[:idempotency_id]
213
+ end
214
+
215
+ def commit(action, params, access_token, options = {})
216
+ request_url = build_request_url(action, options)
217
+ raw_response = ssl_post(request_url, params.to_json, headers(access_token))
218
+ response = parse(raw_response)
219
+ succeeded = success_from(action, response)
220
+
221
+ Response.new(
222
+ succeeded,
223
+ message_from(action, response),
224
+ response,
225
+ error_code: error_code_from(succeeded, response),
226
+ authorization: authorization_from(response),
227
+ test: test?,
228
+ request_method: :post,
229
+ request_endpoint: request_url,
230
+ request_body: params,
231
+ response_type: response_type(response['status']),
232
+ response_http_code: @response_http_code
233
+ )
234
+ rescue ResponseError => e
235
+ handle_response_error(e)
236
+ end
237
+
238
+ def build_request_url(action, options)
239
+ case action
240
+ when 'evaluate'
241
+ "#{url}/v1/evaluate"
242
+ when 'capture'
243
+ "#{url}/v1/capture"
244
+ when 'refund'
245
+ "#{url}/v1/orders/#{options[:order_id]}/refund"
246
+ end
247
+ end
248
+
249
+ def headers(access_token)
250
+ {
251
+ 'Authorization' => "Bearer #{access_token}",
252
+ 'accept' => 'application/json',
253
+ 'content-type' => 'application/*+json'
254
+ }
255
+ end
256
+
257
+ def tokenization_headers
258
+ {
259
+ 'Accept' => 'application/json',
260
+ 'Content-Type' => 'application/json'
261
+ }
262
+ end
263
+
264
+ def get_access_token
265
+ payload = build_token_payload
266
+ raw_response = ssl_post("#{url}/v1/oauth2/token", payload.to_json, tokenization_headers)
267
+ process_token_response(raw_response)
268
+ rescue ResponseError => e
269
+ handle_response_error(e)
270
+ end
271
+
272
+ def build_token_payload
273
+ {
274
+ AppKey: @api_key,
275
+ AppSecret: @api_secret
276
+ }
277
+ end
278
+
279
+ def process_token_response(raw_response)
280
+ parsed = parse(raw_response)
281
+ token = parsed['accessToken']
282
+ success = token.present?
283
+
284
+ Response.new(
285
+ success,
286
+ success ? 'Token Retrieved' : 'Token Request Failed',
287
+ parsed,
288
+ authorization: token,
289
+ test: test?
290
+ )
291
+ end
292
+
293
+ def url
294
+ test? ? test_url : live_url
295
+ end
296
+
297
+ def parse(body)
298
+ JSON.parse(body)
299
+ end
300
+
301
+ def success_from(action, response)
302
+ case action
303
+ when 'evaluate'
304
+ %w[APPROVED CAPTUREREQUIRED].include?(response['status'])
305
+ when 'capture'
306
+ response['status'] == 'SUCCESS'
307
+ when 'refund'
308
+ response['status'] == 'SUCCESS'
309
+ else
310
+ false
311
+ end
312
+ end
313
+
314
+ def message_from(action, response)
315
+ status = response['status']
316
+
317
+ case action
318
+ when 'evaluate'
319
+ return 'Pending' if %w[SUBMITTED CHALLENGE].include?(status)
320
+ status == 'APPROVED' ? 'Succeeded' : status
321
+ when 'capture'
322
+ status == 'SUCCESS' ? status : Array(response['errors']).join(', ').presence
323
+ when 'refund'
324
+ status == 'SUCCESS' ? status : response['responseMessage']
325
+ end
326
+ end
327
+
328
+ def authorization_from(response)
329
+ response['orderId'] || response['transactionId']
330
+ end
331
+
332
+ def handle_response_error(error)
333
+ @response_http_code = error.response&.code&.to_i
334
+ body = error.response.body
335
+ parsed = parse(body) rescue { 'error' => body }
336
+ Response.new(
337
+ false,
338
+ parsed['error'],
339
+ parsed,
340
+ test: test?,
341
+ response_http_code: @response_http_code
342
+ )
343
+ end
344
+
345
+ def error_code_from(succeeded, response)
346
+ return nil if succeeded
347
+
348
+ errors = response['errors']
349
+ return response['title'] if errors.blank?
350
+
351
+ errors.map { |_, messages| Array(messages).join(', ') }.join('; ')
352
+ end
353
+
354
+ def handle_response(response)
355
+ @response_http_code = response.code.to_i
356
+ response.body
357
+ end
358
+
359
+ def response_type(status)
360
+ case status
361
+ when 'APPROVED', 'SUCCESS' then 0
362
+ when 'CAPTUREREQUIRED', 'CHALLENGE', 'SUBMITTED' then 1
363
+ when 'FAILED' then 2
364
+ else 1
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end
@@ -3,7 +3,7 @@ module ActiveMerchant #:nodoc:
3
3
  class MollieGateway < Gateway
4
4
  include Empty
5
5
 
6
- SOFT_DECLINE_REASONS = %w[
6
+ SOFT_DECLINE_CODES = %w[
7
7
  insufficient_funds
8
8
  card_expired
9
9
  card_declined
@@ -195,7 +195,7 @@ module ActiveMerchant #:nodoc:
195
195
  post[:shippingAddress] = shipping_address
196
196
  end
197
197
 
198
- def build_address(data, email)
198
+ def build_address(data, email = nil)
199
199
  return if data.blank?
200
200
 
201
201
  first_name, last_name = split_names(data[:name])
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.0.25'
2
+ VERSION = '1.0.27'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swiss-crm-activemerchant-v2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.25
4
+ version: 1.0.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-25 00:00:00.000000000 Z
11
+ date: 2025-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -289,6 +289,7 @@ files:
289
289
  - lib/active_merchant/billing/gateways/first_pay.rb
290
290
  - lib/active_merchant/billing/gateways/firstdata_e4.rb
291
291
  - lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
292
+ - lib/active_merchant/billing/gateways/flexcharge.rb
292
293
  - lib/active_merchant/billing/gateways/flo2cash.rb
293
294
  - lib/active_merchant/billing/gateways/flo2cash_simple.rb
294
295
  - lib/active_merchant/billing/gateways/fluidpay.rb