activemerchant 1.125.0 → 1.126.0

Sign up to get free protection for your applications and to get access to all the features.
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'