activemerchant 1.125.0 → 1.126.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +75 -0
  3. data/lib/active_merchant/billing/credit_card_methods.rb +12 -0
  4. data/lib/active_merchant/billing/gateway.rb +2 -1
  5. data/lib/active_merchant/billing/gateways/adyen.rb +7 -4
  6. data/lib/active_merchant/billing/gateways/airwallex.rb +341 -0
  7. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +2 -1
  8. data/lib/active_merchant/billing/gateways/blue_pay.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/blue_snap.rb +31 -21
  10. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +6 -1
  11. data/lib/active_merchant/billing/gateways/braintree/token_nonce.rb +113 -0
  12. data/lib/active_merchant/billing/gateways/braintree_blue.rb +87 -15
  13. data/lib/active_merchant/billing/gateways/card_connect.rb +1 -1
  14. data/lib/active_merchant/billing/gateways/checkout_v2.rb +1 -1
  15. data/lib/active_merchant/billing/gateways/credorax.rb +10 -0
  16. data/lib/active_merchant/billing/gateways/cyber_source.rb +13 -33
  17. data/lib/active_merchant/billing/gateways/d_local.rb +49 -0
  18. data/lib/active_merchant/billing/gateways/decidir.rb +17 -1
  19. data/lib/active_merchant/billing/gateways/decidir_plus.rb +185 -14
  20. data/lib/active_merchant/billing/gateways/ebanx.rb +3 -2
  21. data/lib/active_merchant/billing/gateways/global_collect.rb +26 -16
  22. data/lib/active_merchant/billing/gateways/ipg.rb +1 -2
  23. data/lib/active_merchant/billing/gateways/litle.rb +93 -1
  24. data/lib/active_merchant/billing/gateways/moneris.rb +35 -8
  25. data/lib/active_merchant/billing/gateways/nmi.rb +12 -7
  26. data/lib/active_merchant/billing/gateways/orbital.rb +349 -327
  27. data/lib/active_merchant/billing/gateways/payflow.rb +62 -0
  28. data/lib/active_merchant/billing/gateways/paymentez.rb +26 -7
  29. data/lib/active_merchant/billing/gateways/paysafe.rb +15 -15
  30. data/lib/active_merchant/billing/gateways/payu_latam.rb +25 -15
  31. data/lib/active_merchant/billing/gateways/priority.rb +158 -136
  32. data/lib/active_merchant/billing/gateways/rapyd.rb +258 -0
  33. data/lib/active_merchant/billing/gateways/safe_charge.rb +1 -4
  34. data/lib/active_merchant/billing/gateways/simetrik.rb +362 -0
  35. data/lib/active_merchant/billing/gateways/stripe.rb +4 -2
  36. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +93 -48
  37. data/lib/active_merchant/billing/gateways/visanet_peru.rb +6 -2
  38. data/lib/active_merchant/version.rb +1 -1
  39. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f8d0ad7bb54e228b44738e6cf324034467970e6709047a7f799dd7d3276998c
4
- data.tar.gz: 96ab0828793d536f7df925fe962444c1cc0c48d129de54c19d40c81145ed1d11
3
+ metadata.gz: 5bd1687aa128f1fce724fd39276bdb068552dc55e5dadeeab08a127781d54b31
4
+ data.tar.gz: 9519db6995b762487012de3fb0e47ef2f89587d26889fb6c2bbc0e1a74a88713
5
5
  SHA512:
6
- metadata.gz: 1a31f43e0702edeb5727c61285c54afeecc5accc6c9f89134966228fe9c08369f9918890ff25cda85ec863f3cda2bd1e2e6dde8f4eed1254ed6d520f37067dcd
7
- data.tar.gz: 144dc89e223d28ceed1399b50d15116d169dc056f878bb9ba9ed477e4c141fde2c2bc25a882610e4295fa3a25fc8f63800034352b77eb3a370718ea64d017665
6
+ metadata.gz: af5c8f0f3d87e24ab864c880bde2be9374a60ebb47fdb57feafd0e80e790f585a18be65e331d07b2029c8679e1d35fb7f3baa482590da5bb4959828d5e7f5526
7
+ data.tar.gz: e2584e54260092929898ef7af600eecdb28d9641b7de50519eab20e1ad7929c67aada43ab93166af057e18f1f408fc9c9d45b36c010650d840b187661318f962
data/CHANGELOG CHANGED
@@ -3,6 +3,79 @@
3
3
 
4
4
  == HEAD
5
5
 
6
+ == Version 1.126.0 (April 15th, 2022)
7
+ * Moneris: Add 3DS MPI field support [esmitperez] #4373
8
+ * StripePI: Add ability to change payment_method_type to confirm_intent [aenand] #4300
9
+ * GlobalCollect: Improve support for Naranja and Cabal card types [dsmcclain] #4286
10
+ * Payflow: Add support for stored credentials [ajawadmirza] #4277
11
+ * Orbital: Don't void $0 auths for Verify [javierpedrozaing] #2487
12
+ * StripePI: Enable Apple Pay and Google Pay payment methods [gasb150] #4252
13
+ * PaySafe: Update `unstore` method and authorization for redact [ajawadmirza] #4294
14
+ * CyberSource: Add `national_tax_indicator` fields in authorize and purchase [ajawadmirza] #4299
15
+ * NMI: Update gateway credentials to accept security_key [javierpedrozaing] #4302
16
+ * PaySafe: Fix commit for `unstore` method [ajawadmirza] #4303
17
+ * Ebanx: Add support for `order_number` field [ali-hassan] #4304
18
+ * BlueSnap: Add support for `idempotency_key` field [drkjc] #4305
19
+ * Paymentez: Update `capture` method to verify by otp for pending transactions [ajawadmirza] #4267
20
+ * BlueSnap: Update refund request and endpoint along with merchant transaction support [ajawadmirza] #4307
21
+ * DecidirPlus: Added `authorize`, `capture`, `void`, and `verify` methods [ajawadmirza] #4284
22
+ * Paymentez: Fix `authorize` to call `purchase` for otp flow [ajawadmirza] #4310
23
+ * Orbital: Indicate support for network tokenization [dsmcclain] #4309
24
+ * IPG: remove `uruguay` from supported countries [ajawadmirza] #4311
25
+ * Decidir: Add sub_payments sub-fields to gateway [meagabeth] #4315
26
+ * Priority: Add additional fields to purchase and capture requests [dsmcclain] #4301
27
+ * DecidirPlus: Added `unstore` method [ajawadmirza] #4317
28
+ * Decidir & Decidir Plus: Revise handling of `sub_payment` sub-fields [meagabeth] #4318
29
+ * DecidirPlus: Update `unstore` implementation to get token from params [ajawadmirza] #4320
30
+ * CyberSource: Add option for zero amount verify [gasb150] #4313
31
+ * PayU Latam: Refactor `message_from` method, fix failing remote tests [rachelkirk] #4326
32
+ * Adyen: Add currencies with three decimals places [gasb150] #4322
33
+ * GlobalCollect: Stregthen success criteria for void action [peteroas] #4324
34
+ * Priority Payment Systems - Clean up/refactor gateway file and tests [ali-hassan] #4327
35
+ * SafeCharge: change `verify` to send 0 amount [dsmcclain] #4332
36
+ * DLocal: add support for `force_type` field [dsmcclain] #4336
37
+ * Barclaycard SmartPay: Support more nonstandard currencies [jherreraa] #4335
38
+ * DecidirPlus: `name_override` option on `store` [naashton] #4338
39
+ * Priority: Update `add_purchases_data` to return if `options[:purchases]` is empty [drkjc] #4349
40
+ * Stripe PI: update `shipping` field to `shipping_address` [ajawadmirza] #4347
41
+ * DecidirPlus: Handle `payment_method_id` by `card_brand` [naashton] #4350
42
+ * DecidirPlus: `debit` and `payment_method_id` fields [naashton] #4351
43
+ * Adyen: Include Application ID in adyen authorize and purchase transactions [peteroas] #4343
44
+ * Priority: Add support for `replay_id` field [drkjc] #4352
45
+ * Stripe PI: standardize `shipping_address` fields [dsmcclain] #4355
46
+ * Airwallex: support gateway [therufs] #4342
47
+ * Litle: Translate google_pay as android_pay [javierpedrozaing] #4331
48
+ * Braintree: Add ACH support for store [cristian] #4285
49
+ * Simetrik: Add support for Simetrik gateway [simetrik-frank] #4339
50
+ * EBANX: Change amount for Mexico and Chile [flaaviaa] #4337
51
+ * DecidirPlus: Add `establishment_name`, `aggregate_data`, `sub_payments`, `card_holder_identification_type`, `card_holder_identification_number`, `card_door_number`, and `card_holder_birthday` fields [ajawadmirza] #4361
52
+ * DecidirPlus: Update `error_code_from` to get error reason id [ajawadmirza] #4364
53
+ * Dlocal: Add three_ds mpi support [cristian] #4345
54
+ * Stripe PI: Add `request_three_d_secure` field for `create_setup_intent` [aenand] #4365
55
+ * Adyen: Add `verify_amount` field for verify [ajawadmirza] #4369
56
+ * Stripe PI: Pass options for tokenizing Apple/Google Pay [gasb150] #4368
57
+ * Dlocal: Format 3DS mpi enrollment data correctly [cristian] #4371
58
+ * Airwallex: QA fixes for option handling [therufs] #4367
59
+ * CardConnect: Fixed duplicate(concat) Address sent - card_connect is concat. address1 and 2 causing a AVS error [ahmirza] #4362
60
+ * CyberSource: Remove Pinless Debit Transaction Functionality [peteroas] #4370
61
+ * Litle: Add support for Level 2 and 3 enhanced data [curiousepic] #4360
62
+ * Rapyd: Add gateway support [meagabeth] #4372
63
+ * CyberSource: Update and fix test coverage [peteroas] #4374
64
+ * Airwallex: QA fixes for address and create_setup_intent handling [therufs] #4377
65
+ * Airwallex: add `descriptor` field and update logic for sending `request_id` and `merchant_order_id` [dsmcclain] #4379
66
+ * Visanet Peru: use timestamp instead of random for purchaseNumber [therufs] #4093
67
+ * Orbital: add `verify_amount` field [ajawadmirza] #4376
68
+ * Credorax: add `recipient_street_address`, `recipient_city`, `recipient_province_code`, and `recipient_country_code` fields [ajawadmirza] #4384
69
+ * Airwallex: add support for stored credentials [drkjc] #4382
70
+ * Rapyd: Add metadata and ewallet_id options [naashton] #4387
71
+ * Priority: Add additional fields to request and refactor gateway integration [dsmcclain] #4383
72
+ * Rapyd: Update `type` option to `pm_type` [naashton] #4391
73
+ * Conekta: Fix remote test [javierpedrozaing] #4386
74
+ * NMI: Update post URL [jherreraa] #4380
75
+ * Multiple Gateways: Resolve when/case bug [naashton] #4399
76
+ * Airwallex: Add 3DS MPI support [drkjc] #4395
77
+ * Add Cartes Bancaires card bin ranges [leahriffell] #4398
78
+
6
79
  == Version 1.125.0 (January 20, 2022)
7
80
  * Wompi: support gateway [therufs] #4173
8
81
  * Stripe Payment Intents: Add setup_purchase [aenand] #4178
@@ -86,6 +159,8 @@
86
159
  * Update inline documentation with all supported cardtypes [ali-hassan] #4283
87
160
  * PayWay: Update endpoints, response code [jessiagee] #4281
88
161
  * CyberSource: Add `line_items` for purchase [ajawadmirza] #4282
162
+ * Payflow Pro: Add `stored_credential` fields [ajawadmirza] #4277
163
+ * Decidir Plus: Add `fraud_detection` fields [naashton] #4289
89
164
 
90
165
  == Version 1.124.0 (October 28th, 2021)
91
166
  * Worldpay: Add Support for Submerchant Data on Worldpay [almalee24] #4147
@@ -34,6 +34,7 @@ module ActiveMerchant #:nodoc:
34
34
  CARNET_BINS.any? { |bin| num.slice(0, bin.size) == bin }
35
35
  )
36
36
  },
37
+ 'cartes_bancaires' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), CARTES_BANCAIRES_RANGES) },
37
38
  'olimpica' => ->(num) { num =~ /^636853\d{10}$/ },
38
39
  'creditel' => ->(num) { num =~ /^601933\d{10}$/ },
39
40
  'confiable' => ->(num) { num =~ /^560718\d{10}$/ },
@@ -73,6 +74,17 @@ module ActiveMerchant #:nodoc:
73
74
  ]
74
75
  )
75
76
 
77
+ CARTES_BANCAIRES_RANGES = [
78
+ (507589..507590),
79
+ (507593..507595),
80
+ [507597],
81
+ [560408],
82
+ [581752],
83
+ (585402..585405),
84
+ (585501..585505),
85
+ (585577..585582)
86
+ ]
87
+
76
88
  # https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 73
77
89
  MASTERCARD_RANGES = [
78
90
  (222100..272099),
@@ -95,7 +95,8 @@ module ActiveMerchant #:nodoc:
95
95
  pickup_card: 'pick_up_card',
96
96
  config_error: 'config_error',
97
97
  test_mode_live_card: 'test_mode_live_card',
98
- unsupported_feature: 'unsupported_feature'
98
+ unsupported_feature: 'unsupported_feature',
99
+ invalid_amount: 'invalid_amount'
99
100
  }
100
101
 
101
102
  cattr_reader :implementations
@@ -9,6 +9,7 @@ module ActiveMerchant #:nodoc:
9
9
  self.supported_countries = %w(AT AU BE BG BR CH CY CZ DE DK EE ES FI FR GB GI GR HK HU IE IS IT LI LT LU LV MC MT MX NL NO PL PT RO SE SG SK SI US)
10
10
  self.default_currency = 'USD'
11
11
  self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
12
+ self.currencies_with_three_decimal_places = %w(BHD IQD JOD KWD LYD OMR TND)
12
13
  self.supported_cardtypes = %i[visa master american_express diners_club jcb dankort maestro discover elo naranja cabal unionpay]
13
14
 
14
15
  self.money_format = :cents
@@ -143,8 +144,9 @@ module ActiveMerchant #:nodoc:
143
144
  end
144
145
 
145
146
  def verify(credit_card, options = {})
147
+ amount = options[:verify_amount]&.to_i || 0
146
148
  MultiResponse.run(:use_first_response) do |r|
147
- r.process { authorize(0, credit_card, options) }
149
+ r.process { authorize(amount, credit_card, options) }
148
150
  options[:idempotency_key] = nil
149
151
  r.process(:ignore_result) { void(r.authorization, options) }
150
152
  end
@@ -461,12 +463,13 @@ module ActiveMerchant #:nodoc:
461
463
  end
462
464
 
463
465
  def add_external_platform(post, options)
466
+ options.update(externalPlatform: application_id) if application_id
467
+
464
468
  return unless options[:externalPlatform]
465
469
 
466
470
  post[:applicationInfo][:externalPlatform] = {
467
471
  name: options[:externalPlatform][:name],
468
- version: options[:externalPlatform][:version],
469
- integrator: options[:externalPlatform][:integrator]
472
+ version: options[:externalPlatform][:version]
470
473
  }
471
474
  end
472
475
 
@@ -669,7 +672,7 @@ module ActiveMerchant #:nodoc:
669
672
  end
670
673
 
671
674
  def error_code_from(response)
672
- STANDARD_ERROR_CODE_MAPPING[response['errorCode']]
675
+ STANDARD_ERROR_CODE_MAPPING[response['errorCode']] || response['errorCode']
673
676
  end
674
677
 
675
678
  def network_transaction_id_from(response)
@@ -0,0 +1,341 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class AirwallexGateway < Gateway
4
+ self.test_url = 'https://api-demo.airwallex.com/api/v1'
5
+ self.live_url = 'https://pci-api.airwallex.com/api/v1'
6
+
7
+ # per https://www.airwallex.com/docs/online-payments__overview, cards are accepted in all EU countries
8
+ self.supported_countries = %w[AT AU BE BG CY CZ DE DK EE GR ES FI FR GB HK HR HU IE IT LT LU LV MT NL PL PT RO SE SG SI SK]
9
+ self.default_currency = 'AUD'
10
+ self.supported_cardtypes = %i[visa master]
11
+
12
+ self.homepage_url = 'https://airwallex.com/'
13
+ self.display_name = 'Airwallex'
14
+
15
+ ENDPOINTS = {
16
+ login: '/authentication/login',
17
+ setup: '/pa/payment_intents/create',
18
+ sale: '/pa/payment_intents/%{id}/confirm',
19
+ capture: '/pa/payment_intents/%{id}/capture',
20
+ refund: '/pa/refunds/create',
21
+ void: '/pa/payment_intents/%{id}/cancel'
22
+ }
23
+
24
+ def initialize(options = {})
25
+ requires!(options, :client_id, :client_api_key)
26
+ @client_id = options[:client_id]
27
+ @client_api_key = options[:client_api_key]
28
+ super
29
+ @access_token = setup_access_token
30
+ end
31
+
32
+ def purchase(money, card, options = {})
33
+ requires!(options, :return_url)
34
+
35
+ payment_intent_id = create_payment_intent(money, options)
36
+ post = {
37
+ 'request_id' => request_id(options),
38
+ 'merchant_order_id' => merchant_order_id(options),
39
+ 'return_url' => options[:return_url]
40
+ }
41
+ add_card(post, card, options)
42
+ add_descriptor(post, options)
43
+ add_stored_credential(post, options)
44
+ post['payment_method_options'] = { 'card' => { 'auto_capture' => false } } if authorization_only?(options)
45
+
46
+ add_three_ds(post, options)
47
+ commit(:sale, post, payment_intent_id)
48
+ end
49
+
50
+ def authorize(money, payment, options = {})
51
+ # authorize is just a purchase w/o an auto capture
52
+ purchase(money, payment, options.merge({ auto_capture: false }))
53
+ end
54
+
55
+ def capture(money, authorization, options = {})
56
+ raise ArgumentError, 'An authorization value must be provided.' if authorization.blank?
57
+
58
+ post = {
59
+ 'request_id' => request_id(options),
60
+ 'merchant_order_id' => merchant_order_id(options),
61
+ 'amount' => amount(money)
62
+ }
63
+ add_descriptor(post, options)
64
+
65
+ commit(:capture, post, authorization)
66
+ end
67
+
68
+ def refund(money, authorization, options = {})
69
+ raise ArgumentError, 'An authorization value must be provided.' if authorization.blank?
70
+
71
+ post = {}
72
+ post[:amount] = amount(money)
73
+ post[:payment_intent_id] = authorization
74
+ post[:request_id] = request_id(options)
75
+ post[:merchant_order_id] = merchant_order_id(options)
76
+
77
+ commit(:refund, post)
78
+ end
79
+
80
+ def void(authorization, options = {})
81
+ raise ArgumentError, 'An authorization value must be provided.' if authorization.blank?
82
+
83
+ post = {}
84
+ post[:request_id] = request_id(options)
85
+ post[:merchant_order_id] = merchant_order_id(options)
86
+ add_descriptor(post, options)
87
+
88
+ commit(:void, post, authorization)
89
+ end
90
+
91
+ def verify(credit_card, options = {})
92
+ MultiResponse.run(:use_first_response) do |r|
93
+ r.process { authorize(100, credit_card, options) }
94
+ r.process(:ignore_result) { void(r.authorization, options) }
95
+ end
96
+ end
97
+
98
+ def supports_scrubbing?
99
+ true
100
+ end
101
+
102
+ def scrub(transcript)
103
+ transcript.
104
+ gsub(/(\\\"number\\\":\\\")\d+/, '\1[REDACTED]').
105
+ gsub(/(\\\"cvc\\\":\\\")\d+/, '\1[REDACTED]')
106
+ end
107
+
108
+ private
109
+
110
+ def request_id(options)
111
+ options[:request_id] || generate_timestamp
112
+ end
113
+
114
+ def merchant_order_id(options)
115
+ options[:merchant_order_id] || options[:order_id] || generate_timestamp
116
+ end
117
+
118
+ def generate_timestamp
119
+ (Time.now.to_f.round(2) * 100).to_i.to_s
120
+ end
121
+
122
+ def setup_access_token
123
+ token_headers = {
124
+ 'Content-Type' => 'application/json',
125
+ 'x-client-id' => @client_id,
126
+ 'x-api-key' => @client_api_key
127
+ }
128
+ response = ssl_post(build_request_url(:login), nil, token_headers)
129
+ JSON.parse(response)['token']
130
+ end
131
+
132
+ def build_request_url(action, id = nil)
133
+ base_url = (test? ? test_url : live_url)
134
+ base_url + ENDPOINTS[action].to_s % { id: id }
135
+ end
136
+
137
+ def create_payment_intent(money, options = {})
138
+ post = {}
139
+ add_invoice(post, money, options)
140
+ add_order(post, options)
141
+ post[:request_id] = "#{request_id(options)}_setup"
142
+ post[:merchant_order_id] = "#{merchant_order_id(options)}_setup"
143
+ add_descriptor(post, options)
144
+
145
+ response = commit(:setup, post)
146
+ raise ArgumentError.new(response.message) unless response.success?
147
+
148
+ response.params['id']
149
+ end
150
+
151
+ def add_billing(post, card, options = {})
152
+ return unless has_name_info?(card)
153
+
154
+ billing = post['payment_method']['card']['billing'] || {}
155
+ billing['email'] = options[:email] if options[:email]
156
+ billing['phone'] = options[:phone] if options[:phone]
157
+ billing['first_name'] = card.first_name
158
+ billing['last_name'] = card.last_name
159
+ billing_address = options[:billing_address]
160
+ billing['address'] = build_address(billing_address) if has_required_address_info?(billing_address)
161
+
162
+ post['payment_method']['card']['billing'] = billing
163
+ end
164
+
165
+ def has_name_info?(card)
166
+ # These fields are required if billing data is sent.
167
+ card.first_name && card.last_name
168
+ end
169
+
170
+ def has_required_address_info?(address)
171
+ # These fields are required if address data is sent.
172
+ return unless address
173
+
174
+ address[:address1] && address[:country]
175
+ end
176
+
177
+ def build_address(address)
178
+ return unless address
179
+
180
+ address_data = {} # names r hard
181
+ address_data[:country_code] = address[:country]
182
+ address_data[:street] = address[:address1]
183
+ address_data[:city] = address[:city] if address[:city] # required per doc, not in practice
184
+ address_data[:postcode] = address[:zip] if address[:zip]
185
+ address_data[:state] = address[:state] if address[:state]
186
+ address_data
187
+ end
188
+
189
+ def add_invoice(post, money, options)
190
+ post[:amount] = amount(money)
191
+ post[:currency] = (options[:currency] || currency(money))
192
+ end
193
+
194
+ def add_card(post, card, options = {})
195
+ post['payment_method'] = {
196
+ 'type' => 'card',
197
+ 'card' => {
198
+ 'expiry_month' => format(card.month, :two_digits),
199
+ 'expiry_year' => card.year.to_s,
200
+ 'number' => card.number.to_s,
201
+ 'name' => card.name,
202
+ 'cvc' => card.verification_value
203
+ }
204
+ }
205
+ add_billing(post, card, options)
206
+ end
207
+
208
+ def add_order(post, options)
209
+ return unless shipping_address = options[:shipping_address]
210
+
211
+ physical_address = build_shipping_address(shipping_address)
212
+ first_name, last_name = split_names(shipping_address[:name])
213
+ shipping = {}
214
+ shipping[:first_name] = first_name if first_name
215
+ shipping[:last_name] = last_name if last_name
216
+ shipping[:phone_number] = shipping_address[:phone_number] if shipping_address[:phone_number]
217
+ shipping[:address] = physical_address
218
+ post[:order] = { shipping: shipping }
219
+ end
220
+
221
+ def build_shipping_address(shipping_address)
222
+ address = {}
223
+ address[:city] = shipping_address[:city]
224
+ address[:country_code] = shipping_address[:country]
225
+ address[:postcode] = shipping_address[:zip]
226
+ address[:state] = shipping_address[:state]
227
+ address[:street] = shipping_address[:address1]
228
+ address
229
+ end
230
+
231
+ def add_stored_credential(post, options)
232
+ return unless stored_credential = options[:stored_credential]
233
+
234
+ external_recurring_data = post[:external_recurring_data] = {}
235
+
236
+ case stored_credential.dig(:reason_type)
237
+ when 'recurring', 'installment'
238
+ external_recurring_data[:merchant_trigger_reason] = 'scheduled'
239
+ when 'unscheduled'
240
+ external_recurring_data[:merchant_trigger_reason] = 'unscheduled'
241
+ end
242
+
243
+ external_recurring_data[:original_transaction_id] = stored_credential.dig(:network_transaction_id)
244
+ external_recurring_data[:triggered_by] = stored_credential.dig(:initiator) == 'cardholder' ? 'customer' : 'merchant'
245
+ end
246
+
247
+ def add_three_ds(post, options)
248
+ return unless three_d_secure = options[:three_d_secure]
249
+
250
+ pm_options = post.dig('payment_method_options', 'card')
251
+
252
+ external_three_ds = {
253
+ 'version': format_three_ds_version(three_d_secure),
254
+ 'eci': three_d_secure[:eci]
255
+ }.merge(three_ds_version_specific_fields(three_d_secure))
256
+
257
+ pm_options ? pm_options.merge!('external_three_ds': external_three_ds) : post['payment_method_options'] = { 'card': { 'external_three_ds': external_three_ds } }
258
+ end
259
+
260
+ def format_three_ds_version(three_d_secure)
261
+ version = three_d_secure[:version].split('.')
262
+
263
+ version.push('0') until version.length == 3
264
+ version.join('.')
265
+ end
266
+
267
+ def three_ds_version_specific_fields(three_d_secure)
268
+ if three_d_secure[:version].to_f >= 2
269
+ {
270
+ 'authentication_value': three_d_secure[:cavv],
271
+ 'ds_transaction_id': three_d_secure[:ds_transaction_id],
272
+ 'three_ds_server_transaction_id': three_d_secure[:three_ds_server_trans_id]
273
+ }
274
+ else
275
+ {
276
+ 'cavv': three_d_secure[:cavv],
277
+ 'xid': three_d_secure[:xid]
278
+ }
279
+ end
280
+ end
281
+
282
+ def authorization_only?(options = {})
283
+ options.include?(:auto_capture) && options[:auto_capture] == false
284
+ end
285
+
286
+ def add_descriptor(post, options)
287
+ post[:descriptor] = options[:description] if options[:description]
288
+ end
289
+
290
+ def parse(body)
291
+ JSON.parse(body)
292
+ end
293
+
294
+ def commit(action, post, id = nil)
295
+ url = build_request_url(action, id)
296
+ post_headers = { 'Authorization' => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
297
+ response = parse(ssl_post(url, post_data(post), post_headers))
298
+
299
+ Response.new(
300
+ success_from(response),
301
+ message_from(response),
302
+ response,
303
+ authorization: authorization_from(response),
304
+ avs_result: AVSResult.new(code: response.dig('latest_payment_attempt', 'authentication_data', 'avs_result')),
305
+ cvv_result: CVVResult.new(response.dig('latest_payment_attempt', 'authentication_data', 'cvc_code')),
306
+ test: test?,
307
+ error_code: error_code_from(response)
308
+ )
309
+ end
310
+
311
+ def handle_response(response)
312
+ case response.code.to_i
313
+ when 200...300, 400, 404
314
+ response.body
315
+ else
316
+ raise ResponseError.new(response)
317
+ end
318
+ end
319
+
320
+ def post_data(post)
321
+ post.to_json
322
+ end
323
+
324
+ def success_from(response)
325
+ %w(REQUIRES_PAYMENT_METHOD SUCCEEDED RECEIVED REQUIRES_CAPTURE CANCELLED).include?(response['status'])
326
+ end
327
+
328
+ def message_from(response)
329
+ response.dig('latest_payment_attempt', 'status') || response['status'] || response['message']
330
+ end
331
+
332
+ def authorization_from(response)
333
+ response.dig('latest_payment_attempt', 'payment_intent_id')
334
+ end
335
+
336
+ def error_code_from(response)
337
+ response['provider_original_response_code'] || response['code'] unless success_from(response)
338
+ end
339
+ end
340
+ end
341
+ end
@@ -6,9 +6,10 @@ module ActiveMerchant #:nodoc:
6
6
 
7
7
  self.supported_countries = %w[AL AD AM AT AZ BY BE BA BG HR CY CZ DK EE FI FR DE GR HU IS IE IT KZ LV LI LT LU MK MT MD MC ME NL NO PL PT RO RU SM RS SK SI ES SE CH TR UA GB VA]
8
8
  self.default_currency = 'EUR'
9
- self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND)
9
+ self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND IQD JOD LYD)
10
10
  self.money_format = :cents
11
11
  self.supported_cardtypes = %i[visa master american_express discover diners_club jcb dankort maestro]
12
+ self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
12
13
 
13
14
  self.homepage_url = 'https://www.barclaycardsmartpay.com/'
14
15
  self.display_name = 'Barclaycard Smartpay'
@@ -483,7 +483,7 @@ module ActiveMerchant #:nodoc:
483
483
  return unless reason_type = options.dig(:stored_credential, :reason_type)
484
484
 
485
485
  case reason_type
486
- when 'recurring' || 'installment'
486
+ when 'recurring', 'installment'
487
487
  'Y'
488
488
  when 'unscheduled'
489
489
  'N'