activemerchant 1.133.0 → 1.137.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +240 -0
  3. data/lib/active_merchant/billing/check.rb +2 -2
  4. data/lib/active_merchant/billing/compatibility.rb +4 -4
  5. data/lib/active_merchant/billing/credit_card.rb +11 -8
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +4 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +59 -6
  8. data/lib/active_merchant/billing/gateway.rb +9 -0
  9. data/lib/active_merchant/billing/gateways/adyen.rb +162 -43
  10. data/lib/active_merchant/billing/gateways/airwallex.rb +26 -12
  11. data/lib/active_merchant/billing/gateways/alelo.rb +23 -5
  12. data/lib/active_merchant/billing/gateways/authorize_net.rb +43 -35
  13. data/lib/active_merchant/billing/gateways/authorize_net_arb.rb +10 -6
  14. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +1 -3
  15. data/lib/active_merchant/billing/gateways/axcessms.rb +6 -2
  16. data/lib/active_merchant/billing/gateways/banwire.rb +4 -2
  17. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +7 -3
  18. data/lib/active_merchant/billing/gateways/blue_pay.rb +13 -5
  19. data/lib/active_merchant/billing/gateways/blue_snap.rb +5 -5
  20. data/lib/active_merchant/billing/gateways/braintree/token_nonce.rb +65 -20
  21. data/lib/active_merchant/billing/gateways/braintree_blue.rb +226 -73
  22. data/lib/active_merchant/billing/gateways/braintree_orange.rb +1 -1
  23. data/lib/active_merchant/billing/gateways/card_connect.rb +5 -2
  24. data/lib/active_merchant/billing/gateways/card_stream.rb +4 -6
  25. data/lib/active_merchant/billing/gateways/cashnet.rb +1 -1
  26. data/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb +36 -0
  27. data/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb +316 -0
  28. data/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb +220 -0
  29. data/lib/active_merchant/billing/gateways/cecabank.rb +7 -240
  30. data/lib/active_merchant/billing/gateways/checkout_v2.rb +238 -34
  31. data/lib/active_merchant/billing/gateways/commerce_hub.rb +63 -6
  32. data/lib/active_merchant/billing/gateways/credorax.rb +3 -5
  33. data/lib/active_merchant/billing/gateways/cyber_source.rb +185 -47
  34. data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +102 -58
  35. data/lib/active_merchant/billing/gateways/d_local.rb +26 -15
  36. data/lib/active_merchant/billing/gateways/data_cash.rb +21 -17
  37. data/lib/active_merchant/billing/gateways/datatrans.rb +279 -0
  38. data/lib/active_merchant/billing/gateways/decidir.rb +53 -18
  39. data/lib/active_merchant/billing/gateways/decidir_plus.rb +4 -1
  40. data/lib/active_merchant/billing/gateways/deepstack.rb +382 -0
  41. data/lib/active_merchant/billing/gateways/ebanx.rb +40 -36
  42. data/lib/active_merchant/billing/gateways/efsnet.rb +6 -2
  43. data/lib/active_merchant/billing/gateways/elavon.rb +99 -33
  44. data/lib/active_merchant/billing/gateways/element.rb +36 -7
  45. data/lib/active_merchant/billing/gateways/epay.rb +6 -2
  46. data/lib/active_merchant/billing/gateways/evo_ca.rb +6 -2
  47. data/lib/active_merchant/billing/gateways/eway.rb +4 -2
  48. data/lib/active_merchant/billing/gateways/eway_managed.rb +6 -2
  49. data/lib/active_merchant/billing/gateways/exact.rb +6 -2
  50. data/lib/active_merchant/billing/gateways/fat_zebra.rb +31 -3
  51. data/lib/active_merchant/billing/gateways/federated_canada.rb +6 -2
  52. data/lib/active_merchant/billing/gateways/first_pay/first_pay_common.rb +15 -0
  53. data/lib/active_merchant/billing/gateways/first_pay/first_pay_json.rb +190 -0
  54. data/lib/active_merchant/billing/gateways/first_pay/first_pay_xml.rb +183 -0
  55. data/lib/active_merchant/billing/gateways/first_pay.rb +6 -172
  56. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +6 -2
  57. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +7 -3
  58. data/lib/active_merchant/billing/gateways/flex_charge.rb +347 -0
  59. data/lib/active_merchant/billing/gateways/garanti.rb +4 -2
  60. data/lib/active_merchant/billing/gateways/global_collect.rb +45 -37
  61. data/lib/active_merchant/billing/gateways/hi_pay.rb +286 -0
  62. data/lib/active_merchant/billing/gateways/hps.rb +1 -1
  63. data/lib/active_merchant/billing/gateways/iats_payments.rb +7 -2
  64. data/lib/active_merchant/billing/gateways/inspire.rb +6 -4
  65. data/lib/active_merchant/billing/gateways/instapay.rb +7 -4
  66. data/lib/active_merchant/billing/gateways/ipg.rb +9 -5
  67. data/lib/active_merchant/billing/gateways/iridium.rb +15 -5
  68. data/lib/active_merchant/billing/gateways/itransact.rb +6 -2
  69. data/lib/active_merchant/billing/gateways/iveri.rb +3 -3
  70. data/lib/active_merchant/billing/gateways/ixopay.rb +2 -2
  71. data/lib/active_merchant/billing/gateways/jetpay.rb +4 -2
  72. data/lib/active_merchant/billing/gateways/jetpay_v2.rb +4 -2
  73. data/lib/active_merchant/billing/gateways/kushki.rb +72 -12
  74. data/lib/active_merchant/billing/gateways/linkpoint.rb +6 -2
  75. data/lib/active_merchant/billing/gateways/litle.rb +33 -50
  76. data/lib/active_merchant/billing/gateways/mastercard.rb +4 -4
  77. data/lib/active_merchant/billing/gateways/maxipago.rb +2 -2
  78. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +8 -5
  79. data/lib/active_merchant/billing/gateways/merchant_ware.rb +11 -4
  80. data/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb +11 -4
  81. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +19 -3
  82. data/lib/active_merchant/billing/gateways/mercury.rb +6 -2
  83. data/lib/active_merchant/billing/gateways/metrics_global.rb +8 -6
  84. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +1 -0
  85. data/lib/active_merchant/billing/gateways/migs.rb +6 -2
  86. data/lib/active_merchant/billing/gateways/mit.rb +8 -3
  87. data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +18 -10
  88. data/lib/active_merchant/billing/gateways/monei.rb +1 -1
  89. data/lib/active_merchant/billing/gateways/moneris.rb +9 -3
  90. data/lib/active_merchant/billing/gateways/money_movers.rb +6 -2
  91. data/lib/active_merchant/billing/gateways/nab_transact.rb +12 -4
  92. data/lib/active_merchant/billing/gateways/net_registry.rb +6 -2
  93. data/lib/active_merchant/billing/gateways/netbanx.rb +1 -3
  94. data/lib/active_merchant/billing/gateways/netbilling.rb +6 -2
  95. data/lib/active_merchant/billing/gateways/network_merchants.rb +6 -2
  96. data/lib/active_merchant/billing/gateways/nmi.rb +18 -6
  97. data/lib/active_merchant/billing/gateways/ogone.rb +6 -2
  98. data/lib/active_merchant/billing/gateways/openpay.rb +4 -2
  99. data/lib/active_merchant/billing/gateways/opp.rb +1 -2
  100. data/lib/active_merchant/billing/gateways/optimal_payment.rb +6 -2
  101. data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +1 -3
  102. data/lib/active_merchant/billing/gateways/orbital.rb +83 -24
  103. data/lib/active_merchant/billing/gateways/pac_net_raven.rb +7 -4
  104. data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +6 -2
  105. data/lib/active_merchant/billing/gateways/pay_hub.rb +4 -2
  106. data/lib/active_merchant/billing/gateways/pay_junction.rb +6 -2
  107. data/lib/active_merchant/billing/gateways/pay_secure.rb +6 -2
  108. data/lib/active_merchant/billing/gateways/pay_trace.rb +31 -18
  109. data/lib/active_merchant/billing/gateways/payeezy.rb +19 -8
  110. data/lib/active_merchant/billing/gateways/payex.rb +4 -2
  111. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -1
  112. data/lib/active_merchant/billing/gateways/payflow.rb +1 -3
  113. data/lib/active_merchant/billing/gateways/payment_express.rb +8 -4
  114. data/lib/active_merchant/billing/gateways/paymentez.rb +23 -11
  115. data/lib/active_merchant/billing/gateways/paysafe.rb +12 -11
  116. data/lib/active_merchant/billing/gateways/payscout.rb +7 -4
  117. data/lib/active_merchant/billing/gateways/paystation.rb +7 -3
  118. data/lib/active_merchant/billing/gateways/payway.rb +6 -2
  119. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +2 -2
  120. data/lib/active_merchant/billing/gateways/pin.rb +22 -4
  121. data/lib/active_merchant/billing/gateways/plexo.rb +49 -10
  122. data/lib/active_merchant/billing/gateways/plugnpay.rb +6 -2
  123. data/lib/active_merchant/billing/gateways/priority.rb +6 -5
  124. data/lib/active_merchant/billing/gateways/psigate.rb +6 -2
  125. data/lib/active_merchant/billing/gateways/psl_card.rb +6 -2
  126. data/lib/active_merchant/billing/gateways/qbms.rb +6 -2
  127. data/lib/active_merchant/billing/gateways/quantum.rb +6 -2
  128. data/lib/active_merchant/billing/gateways/quickbooks.rb +6 -5
  129. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +7 -4
  130. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb +6 -2
  131. data/lib/active_merchant/billing/gateways/rapyd.rb +148 -46
  132. data/lib/active_merchant/billing/gateways/reach.rb +11 -4
  133. data/lib/active_merchant/billing/gateways/redsys.rb +2 -10
  134. data/lib/active_merchant/billing/gateways/redsys_rest.rb +507 -0
  135. data/lib/active_merchant/billing/gateways/s5.rb +3 -3
  136. data/lib/active_merchant/billing/gateways/safe_charge.rb +36 -16
  137. data/lib/active_merchant/billing/gateways/sage.rb +12 -4
  138. data/lib/active_merchant/billing/gateways/sage_pay.rb +79 -5
  139. data/lib/active_merchant/billing/gateways/sallie_mae.rb +6 -2
  140. data/lib/active_merchant/billing/gateways/secure_net.rb +6 -2
  141. data/lib/active_merchant/billing/gateways/secure_pay.rb +8 -6
  142. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +12 -4
  143. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +6 -2
  144. data/lib/active_merchant/billing/gateways/securion_pay.rb +24 -10
  145. data/lib/active_merchant/billing/gateways/shift4.rb +17 -20
  146. data/lib/active_merchant/billing/gateways/shift4_v2.rb +117 -0
  147. data/lib/active_merchant/billing/gateways/simetrik.rb +17 -11
  148. data/lib/active_merchant/billing/gateways/skip_jack.rb +6 -2
  149. data/lib/active_merchant/billing/gateways/smart_ps.rb +7 -4
  150. data/lib/active_merchant/billing/gateways/so_easy_pay.rb +4 -2
  151. data/lib/active_merchant/billing/gateways/spreedly_core.rb +2 -4
  152. data/lib/active_merchant/billing/gateways/stripe.rb +53 -21
  153. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +199 -50
  154. data/lib/active_merchant/billing/gateways/sum_up.rb +223 -0
  155. data/lib/active_merchant/billing/gateways/swipe_checkout.rb +4 -2
  156. data/lib/active_merchant/billing/gateways/telr.rb +3 -4
  157. data/lib/active_merchant/billing/gateways/trans_first.rb +1 -2
  158. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +8 -16
  159. data/lib/active_merchant/billing/gateways/transact_pro.rb +1 -1
  160. data/lib/active_merchant/billing/gateways/trust_commerce.rb +6 -2
  161. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +9 -8
  162. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +6 -2
  163. data/lib/active_merchant/billing/gateways/vanco.rb +2 -4
  164. data/lib/active_merchant/billing/gateways/vantiv_express.rb +587 -0
  165. data/lib/active_merchant/billing/gateways/verifi.rb +6 -2
  166. data/lib/active_merchant/billing/gateways/viaklix.rb +6 -2
  167. data/lib/active_merchant/billing/gateways/visanet_peru.rb +2 -2
  168. data/lib/active_merchant/billing/gateways/vpos.rb +3 -3
  169. data/lib/active_merchant/billing/gateways/wirecard.rb +7 -3
  170. data/lib/active_merchant/billing/gateways/wompi.rb +5 -0
  171. data/lib/active_merchant/billing/gateways/worldpay.rb +140 -73
  172. data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +13 -10
  173. data/lib/active_merchant/billing/gateways/xpay.rb +242 -0
  174. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  175. data/lib/active_merchant/billing/response.rb +2 -2
  176. data/lib/active_merchant/connection.rb +3 -17
  177. data/lib/active_merchant/country.rb +1 -0
  178. data/lib/active_merchant/errors.rb +10 -0
  179. data/lib/active_merchant/version.rb +1 -1
  180. data/lib/support/gateway_support.rb +2 -2
  181. data/lib/support/ssl_verify.rb +4 -4
  182. data/lib/support/ssl_version.rb +6 -6
  183. metadata +30 -9
@@ -55,6 +55,7 @@ module ActiveMerchant #:nodoc:
55
55
  add_invoice(post, money, options)
56
56
  add_transaction_details(post, options, 'capture')
57
57
  add_reference_transaction_details(post, authorization, options, :capture)
58
+ add_dynamic_descriptors(post, options)
58
59
 
59
60
  commit('sale', post, options)
60
61
  end
@@ -68,6 +69,15 @@ module ActiveMerchant #:nodoc:
68
69
  commit('refund', post, options)
69
70
  end
70
71
 
72
+ def credit(money, payment_method, options = {})
73
+ post = {}
74
+ add_invoice(post, money, options)
75
+ add_transaction_interaction(post, options)
76
+ add_payment(post, payment_method, options)
77
+
78
+ commit('refund', post, options)
79
+ end
80
+
71
81
  def void(authorization, options = {})
72
82
  post = {}
73
83
  add_transaction_details(post, options)
@@ -102,6 +112,7 @@ module ActiveMerchant #:nodoc:
102
112
  transcript.
103
113
  gsub(%r((Authorization: )[a-zA-Z0-9+./=]+), '\1[FILTERED]').
104
114
  gsub(%r((Api-Key: )\w+), '\1[FILTERED]').
115
+ gsub(%r(("apiKey\\?":\\?")\w+), '\1[FILTERED]').
105
116
  gsub(%r(("cardData\\?":\\?")\d+), '\1[FILTERED]').
106
117
  gsub(%r(("securityCode\\?":\\?")\d+), '\1[FILTERED]').
107
118
  gsub(%r(("cavv\\?":\\?")\w+), '\1[FILTERED]')
@@ -109,6 +120,23 @@ module ActiveMerchant #:nodoc:
109
120
 
110
121
  private
111
122
 
123
+ def add_three_d_secure(post, payment, options)
124
+ return unless three_d_secure = options[:three_d_secure]
125
+
126
+ post[:additionalData3DS] = {
127
+ dsTransactionId: three_d_secure[:ds_transaction_id],
128
+ authenticationStatus: three_d_secure[:authentication_response_status],
129
+ serviceProviderTransactionId: three_d_secure[:three_ds_server_trans_id],
130
+ acsTransactionId: three_d_secure[:acs_transaction_id],
131
+ mpiData: {
132
+ cavv: three_d_secure[:cavv],
133
+ eci: three_d_secure[:eci],
134
+ xid: three_d_secure[:xid]
135
+ }.compact,
136
+ versionData: { recommendedVersion: three_d_secure[:version] }
137
+ }.compact
138
+ end
139
+
112
140
  def add_transaction_interaction(post, options)
113
141
  post[:transactionInteraction] = {}
114
142
  post[:transactionInteraction][:origin] = options[:origin] || 'ECOM'
@@ -144,10 +172,7 @@ module ActiveMerchant #:nodoc:
144
172
  return unless billing = options[:billing_address]
145
173
 
146
174
  billing_address = {}
147
- if payment.is_a?(CreditCard)
148
- billing_address[:firstName] = payment.first_name if payment.first_name
149
- billing_address[:lastName] = payment.last_name if payment.last_name
150
- end
175
+ name_from_address(billing_address, billing) || name_from_payment(billing_address, payment)
151
176
  address = {}
152
177
  address[:street] = billing[:address1] if billing[:address1]
153
178
  address[:houseNumberOrName] = billing[:address2] if billing[:address2]
@@ -165,6 +190,22 @@ module ActiveMerchant #:nodoc:
165
190
  post[:billingAddress] = billing_address
166
191
  end
167
192
 
193
+ def name_from_payment(billing_address, payment)
194
+ return unless payment.respond_to?(:first_name) && payment.respond_to?(:last_name)
195
+
196
+ billing_address[:firstName] = payment.first_name if payment.first_name
197
+ billing_address[:lastName] = payment.last_name if payment.last_name
198
+ end
199
+
200
+ def name_from_address(billing_address, billing)
201
+ return unless address = billing
202
+
203
+ first_name, last_name = split_names(address[:name]) if address[:name]
204
+
205
+ billing_address[:firstName] = first_name if first_name
206
+ billing_address[:lastName] = last_name if last_name
207
+ end
208
+
168
209
  def add_shipping_address(post, options)
169
210
  return unless shipping = options[:shipping_address]
170
211
 
@@ -187,12 +228,28 @@ module ActiveMerchant #:nodoc:
187
228
  end
188
229
 
189
230
  def build_purchase_and_auth_request(post, money, payment, options)
231
+ add_three_d_secure(post, payment, options)
190
232
  add_invoice(post, money, options)
191
233
  add_payment(post, payment, options)
192
234
  add_stored_credentials(post, options)
193
235
  add_transaction_interaction(post, options)
194
236
  add_billing_address(post, payment, options)
195
237
  add_shipping_address(post, options)
238
+ add_dynamic_descriptors(post, options)
239
+ end
240
+
241
+ def add_dynamic_descriptors(post, options)
242
+ dynamic_descriptors_fields = %i[mcc merchant_name customer_service_number service_entitlement dynamic_descriptors_address]
243
+ return unless dynamic_descriptors_fields.any? { |key| options.include?(key) }
244
+
245
+ dynamic_descriptors = {}
246
+ dynamic_descriptors[:mcc] = options[:mcc] if options[:mcc]
247
+ dynamic_descriptors[:merchantName] = options[:merchant_name] if options[:merchant_name]
248
+ dynamic_descriptors[:customerServiceNumber] = options[:customer_service_number] if options[:customer_service_number]
249
+ dynamic_descriptors[:serviceEntitlement] = options[:service_entitlement] if options[:service_entitlement]
250
+ dynamic_descriptors[:address] = options[:dynamic_descriptors_address] if options[:dynamic_descriptors_address]
251
+
252
+ post[:dynamicDescriptors] = dynamic_descriptors
196
253
  end
197
254
 
198
255
  def add_reference_transaction_details(post, authorization, options, action = nil)
@@ -282,7 +339,7 @@ module ActiveMerchant #:nodoc:
282
339
  raw_signature = @options[:api_key] + client_request_id.to_s + time + request
283
340
  hmac = OpenSSL::HMAC.digest('sha256', @options[:api_secret], raw_signature)
284
341
  signature = Base64.strict_encode64(hmac.to_s).to_s
285
-
342
+ custom_headers = options.fetch(:headers_identifiers, {})
286
343
  {
287
344
  'Client-Request-Id' => client_request_id,
288
345
  'Api-Key' => @options[:api_key],
@@ -292,7 +349,7 @@ module ActiveMerchant #:nodoc:
292
349
  'Content-Type' => 'application/json',
293
350
  'Accept' => 'application/json',
294
351
  'Authorization' => signature
295
- }
352
+ }.merge!(custom_headers)
296
353
  end
297
354
 
298
355
  def add_merchant_details(post)
@@ -254,9 +254,7 @@ module ActiveMerchant #:nodoc:
254
254
  normalized_value = normalize(value)
255
255
  next if normalized_value.nil?
256
256
 
257
- if key == :'3ds_homephonecountry'
258
- next unless options[:billing_address] && options[:billing_address][:phone]
259
- end
257
+ next if key == :'3ds_homephonecountry' && !(options[:billing_address] && options[:billing_address][:phone])
260
258
 
261
259
  post[key] = normalized_value unless post[key]
262
260
  end
@@ -417,7 +415,7 @@ module ActiveMerchant #:nodoc:
417
415
  three_d_secure_options[:eci],
418
416
  three_d_secure_options[:cavv]
419
417
  )
420
- post[:'3ds_version'] = three_d_secure_options[:version]&.start_with?('2') ? '2.0' : three_d_secure_options[:version]
418
+ post[:'3ds_version'] = three_d_secure_options[:version] == '2' ? '2.0' : three_d_secure_options[:version]
421
419
  post[:'3ds_dstrxid'] = three_d_secure_options[:ds_transaction_id]
422
420
  end
423
421
 
@@ -507,7 +505,7 @@ module ActiveMerchant #:nodoc:
507
505
  end
508
506
 
509
507
  def parse(body)
510
- Hash[CGI::parse(body).map { |k, v| [k.upcase, v.first] }]
508
+ CGI::parse(body).map { |k, v| [k.upcase, v.first] }.to_h
511
509
  end
512
510
 
513
511
  def success_from(response)
@@ -33,6 +33,15 @@ module ActiveMerchant #:nodoc:
33
33
  discover: 'pb',
34
34
  diners_club: 'pb'
35
35
  }.freeze
36
+ THREEDS_EXEMPTIONS = {
37
+ authentication_outage: 'authenticationOutageExemptionIndicator',
38
+ corporate_card: 'secureCorporatePaymentIndicator',
39
+ delegated_authentication: 'delegatedAuthenticationExemptionIndicator',
40
+ low_risk: 'riskAnalysisExemptionIndicator',
41
+ low_value: 'lowValueExemptionIndicator',
42
+ stored_credential: 'stored_credential',
43
+ trusted_merchant: 'trustedMerchantExemptionIndicator'
44
+ }
36
45
  DEFAULT_COLLECTION_INDICATOR = 2
37
46
 
38
47
  self.supported_cardtypes = %i[visa master american_express discover diners_club jcb dankort maestro elo]
@@ -53,7 +62,8 @@ module ActiveMerchant #:nodoc:
53
62
  jcb: '007',
54
63
  dankort: '034',
55
64
  maestro: '042',
56
- elo: '054'
65
+ elo: '054',
66
+ carnet: '058'
57
67
  }
58
68
 
59
69
  @@decision_codes = {
@@ -132,11 +142,16 @@ module ActiveMerchant #:nodoc:
132
142
  r703: 'Export hostname_country/ip_country match'
133
143
  }
134
144
 
135
- @@payment_solution = {
145
+ @@wallet_payment_solution = {
136
146
  apple_pay: '001',
137
147
  google_pay: '012'
138
148
  }
139
149
 
150
+ NT_PAYMENT_SOLUTION = {
151
+ 'master' => '014',
152
+ 'visa' => '015'
153
+ }
154
+
140
155
  # These are the options that can be used when creating a new CyberSource
141
156
  # Gateway object.
142
157
  #
@@ -161,9 +176,15 @@ module ActiveMerchant #:nodoc:
161
176
  super
162
177
  end
163
178
 
164
- def authorize(money, creditcard_or_reference, options = {})
165
- setup_address_hash(options)
166
- commit(build_auth_request(money, creditcard_or_reference, options), :authorize, money, options)
179
+ def authorize(money, payment_method, options = {})
180
+ if valid_payment_method?(payment_method)
181
+ setup_address_hash(options)
182
+ commit(build_auth_request(money, payment_method, options), :authorize, money, options)
183
+ else
184
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
185
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
186
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
187
+ end
167
188
  end
168
189
 
169
190
  def capture(money, authorization, options = {})
@@ -171,9 +192,15 @@ module ActiveMerchant #:nodoc:
171
192
  commit(build_capture_request(money, authorization, options), :capture, money, options)
172
193
  end
173
194
 
174
- def purchase(money, payment_method_or_reference, options = {})
175
- setup_address_hash(options)
176
- commit(build_purchase_request(money, payment_method_or_reference, options), :purchase, money, options)
195
+ def purchase(money, payment_method, options = {})
196
+ if valid_payment_method?(payment_method)
197
+ setup_address_hash(options)
198
+ commit(build_purchase_request(money, payment_method, options), :purchase, money, options)
199
+ else
200
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
201
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
202
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
203
+ end
177
204
  end
178
205
 
179
206
  def void(identification, options = {})
@@ -206,8 +233,14 @@ module ActiveMerchant #:nodoc:
206
233
  # To charge the card while creating a profile, pass
207
234
  # options[:setup_fee] => money
208
235
  def store(payment_method, options = {})
209
- setup_address_hash(options)
210
- commit(build_create_subscription_request(payment_method, options), :store, nil, options)
236
+ if valid_payment_method?(payment_method)
237
+ setup_address_hash(options)
238
+ commit(build_create_subscription_request(payment_method, options), :store, nil, options)
239
+ else
240
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
241
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
242
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
243
+ end
211
244
  end
212
245
 
213
246
  # Updates a customer subscription/profile
@@ -272,6 +305,8 @@ module ActiveMerchant #:nodoc:
272
305
  gsub(%r((<cvNumber>)[^<]*(</cvNumber>))i, '\1[FILTERED]\2').
273
306
  gsub(%r((<cavv>)[^<]*(</cavv>))i, '\1[FILTERED]\2').
274
307
  gsub(%r((<xid>)[^<]*(</xid>))i, '\1[FILTERED]\2').
308
+ gsub(%r((<networkTokenCryptogram>)[^<]*(</networkTokenCryptogram>))i, '\1[FILTERED]\2').
309
+ gsub(%r((<requestorID>)[^<]*(</requestorID>))i, '\1[FILTERED]\2').
275
310
  gsub(%r((<authenticationData>)[^<]*(</authenticationData>))i, '\1[FILTERED]\2')
276
311
  end
277
312
 
@@ -286,6 +321,12 @@ module ActiveMerchant #:nodoc:
286
321
 
287
322
  private
288
323
 
324
+ def valid_payment_method?(payment_method)
325
+ return true unless payment_method.is_a?(NetworkTokenizationCreditCard)
326
+
327
+ %w(visa master american_express).include?(card_brand(payment_method))
328
+ end
329
+
289
330
  # Create all required address hash key value pairs
290
331
  # If a value of nil is received, that value will be passed on to the gateway and will not be replaced with a default value
291
332
  # Billing address fields received without an override value or with an empty string value will be replaced with the default_address values
@@ -317,17 +358,19 @@ module ActiveMerchant #:nodoc:
317
358
  xml = Builder::XmlMarkup.new indent: 2
318
359
  add_customer_id(xml, options)
319
360
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
320
- add_other_tax(xml, options)
321
361
  add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
362
+ add_mastercard_network_tokenization_ucaf_data(xml, creditcard_or_reference, options)
322
363
  add_decision_manager_fields(xml, options)
364
+ add_other_tax(xml, options)
323
365
  add_mdd_fields(xml, options)
324
366
  add_auth_service(xml, creditcard_or_reference, options)
367
+ add_capture_service_fields_with_run_false(xml, options)
325
368
  add_threeds_services(xml, options)
326
369
  add_business_rules_data(xml, creditcard_or_reference, options)
327
370
  add_airline_data(xml, options)
328
371
  add_sales_slip_number(xml, options)
329
- add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
330
- add_payment_solution(xml, creditcard_or_reference.source) if network_tokenization?(creditcard_or_reference)
372
+ add_payment_network_token(xml, creditcard_or_reference, options)
373
+ add_payment_solution(xml, creditcard_or_reference)
331
374
  add_tax_management_indicator(xml, options)
332
375
  add_stored_credential_subsequent_auth(xml, options)
333
376
  add_issuer_additional_data(xml, options)
@@ -380,9 +423,10 @@ module ActiveMerchant #:nodoc:
380
423
  xml = Builder::XmlMarkup.new indent: 2
381
424
  add_customer_id(xml, options)
382
425
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
383
- add_other_tax(xml, options)
384
426
  add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
427
+ add_mastercard_network_tokenization_ucaf_data(xml, payment_method_or_reference, options)
385
428
  add_decision_manager_fields(xml, options)
429
+ add_other_tax(xml, options)
386
430
  add_mdd_fields(xml, options)
387
431
  if (!payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check') || reference_is_a_check?(payment_method_or_reference)
388
432
  add_check_service(xml)
@@ -398,8 +442,8 @@ module ActiveMerchant #:nodoc:
398
442
  add_business_rules_data(xml, payment_method_or_reference, options)
399
443
  add_airline_data(xml, options)
400
444
  add_sales_slip_number(xml, options)
401
- add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
402
- add_payment_solution(xml, payment_method_or_reference.source) if network_tokenization?(payment_method_or_reference)
445
+ add_payment_network_token(xml, payment_method_or_reference, options)
446
+ add_payment_solution(xml, payment_method_or_reference)
403
447
  add_tax_management_indicator(xml, options)
404
448
  add_stored_credential_subsequent_auth(xml, options)
405
449
  add_issuer_additional_data(xml, options)
@@ -487,7 +531,7 @@ module ActiveMerchant #:nodoc:
487
531
  add_check_service(xml)
488
532
  else
489
533
  add_purchase_service(xml, payment_method, options)
490
- add_payment_network_token(xml) if network_tokenization?(payment_method)
534
+ add_payment_network_token(xml, payment_method, options)
491
535
  end
492
536
  end
493
537
  add_subscription_create_service(xml, options)
@@ -556,7 +600,7 @@ module ActiveMerchant #:nodoc:
556
600
  end
557
601
 
558
602
  def add_merchant_data(xml, options)
559
- xml.tag! 'merchantID', @options[:login]
603
+ xml.tag! 'merchantID', options[:merchant_id] || @options[:login]
560
604
  xml.tag! 'merchantReferenceCode', options[:order_id] || generate_unique_id
561
605
  xml.tag! 'clientLibrary', 'Ruby Active Merchant'
562
606
  xml.tag! 'clientLibraryVersion', VERSION
@@ -566,12 +610,24 @@ module ActiveMerchant #:nodoc:
566
610
  end
567
611
 
568
612
  def add_merchant_descriptor(xml, options)
569
- return unless options[:merchant_descriptor] || options[:user_po] || options[:taxable] || options[:reference_data_code] || options[:invoice_number]
613
+ return unless options[:merchant_descriptor] ||
614
+ options[:user_po] ||
615
+ options[:taxable] ||
616
+ options[:reference_data_code] ||
617
+ options[:invoice_number] ||
618
+ options[:merchant_descriptor_city] ||
619
+ options[:submerchant_id] ||
620
+ options[:merchant_descriptor_state] ||
621
+ options[:merchant_descriptor_country]
570
622
 
571
623
  xml.tag! 'invoiceHeader' do
572
624
  xml.tag! 'merchantDescriptor', options[:merchant_descriptor] if options[:merchant_descriptor]
625
+ xml.tag! 'merchantDescriptorCity', options[:merchant_descriptor_city] if options[:merchant_descriptor_city]
626
+ xml.tag! 'merchantDescriptorState', options[:merchant_descriptor_state] if options[:merchant_descriptor_state]
627
+ xml.tag! 'merchantDescriptorCountry', options[:merchant_descriptor_country] if options[:merchant_descriptor_country]
573
628
  xml.tag! 'userPO', options[:user_po] if options[:user_po]
574
629
  xml.tag! 'taxable', options[:taxable] if options[:taxable]
630
+ xml.tag! 'submerchantID', options[:submerchant_id] if options[:submerchant_id]
575
631
  xml.tag! 'referenceDataCode', options[:reference_data_code] if options[:reference_data_code]
576
632
  xml.tag! 'invoiceNumber', options[:invoice_number] if options[:invoice_number]
577
633
  end
@@ -677,10 +733,16 @@ module ActiveMerchant #:nodoc:
677
733
  end
678
734
  end
679
735
 
680
- def add_payment_solution(xml, source)
681
- return unless (payment_solution = @@payment_solution[source])
736
+ def add_payment_solution(xml, payment_method)
737
+ return unless network_tokenization?(payment_method)
682
738
 
683
- xml.tag! 'paymentSolution', payment_solution
739
+ case payment_method.source
740
+ when :network_token
741
+ payment_solution = NT_PAYMENT_SOLUTION[payment_method.brand]
742
+ xml.tag! 'paymentSolution', payment_solution if payment_solution
743
+ when :apple_pay, :google_pay
744
+ xml.tag! 'paymentSolution', @@wallet_payment_solution[payment_method.source]
745
+ end
684
746
  end
685
747
 
686
748
  def add_issuer_additional_data(xml, options)
@@ -692,7 +754,7 @@ module ActiveMerchant #:nodoc:
692
754
  end
693
755
 
694
756
  def add_other_tax(xml, options)
695
- return unless options[:local_tax_amount] || options[:national_tax_amount] || options[:national_tax_indicator]
757
+ return unless %i[vat_tax_rate local_tax_amount national_tax_amount national_tax_indicator].any? { |gsf| options.include?(gsf) }
696
758
 
697
759
  xml.tag! 'otherTax' do
698
760
  xml.tag! 'vatTaxRate', options[:vat_tax_rate] if options[:vat_tax_rate]
@@ -731,21 +793,39 @@ module ActiveMerchant #:nodoc:
731
793
 
732
794
  def add_auth_service(xml, payment_method, options)
733
795
  if network_tokenization?(payment_method)
734
- add_auth_network_tokenization(xml, payment_method, options)
796
+ if payment_method.source == :network_token
797
+ add_auth_network_tokenization(xml, payment_method, options)
798
+ else
799
+ add_auth_wallet(xml, payment_method, options)
800
+ end
735
801
  else
736
802
  xml.tag! 'ccAuthService', { 'run' => 'true' } do
737
803
  if options[:three_d_secure]
738
804
  add_normalized_threeds_2_data(xml, payment_method, options)
805
+ add_threeds_exemption_data(xml, options) if options[:three_ds_exemption_type]
739
806
  else
740
807
  indicator = options[:commerce_indicator] || stored_credential_commerce_indicator(options)
741
808
  xml.tag!('commerceIndicator', indicator) if indicator
742
809
  end
810
+ xml.tag!('aggregatorID', options[:aggregator_id]) if options[:aggregator_id]
743
811
  xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
812
+ xml.tag!('firstRecurringPayment', options[:first_recurring_payment]) if options[:first_recurring_payment]
744
813
  xml.tag!('mobileRemotePaymentType', options[:mobile_remote_payment_type]) if options[:mobile_remote_payment_type]
745
814
  end
746
815
  end
747
816
  end
748
817
 
818
+ def add_threeds_exemption_data(xml, options)
819
+ return unless options[:three_ds_exemption_type]
820
+
821
+ exemption = options[:three_ds_exemption_type].to_sym
822
+
823
+ case exemption
824
+ when :authentication_outage, :corporate_card, :delegated_authentication, :low_risk, :low_value, :trusted_merchant
825
+ xml.tag!(THREEDS_EXEMPTIONS[exemption], '1')
826
+ end
827
+ end
828
+
749
829
  def add_incremental_auth_service(xml, authorization, options)
750
830
  xml.tag! 'ccIncrementalAuthService', { 'run' => 'true' } do
751
831
  xml.tag! 'authRequestID', authorization
@@ -793,13 +873,15 @@ module ActiveMerchant #:nodoc:
793
873
  end
794
874
 
795
875
  def stored_credential_commerce_indicator(options)
796
- return unless options[:stored_credential]
876
+ return unless (reason_type = options.dig(:stored_credential, :reason_type))
797
877
 
798
- return if options[:stored_credential][:initial_transaction]
799
-
800
- case options[:stored_credential][:reason_type]
801
- when 'installment' then 'install'
802
- when 'recurring' then 'recurring'
878
+ case reason_type
879
+ when 'installment'
880
+ 'install'
881
+ when 'recurring'
882
+ 'recurring'
883
+ else
884
+ 'internet'
803
885
  end
804
886
  end
805
887
 
@@ -807,26 +889,38 @@ module ActiveMerchant #:nodoc:
807
889
  payment_method.is_a?(NetworkTokenizationCreditCard)
808
890
  end
809
891
 
892
+ def subsequent_nt_apple_pay_auth(source, options)
893
+ return unless options[:stored_credential] || options[:stored_credential_overrides]
894
+ return unless @@wallet_payment_solution[source]
895
+
896
+ options.dig(:stored_credential_overrides, :subsequent_auth) || options.dig(:stored_credential, :initiator) == 'merchant'
897
+ end
898
+
810
899
  def add_auth_network_tokenization(xml, payment_method, options)
811
- return unless network_tokenization?(payment_method)
900
+ commerce_indicator = stored_credential_commerce_indicator(options) || 'internet'
901
+ xml.tag! 'ccAuthService', { 'run' => 'true' } do
902
+ xml.tag!('networkTokenCryptogram', payment_method.payment_cryptogram)
903
+ xml.tag!('commerceIndicator', commerce_indicator)
904
+ xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
905
+ end
906
+ end
907
+
908
+ def add_auth_wallet(xml, payment_method, options)
909
+ commerce_indicator = 'internet' if subsequent_nt_apple_pay_auth(payment_method.source, options)
812
910
 
813
911
  brand = card_brand(payment_method).to_sym
814
912
 
815
913
  case brand
816
914
  when :visa
817
915
  xml.tag! 'ccAuthService', { 'run' => 'true' } do
818
- xml.tag!('cavv', payment_method.payment_cryptogram)
819
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
820
- xml.tag!('xid', payment_method.payment_cryptogram)
916
+ xml.tag!('cavv', payment_method.payment_cryptogram) unless commerce_indicator
917
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
918
+ xml.tag!('xid', payment_method.payment_cryptogram) unless commerce_indicator
821
919
  xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
822
920
  end
823
921
  when :master
824
- xml.tag! 'ucaf' do
825
- xml.tag!('authenticationData', payment_method.payment_cryptogram)
826
- xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
827
- end
828
922
  xml.tag! 'ccAuthService', { 'run' => 'true' } do
829
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
923
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
830
924
  xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
831
925
  end
832
926
  when :american_express
@@ -837,14 +931,28 @@ module ActiveMerchant #:nodoc:
837
931
  xml.tag!('xid', Base64.encode64(cryptogram[20...40])) if cryptogram.bytes.count > 20
838
932
  xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
839
933
  end
840
- else
841
- raise ArgumentError.new("Payment method #{brand} is not supported, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
842
934
  end
843
935
  end
844
936
 
845
- def add_payment_network_token(xml)
937
+ def add_mastercard_network_tokenization_ucaf_data(xml, payment_method, options)
938
+ return unless network_tokenization?(payment_method) && card_brand(payment_method).to_sym == :master
939
+ return if payment_method.source == :network_token
940
+
941
+ commerce_indicator = 'internet' if subsequent_nt_apple_pay_auth(payment_method.source, options)
942
+
943
+ xml.tag! 'ucaf' do
944
+ xml.tag!('authenticationData', payment_method.payment_cryptogram) unless commerce_indicator
945
+ xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
946
+ end
947
+ end
948
+
949
+ def add_payment_network_token(xml, payment_method, options)
950
+ return unless network_tokenization?(payment_method)
951
+
952
+ transaction_type = payment_method.source == :network_token ? '3' : '1'
846
953
  xml.tag! 'paymentNetworkToken' do
847
- xml.tag!('transactionType', '1')
954
+ xml.tag!('requestorID', options[:trid]) if transaction_type == '3' && options[:trid]
955
+ xml.tag!('transactionType', transaction_type)
848
956
  end
849
957
  end
850
958
 
@@ -857,10 +965,19 @@ module ActiveMerchant #:nodoc:
857
965
  end
858
966
  end
859
967
 
968
+ def add_capture_service_fields_with_run_false(xml, options)
969
+ return unless options[:gratuity_amount]
970
+
971
+ xml.tag! 'ccCaptureService', { 'run' => 'false' } do
972
+ xml.tag! 'gratuityAmount', options[:gratuity_amount]
973
+ end
974
+ end
975
+
860
976
  def add_purchase_service(xml, payment_method, options)
861
977
  add_auth_service(xml, payment_method, options)
862
978
  xml.tag! 'ccCaptureService', { 'run' => 'true' } do
863
979
  xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
980
+ xml.tag!('gratuityAmount', options[:gratuity_amount]) if options[:gratuity_amount]
864
981
  end
865
982
  end
866
983
 
@@ -1003,9 +1120,9 @@ module ActiveMerchant #:nodoc:
1003
1120
  def add_stored_credential_options(xml, options = {})
1004
1121
  return unless options[:stored_credential] || options[:stored_credential_overrides]
1005
1122
 
1006
- stored_credential_subsequent_auth_first = 'true' if options.dig(:stored_credential, :initial_transaction)
1123
+ stored_credential_subsequent_auth_first = 'true' if cardholder_or_initiated_transaction?(options)
1007
1124
  stored_credential_transaction_id = options.dig(:stored_credential, :network_transaction_id) if options.dig(:stored_credential, :initiator) == 'merchant'
1008
- stored_credential_subsequent_auth_stored_cred = 'true' if options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction) || options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
1125
+ stored_credential_subsequent_auth_stored_cred = 'true' if subsequent_cardholder_initiated_transaction?(options) || unscheduled_merchant_initiated_transaction?(options) || threeds_stored_credential_exemption?(options)
1009
1126
 
1010
1127
  override_subsequent_auth_first = options.dig(:stored_credential_overrides, :subsequent_auth_first)
1011
1128
  override_subsequent_auth_transaction_id = options.dig(:stored_credential_overrides, :subsequent_auth_transaction_id)
@@ -1016,6 +1133,22 @@ module ActiveMerchant #:nodoc:
1016
1133
  xml.subsequentAuthStoredCredential override_subsequent_auth_stored_cred.nil? ? stored_credential_subsequent_auth_stored_cred : override_subsequent_auth_stored_cred
1017
1134
  end
1018
1135
 
1136
+ def cardholder_or_initiated_transaction?(options)
1137
+ options.dig(:stored_credential, :initiator) == 'cardholder' || options.dig(:stored_credential, :initial_transaction)
1138
+ end
1139
+
1140
+ def subsequent_cardholder_initiated_transaction?(options)
1141
+ options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction)
1142
+ end
1143
+
1144
+ def unscheduled_merchant_initiated_transaction?(options)
1145
+ options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
1146
+ end
1147
+
1148
+ def threeds_stored_credential_exemption?(options)
1149
+ options[:three_ds_exemption_type] == THREEDS_EXEMPTIONS[:stored_credential]
1150
+ end
1151
+
1019
1152
  def add_partner_solution_id(xml)
1020
1153
  return unless application_id
1021
1154
 
@@ -1068,12 +1201,16 @@ module ActiveMerchant #:nodoc:
1068
1201
 
1069
1202
  message = auto_void?(authorization_from(response, action, amount, options), response, message, options)
1070
1203
 
1071
- Response.new(success, message, response,
1204
+ Response.new(
1205
+ success,
1206
+ message,
1207
+ response,
1072
1208
  test: test?,
1073
1209
  authorization: authorization,
1074
1210
  fraud_review: in_fraud_review?(response),
1075
1211
  avs_result: { code: response[:avsCode] },
1076
- cvv_result: response[:cvCode])
1212
+ cvv_result: response[:cvCode]
1213
+ )
1077
1214
  end
1078
1215
 
1079
1216
  def auto_void?(authorization, response, message, options = {})
@@ -1115,6 +1252,7 @@ module ActiveMerchant #:nodoc:
1115
1252
  parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id']
1116
1253
  parent += '_'
1117
1254
  end
1255
+ reply[:reconciliationID2] = node.text if node.name == 'reconciliationID' && reply[:reconciliationID]
1118
1256
  reply["#{parent}#{node.name}".to_sym] ||= node.text
1119
1257
  end
1120
1258
  return reply