cybersource_rest_client 0.0.80 → 0.0.81

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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/lib/AuthenticationSDK/authentication/jwt/JwtToken.rb +17 -7
  3. data/lib/AuthenticationSDK/core/Authorization.rb +2 -2
  4. data/lib/AuthenticationSDK/core/MerchantConfig.rb +277 -23
  5. data/lib/AuthenticationSDK/util/Cache.rb +144 -8
  6. data/lib/AuthenticationSDK/util/CachedMLEKId.rb +17 -0
  7. data/lib/AuthenticationSDK/util/CertificateUtility.rb +109 -29
  8. data/lib/AuthenticationSDK/util/Constants.rb +4 -0
  9. data/lib/AuthenticationSDK/util/MLEUtility.rb +167 -11
  10. data/lib/AuthenticationSDK/util/Utility.rb +37 -1
  11. data/lib/cybersource_rest_client/api/bank_account_validation_api.rb +6 -2
  12. data/lib/cybersource_rest_client/api/batches_api.rb +24 -8
  13. data/lib/cybersource_rest_client/api/billing_agreements_api.rb +18 -6
  14. data/lib/cybersource_rest_client/api/bin_lookup_api.rb +6 -2
  15. data/lib/cybersource_rest_client/api/capture_api.rb +6 -2
  16. data/lib/cybersource_rest_client/api/chargeback_details_api.rb +6 -2
  17. data/lib/cybersource_rest_client/api/chargeback_summaries_api.rb +6 -2
  18. data/lib/cybersource_rest_client/api/conversion_details_api.rb +6 -2
  19. data/lib/cybersource_rest_client/api/create_new_webhooks_api.rb +18 -6
  20. data/lib/cybersource_rest_client/api/credit_api.rb +6 -2
  21. data/lib/cybersource_rest_client/api/customer_api.rb +24 -8
  22. data/lib/cybersource_rest_client/api/customer_payment_instrument_api.rb +30 -10
  23. data/lib/cybersource_rest_client/api/customer_shipping_address_api.rb +30 -10
  24. data/lib/cybersource_rest_client/api/decision_manager_api.rb +30 -10
  25. data/lib/cybersource_rest_client/api/device_de_association_api.rb +12 -4
  26. data/lib/cybersource_rest_client/api/device_search_api.rb +12 -4
  27. data/lib/cybersource_rest_client/api/download_dtd_api.rb +6 -2
  28. data/lib/cybersource_rest_client/api/download_xsd_api.rb +6 -2
  29. data/lib/cybersource_rest_client/api/emv_tag_details_api.rb +12 -4
  30. data/lib/cybersource_rest_client/api/enrollment_api.rb +104 -0
  31. data/lib/cybersource_rest_client/api/flex_api_api.rb +6 -2
  32. data/lib/cybersource_rest_client/api/instructions_api.rb +452 -0
  33. data/lib/cybersource_rest_client/api/instrument_identifier_api.rb +36 -12
  34. data/lib/cybersource_rest_client/api/interchange_clearing_level_details_api.rb +6 -2
  35. data/lib/cybersource_rest_client/api/invoice_settings_api.rb +12 -4
  36. data/lib/cybersource_rest_client/api/invoices_api.rb +42 -14
  37. data/lib/cybersource_rest_client/api/manage_webhooks_api.rb +42 -14
  38. data/lib/cybersource_rest_client/api/merchant_boarding_api.rb +12 -4
  39. data/lib/cybersource_rest_client/api/merchant_defined_fields_api.rb +24 -8
  40. data/lib/cybersource_rest_client/api/microform_integration_api.rb +6 -2
  41. data/lib/cybersource_rest_client/api/net_fundings_api.rb +6 -2
  42. data/lib/cybersource_rest_client/api/notification_of_changes_api.rb +6 -2
  43. data/lib/cybersource_rest_client/api/offers_api.rb +12 -4
  44. data/lib/cybersource_rest_client/api/orders_api.rb +12 -4
  45. data/lib/cybersource_rest_client/api/payer_authentication_api.rb +18 -6
  46. data/lib/cybersource_rest_client/api/payment_batch_summaries_api.rb +6 -2
  47. data/lib/cybersource_rest_client/api/payment_instrument_api.rb +24 -8
  48. data/lib/cybersource_rest_client/api/payment_links_api.rb +24 -8
  49. data/lib/cybersource_rest_client/api/payment_tokens_api.rb +6 -2
  50. data/lib/cybersource_rest_client/api/payments_api.rb +36 -12
  51. data/lib/cybersource_rest_client/api/payouts_api.rb +6 -2
  52. data/lib/cybersource_rest_client/api/plans_api.rb +48 -16
  53. data/lib/cybersource_rest_client/api/purchase_and_refund_details_api.rb +6 -2
  54. data/lib/cybersource_rest_client/api/push_funds_api.rb +6 -2
  55. data/lib/cybersource_rest_client/api/refund_api.rb +12 -4
  56. data/lib/cybersource_rest_client/api/report_definitions_api.rb +12 -4
  57. data/lib/cybersource_rest_client/api/report_downloads_api.rb +6 -2
  58. data/lib/cybersource_rest_client/api/report_subscriptions_api.rb +30 -10
  59. data/lib/cybersource_rest_client/api/reports_api.rb +18 -6
  60. data/lib/cybersource_rest_client/api/retrieval_details_api.rb +6 -2
  61. data/lib/cybersource_rest_client/api/retrieval_summaries_api.rb +6 -2
  62. data/lib/cybersource_rest_client/api/reversal_api.rb +12 -4
  63. data/lib/cybersource_rest_client/api/search_transactions_api.rb +12 -4
  64. data/lib/cybersource_rest_client/api/secure_file_share_api.rb +12 -4
  65. data/lib/cybersource_rest_client/api/subscriptions_api.rb +48 -16
  66. data/lib/cybersource_rest_client/api/subscriptions_follow_ons_api.rb +12 -4
  67. data/lib/cybersource_rest_client/api/taxes_api.rb +12 -4
  68. data/lib/cybersource_rest_client/api/token_api.rb +12 -4
  69. data/lib/cybersource_rest_client/api/tokenize_api.rb +6 -2
  70. data/lib/cybersource_rest_client/api/tokenized_card_api.rb +24 -8
  71. data/lib/cybersource_rest_client/api/transaction_batches_api.rb +24 -8
  72. data/lib/cybersource_rest_client/api/transaction_details_api.rb +6 -2
  73. data/lib/cybersource_rest_client/api/transient_token_data_api.rb +12 -4
  74. data/lib/cybersource_rest_client/api/unified_checkout_capture_context_api.rb +6 -2
  75. data/lib/cybersource_rest_client/api/user_management_api.rb +6 -2
  76. data/lib/cybersource_rest_client/api/user_management_search_api.rb +6 -2
  77. data/lib/cybersource_rest_client/api/verification_api.rb +12 -4
  78. data/lib/cybersource_rest_client/api/void_api.rb +30 -10
  79. data/lib/cybersource_rest_client/api_client.rb +20 -3
  80. data/lib/cybersource_rest_client/models/acpv1instructions_decline_threshold.rb +232 -0
  81. data/lib/cybersource_rest_client/models/acpv1instructions_mandates.rb +405 -0
  82. data/lib/cybersource_rest_client/models/acpv1instructions_recurring_payment_information.rb +191 -0
  83. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idconf_processor_info_payment_instrument_verifi_results.rb +213 -0
  84. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idconfirmations_confirmation_data.rb +229 -0
  85. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idconfirmations_merchant_information.rb +258 -0
  86. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idconfirmations_order_information.rb +377 -0
  87. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idconfirmations_order_information_shipping_details.rb +206 -0
  88. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idconfirmations_processor_information.rb +425 -0
  89. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idconfirmations_processor_information_payment_instrument.rb +190 -0
  90. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_attachments.rb +258 -0
  91. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_client_reference_information.rb +215 -0
  92. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_mandate_reference_data.rb +205 -0
  93. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_merchant_information.rb +334 -0
  94. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_merchant_information_merchant_descriptor.rb +250 -0
  95. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_order_information.rb +228 -0
  96. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_order_information_amount_detail.rb +354 -0
  97. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_order_information_items.rb +367 -0
  98. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_order_information_items_additional_info.rb +231 -0
  99. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_order_information_items_policies.rb +346 -0
  100. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_order_information_line_items.rb +190 -0
  101. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_order_information_ship_to.rb +686 -0
  102. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_payment_options.rb +217 -0
  103. data/lib/cybersource_rest_client/models/acpv1instructionsinstruction_idcredentials_transaction_data.rb +348 -0
  104. data/lib/cybersource_rest_client/models/acpv1tokens_assurance_data.rb +313 -0
  105. data/lib/cybersource_rest_client/models/acpv1tokens_authenticated_identities.rb +237 -0
  106. data/lib/cybersource_rest_client/models/acpv1tokens_authentication_context.rb +191 -0
  107. data/lib/cybersource_rest_client/models/acpv1tokens_bill_to.rb +400 -0
  108. data/lib/cybersource_rest_client/models/acpv1tokens_buyer_information.rb +229 -0
  109. data/lib/cybersource_rest_client/models/acpv1tokens_buyer_information_personal_identification.rb +212 -0
  110. data/lib/cybersource_rest_client/models/acpv1tokens_consent_data.rb +297 -0
  111. data/lib/cybersource_rest_client/models/acpv1tokens_consumer_identity.rb +268 -0
  112. data/lib/cybersource_rest_client/models/acpv1tokens_device_information.rb +378 -0
  113. data/lib/cybersource_rest_client/models/acpv1tokens_device_information_device_data.rb +302 -0
  114. data/lib/cybersource_rest_client/models/acpv1tokens_enrollment_reference_data.rb +207 -0
  115. data/lib/cybersource_rest_client/models/acpv1tokens_payment_information.rb +215 -0
  116. data/lib/cybersource_rest_client/models/acpv1tokens_payment_information_customer.rb +197 -0
  117. data/lib/cybersource_rest_client/models/acpv1tokens_payment_information_instrument_identifier.rb +206 -0
  118. data/lib/cybersource_rest_client/models/acpv1tokens_payment_information_payment_instrument.rb +197 -0
  119. data/lib/cybersource_rest_client/models/agentic_cancel_purchase_intent_request.rb +257 -0
  120. data/lib/cybersource_rest_client/models/agentic_card_enrollment_bad_request_response400.rb +189 -0
  121. data/lib/cybersource_rest_client/models/agentic_card_enrollment_bad_request_response400_error.rb +238 -0
  122. data/lib/cybersource_rest_client/models/agentic_card_enrollment_bad_request_response400_error_detail.rb +224 -0
  123. data/lib/cybersource_rest_client/models/agentic_card_enrollment_request.rb +325 -0
  124. data/lib/cybersource_rest_client/models/agentic_card_enrollment_response200.rb +206 -0
  125. data/lib/cybersource_rest_client/models/agentic_card_enrollment_response202.rb +219 -0
  126. data/lib/cybersource_rest_client/models/agentic_confirm_transaction_events_request.rb +247 -0
  127. data/lib/cybersource_rest_client/models/agentic_confirm_transaction_events_response202.rb +195 -0
  128. data/lib/cybersource_rest_client/models/agentic_create_purchase_intent_request.rb +327 -0
  129. data/lib/cybersource_rest_client/models/agentic_create_purchase_intent_response200.rb +222 -0
  130. data/lib/cybersource_rest_client/models/agentic_pending_purchase_intent_response202.rb +241 -0
  131. data/lib/cybersource_rest_client/models/agentic_retrieve_payment_credentials_request.rb +247 -0
  132. data/lib/cybersource_rest_client/models/agentic_retrieve_payment_credentials_response200.rb +200 -0
  133. data/lib/cybersource_rest_client/models/agentic_retrieve_payment_credentials_response200_transaction_response_complete.rb +244 -0
  134. data/lib/cybersource_rest_client/models/agentic_retrieve_payment_credentials_response200_transaction_response_with_pending_events.rb +235 -0
  135. data/lib/cybersource_rest_client/models/agentic_update_purchase_intent_request.rb +315 -0
  136. data/lib/cybersource_rest_client/models/boardingv1registrations_organization_information_business_information.rb +36 -1
  137. data/lib/cybersource_rest_client/models/boardingv1registrations_organization_information_business_information_localized_names.rb +254 -0
  138. data/lib/cybersource_rest_client/models/ptsv2payments_travel_information_transit_airline_ancillary_information.rb +22 -5
  139. data/lib/cybersource_rest_client/models/ptsv2payments_travel_information_transit_airline_ancillary_information_service.rb +39 -5
  140. data/lib/cybersource_rest_client/models/ptsv2paymentsidrefunds_travel_information.rb +246 -0
  141. data/lib/cybersource_rest_client/models/ptsv2paymentsidrefunds_travel_information_transit.rb +189 -0
  142. data/lib/cybersource_rest_client/models/ptsv2paymentsidrefunds_travel_information_transit_airline.rb +889 -0
  143. data/lib/cybersource_rest_client/models/ptsv2paymentsidrefunds_travel_information_transit_airline_ancillary_information.rb +259 -0
  144. data/lib/cybersource_rest_client/models/ptsv2paymentsidrefunds_travel_information_transit_airline_ancillary_information_service.rb +213 -0
  145. data/lib/cybersource_rest_client/models/refund_capture_request.rb +1 -1
  146. data/lib/cybersource_rest_client/models/refund_payment_request.rb +1 -1
  147. data/lib/cybersource_rest_client/models/upv1capturecontexts_data.rb +4 -2
  148. data/lib/cybersource_rest_client/models/upv1capturecontexts_data_merchant_defined_information.rb +3 -2
  149. data/lib/cybersource_rest_client/utilities/jwe_utility.rb +1 -1
  150. data/lib/cybersource_rest_client.rb +64 -0
  151. metadata +68 -3
  152. /data/lib/AuthenticationSDK/util/{JWEUtility.rb → AuthJWEUtility.rb} +0 -0
@@ -7,34 +7,6 @@ public
7
7
  class CertificateUtility
8
8
  @@logger
9
9
 
10
- def self.getCertificateCollectionAndPrivateKeyFromP12(certificateFilePath, merchantConfig)
11
- if !CertificateUtility.class_variable_defined?(:@@logger) || @@logger.nil?
12
- @@logger = Log.new merchantConfig.log_config, "CertificateUtility"
13
- end
14
- logger = @@logger.logger
15
-
16
- p12File = File.binread(certificateFilePath)
17
- p12Object = OpenSSL::PKCS12.new(p12File, merchantConfig.keyPass)
18
-
19
- privateKey = OpenSSL::PKey::RSA.new(p12Object.key)
20
-
21
- primaryX5Certificate = p12Object.certificate
22
- additionalX5Certificates = p12Object.ca_certs
23
-
24
- certificateList = [primaryX5Certificate]
25
- certificateList.concat(additionalX5Certificates) if additionalX5Certificates
26
-
27
- return [privateKey, certificateList]
28
- end
29
-
30
- def self.getCertificateBasedOnKeyAlias(certificateList, keyAlias)
31
- return nil if certificateList.nil?
32
-
33
- certificateList.find do |cert|
34
- cert.subject.to_a.any? { |_, value, _| value.include?(keyAlias) }
35
- end
36
- end
37
-
38
10
  def self.getCertificatesFromPemFile(certificateFilePath)
39
11
  pem_data = File.read(certificateFilePath)
40
12
  certificateList = []
@@ -121,4 +93,112 @@ public
121
93
  raise IOError, "#{pathType} is not readable: #{path}"
122
94
  end
123
95
  end
124
- end
96
+
97
+ def self.read_private_key_from_p12(p12_file_path, password)
98
+ begin
99
+ # `password` should be a String in Ruby
100
+ raise ArgumentError, "password must be a String" unless password.is_a?(String)
101
+
102
+ data = File.binread(p12_file_path)
103
+ pkcs12 = OpenSSL::PKCS12.new(data, password)
104
+
105
+ key = pkcs12.key
106
+ raise "No private key found in the P12 file" if key.nil?
107
+
108
+ jwk_private_key = self.convert_key_to_JWK(key)
109
+ return jwk_private_key
110
+ rescue OpenSSL::PKCS12::PKCS12Error => e
111
+ raise "Could not recover key from P12: #{e.message}"
112
+ rescue Errno::ENOENT => e
113
+ raise "P12 file not found: #{p12_file_path}"
114
+ end
115
+ end
116
+
117
+ def self.load_private_key_from_pem_file(key_file_path, password = nil)
118
+ begin
119
+ pem_data = File.binread(key_file_path)
120
+
121
+ # OpenSSL::PKey.read supports:
122
+ # - "BEGIN RSA/EC/PRIVATE KEY" (PKCS#1), encrypted or not (Proc-Type/DEK-Info)
123
+ # - "BEGIN PRIVATE KEY" (PKCS#8 unencrypted)
124
+ # - "BEGIN ENCRYPTED PRIVATE KEY" (PKCS#8 encrypted)
125
+ rsa_key = OpenSSL::PKey.read(pem_data, password)
126
+ jwk_private_key = self.convert_key_to_JWK(rsa_key)
127
+ return jwk_private_key
128
+ rescue OpenSSL::PKey::PKeyError => e
129
+ # Missing password for an encrypted PEM
130
+ if pem_data =~ /(BEGIN ENCRYPTED PRIVATE KEY|Proc-Type:\s*4,ENCRYPTED)/ && (password.nil? || password.to_s.empty?)
131
+ raise ArgumentError, "Private key is password protected, but no password was provided."
132
+ end
133
+
134
+ # Wrong password (common OpenSSL messages)
135
+ if password && e.message =~ /(bad decrypt|bad password|mac verify failure)/i
136
+ logger&.error("Failed to decrypt PKCS#8 private key - incorrect password provided")
137
+ raise ArgumentError, "Password is incorrect for the encrypted private key. Error: #{e.message}"
138
+ end
139
+
140
+ # Unsupported/invalid PEM contents
141
+ raise ArgumentError, "Unsupported PEM object or invalid key: #{e.message}"
142
+ rescue Errno::ENOENT
143
+ raise ArgumentError, "PEM file not found: #{key_file_path}"
144
+ end
145
+ end
146
+
147
+ def self.convert_key_to_JWK(keyValue, password=nil)
148
+ if !keyValue.nil?
149
+ case keyValue
150
+ when String
151
+ begin
152
+ if keyValue.encoding == Encoding::UTF_8
153
+ # This is for PEM formatted string
154
+ encrypted = keyValue.include?('BEGIN ENCRYPTED PRIVATE KEY')
155
+ pkey = nil
156
+
157
+ key = begin
158
+ if encrypted
159
+ if password.nil? || password.empty?
160
+ raise ArgumentError, "Encrypted PEM detected, but no password was provided."
161
+ end
162
+ pkey = OpenSSL::PKey.read(keyValue, password)
163
+ else
164
+ # Try without password first
165
+ pkey = OpenSSL::PKey.read(keyValue)
166
+ end
167
+ rescue OpenSSL::PKey::PKeyError
168
+ # If initial attempt failed and a password was provided, retry with password
169
+ if !password.nil? && !password.empty?
170
+ begin
171
+ pkey = OpenSSL::PKey.read(keyValue, password)
172
+ rescue OpenSSL::PKey::PKeyError => e
173
+ raise "Failed to load PEM private key. Incorrect password or corrupted/unsupported format. OpenSSL: #{e.message}"
174
+ end
175
+ else
176
+ raise "Failed to load PEM private key. Invalid key format or password required."
177
+ end
178
+ end
179
+ keyValue = JOSE::JWK.from_key(pkey)
180
+ else
181
+ # This is for P12 formatted string
182
+ begin
183
+ if !password.nil? && !password.empty?
184
+ pkey = OpenSSL::PKCS12.new(keyValue, password)
185
+ else
186
+ pkey = OpenSSL::PKCS12.new(keyValue)
187
+ end
188
+ rescue OpenSSL::PKCS12::PKCS12Error => e
189
+ raise "Could not recover key from P12 data: #{e.message}"
190
+ end
191
+
192
+ key = pkey.key
193
+ raise "No private key found in the P12 data" if key.nil?
194
+ keyValue = JOSE::JWK.from_key(key)
195
+ end
196
+ end
197
+ when OpenSSL::PKey::RSA
198
+ keyValue = JOSE::JWK.from_pem(keyValue.to_pem)
199
+ else
200
+ keyValue = JOSE::JWK.from_key(keyValue)
201
+ end
202
+ end
203
+ end
204
+ end
@@ -176,5 +176,9 @@
176
176
 
177
177
  DEFAULT_KEY_FILE_PATH = File.join(Dir.pwd, "resources")
178
178
 
179
+ MLE_CACHE_KEY_IDENTIFIER_FOR_RESPONSE_PRIVATE_KEY = "mleResponsePrivateKeyFromFile"
180
+
179
181
  PUBLIC_KEY_CACHE_IDENTIFIER = "FlexV2PublicKeys"
182
+
183
+ RESPONSE_MLE_P12_PFX_CACHE_IDENTIFIER = "_responseMleP12Pfx"
180
184
  end
@@ -1,6 +1,10 @@
1
1
  require_relative '../logging/log_factory.rb'
2
2
  require 'jose'
3
+ require 'json'
3
4
  require_relative './Cache'
5
+ require_relative './Constants'
6
+ require_relative './ExceptionHandler'
7
+ require_relative './AuthJWEUtility'
4
8
 
5
9
  public
6
10
  class MLEUtility
@@ -16,10 +20,10 @@ public
16
20
  is_mle_for_api = !merchant_config.disableRequestMLEForMandatoryApisGlobally
17
21
  end
18
22
 
19
- if merchant_config.mapToControlMLEonAPI && operation_ids
23
+ if !merchant_config.internalMapToControlRequestMLEonAPI.nil? && !merchant_config.internalMapToControlRequestMLEonAPI.empty? && operation_ids
20
24
  operation_ids.each do |operation_id|
21
- if merchant_config.mapToControlMLEonAPI.key?(operation_id)
22
- is_mle_for_api = merchant_config.mapToControlMLEonAPI[operation_id]
25
+ if merchant_config.internalMapToControlRequestMLEonAPI.key?(operation_id)
26
+ is_mle_for_api = merchant_config.internalMapToControlRequestMLEonAPI[operation_id]
23
27
  break
24
28
  end
25
29
  end
@@ -45,11 +49,7 @@ public
45
49
  end
46
50
 
47
51
  begin
48
- serial_number = extract_serial_number_from_certificate(mleCertificate)
49
- if serial_number.nil?
50
- @log_obj.logger.error('Serial number not found in certificate for MLE')
51
- raise StandardError.new('Serial number not found in MLE certificate')
52
- end
52
+ serial_number = self.extract_serial_number_from_certificate(mleCertificate)
53
53
 
54
54
  jwk = JOSE::JWK.from_key(mleCertificate.public_key)
55
55
  if jwk.nil?
@@ -66,7 +66,7 @@ public
66
66
  jwe = JOSE::JWE.block_encrypt(jwk, requestBody, headers)
67
67
 
68
68
  compact_jwe = jwe.compact
69
- mle_request_body = create_request_payload(compact_jwe)
69
+ mle_request_body = self.create_request_payload(compact_jwe)
70
70
  @log_obj.logger.debug('LOG_REQUEST_AFTER_MLE: ' + mle_request_body)
71
71
  return mle_request_body
72
72
  rescue StandardError => e
@@ -76,14 +76,170 @@ public
76
76
  end
77
77
 
78
78
  def self.extract_serial_number_from_certificate(certificate)
79
- return nil if certificate.subject.to_s.empty? && certificate.issuer.to_s.empty?
79
+ raise StandardError.new('Certificate cannot be nil') if certificate.nil?
80
+ raise StandardError.new('Certificate subject and issuer cannot both be empty') if certificate.subject.to_s.empty? && certificate.issuer.to_s.empty?
80
81
  certificate.subject.to_a.each do |attribute|
81
82
  return attribute[1] if attribute[0].include?('serialNumber')
82
83
  end
83
- certificate.serial.nil? ? nil : certificate.serial.to_s
84
+ raise StandardError.new('Serial number not found in certificate subject')
84
85
  end
85
86
 
86
87
  def self.create_request_payload(compact_jwe)
87
88
  "{ \"encryptedRequest\": \"#{compact_jwe}\" }"
88
89
  end
90
+
91
+ def self.check_is_response_mle_for_api(merchant_config, operation_ids)
92
+ isResponseMLEForApi = false
93
+
94
+ if merchant_config.enableResponseMleGlobally
95
+ isResponseMLEForApi = true
96
+ end
97
+
98
+ # operation_ids is an array of the multiple public function for apiCallFunction such as apiCall, apiCallAsync
99
+ # Control the Response MLE only from map
100
+ # Special Note: If API expect MLE Response mandatory and map is saying to receive non MLE response then API might throw an error from CyberSource
101
+ if merchant_config.internalMapToControlResponseMLEonAPI
102
+ operation_ids.each do |operation_id|
103
+ if merchant_config.internalMapToControlResponseMLEonAPI.key?(operation_id)
104
+ isResponseMLEForApi = merchant_config.internalMapToControlResponseMLEonAPI[operation_id]
105
+ break
106
+ end
107
+ end
108
+ end
109
+
110
+ isResponseMLEForApi
111
+ end
112
+
113
+ def self.check_is_mle_encrypted_response(responseBody)
114
+ return false if responseBody.nil? || responseBody.strip.empty?
115
+
116
+ begin
117
+ jsonObject = JSON.parse(responseBody)
118
+ return false unless jsonObject.is_a?(Hash) && jsonObject.size == 1
119
+
120
+ jsonObject.key?('encryptedResponse') && jsonObject['encryptedResponse'].is_a?(String)
121
+ rescue JSON::ParserError, TypeError
122
+ false
123
+ end
124
+ end
125
+
126
+ def self.decrypt_mle_response_payload(merchantConfig, responseBody)
127
+ @log_obj ||= Log.new(merchantConfig.log_config, 'MLEUtility')
128
+
129
+ if !self.check_is_mle_encrypted_response(responseBody)
130
+ raise StandardError.new('Response body is not MLE encrypted.')
131
+ end
132
+
133
+ mlePrivateKey = self.get_mle_response_private_key(merchantConfig)
134
+ jweResponseToken = self.get_response_mle_token(responseBody)
135
+
136
+ # When mle token is empty or null then fall back to non mle encrypted response
137
+ if jweResponseToken.nil? || jweResponseToken.strip.empty?
138
+ return responseBody
139
+ end
140
+
141
+ begin
142
+ @log_obj.logger.info("LOG_NETWORK_RESPONSE_BEFORE_MLE_DECRYPTION: #{responseBody}")
143
+
144
+ decryptedResponse = AuthJWEUtility.decrypt_jwe_using_private_key(mlePrivateKey, jweResponseToken)
145
+
146
+ @log_obj.logger.info("LOG_NETWORK_RESPONSE_AFTER_MLE_DECRYPTION: #{decryptedResponse}")
147
+
148
+ return decryptedResponse
149
+ rescue => e
150
+ raise StandardError.new(Constants::ERROR_PREFIX + "An error occurred during MLE decryption: #{e.message}")
151
+ end
152
+ end
153
+
154
+ def self.get_response_mle_token(responseBody)
155
+ @log_obj ||= Log.new(merchantConfig.log_config, 'MLEUtility')
156
+
157
+ begin
158
+ jsonObject = JSON.parse(responseBody)
159
+ token = jsonObject['encryptedResponse']
160
+ token.is_a?(String) ? token : nil
161
+ rescue JSON::ParserError, TypeError => e
162
+ err = StandardError.new(Constants::ERROR_PREFIX + "Failed to extract Response MLE token: #{e.message}")
163
+ @log_obj.logger.error(ExceptionHandler.new.new_api_exception err)
164
+ raise err
165
+ end
166
+ end
167
+
168
+ private_class_method :get_response_mle_token
169
+
170
+ def self.get_mle_response_private_key(merchantConfig)
171
+ @log_obj ||= Log.new(merchantConfig.log_config, 'MLEUtility')
172
+
173
+ # First priority - Return private key provided in merchant config, if any
174
+ if !merchantConfig.responseMlePrivateKey.nil? && !merchantConfig.responseMlePrivateKey.to_s.strip.empty?
175
+ return merchantConfig.responseMlePrivateKey
176
+ end
177
+
178
+ # Second priority - Return private key loaded from merchantConfig.responseMlePrivateKeyFilePath
179
+ responseMlePrivateKey = Cache.new.getMLEResponsePrivateKeyFromFilePath(merchantConfig)
180
+ return responseMlePrivateKey
181
+ end
182
+
183
+ private_class_method :get_mle_response_private_key
184
+
185
+ def self.validate_and_auto_extract_response_mle_kid(merchant_config)
186
+ @log_obj ||= Log.new(merchant_config.log_config, 'MLEUtility')
187
+
188
+ if !merchant_config.responseMlePrivateKey.nil? && !merchant_config.responseMlePrivateKey.to_s.strip.empty?
189
+ @log_obj.logger.debug('responseMlePrivateKey is provided directly, using configured responseMleKID')
190
+ return merchant_config.responseMleKID
191
+ end
192
+
193
+ @log_obj.logger.debug('Validating responseMleKID for JWT token generation')
194
+ cybs_kid = nil
195
+ p12_file = false
196
+
197
+ # File path validity
198
+ begin
199
+ CertificateUtility.validatePathAndFile(merchant_config.responseMlePrivateKeyFilePath, 'responseMlePrivateKeyFilePath', merchant_config.log_config)
200
+ extension = File.extname(merchant_config.responseMlePrivateKeyFilePath).delete_prefix('.').downcase
201
+ if extension == 'p12' || extension == 'pfx'
202
+ p12_file = true
203
+ end
204
+ rescue IOError => e
205
+ @log_obj.logger.debug('No valid private key file path provided, skipping auto-extraction')
206
+ end
207
+
208
+ if p12_file
209
+ @log_obj.logger.debug('P12/PFX file detected, checking if it is a CyberSource certificate')
210
+ cached_data = Cache.new.get_mle_kid_data_from_cache(merchant_config)
211
+ if !cached_data.nil?
212
+ if !cached_data.kid.nil?
213
+ # KID present means it's a CyberSource P12, use it
214
+ cybs_kid = cached_data.kid
215
+ else
216
+ # KID is null means either non-CyberSource P12 or extraction failed
217
+ @log_obj.logger.debug('Private key file is not a CyberSource generated P12/PFX file, skipping auto-extraction')
218
+ end
219
+ end
220
+ else
221
+ @log_obj.logger.debug('Private key file is not a P12/PFX file, skipping auto-extraction')
222
+ end
223
+
224
+ if !cybs_kid.nil?
225
+ @log_obj.logger.debug('Successfully auto-extracted responseMleKID from CyberSource P12 certificate')
226
+ end
227
+
228
+ configured_kid = merchant_config.responseMleKID
229
+ if cybs_kid.nil? && configured_kid.nil?
230
+ raise StandardError.new('responseMleKID is required when response MLE is enabled. ' +
231
+ 'Could not auto-extract from certificate and no manual configuration provided. ' +
232
+ 'Please provide responseMleKID explicitly in your configuration.'
233
+ )
234
+ elsif cybs_kid.nil?
235
+ @log_obj.logger.debug('Using manually configured responseMleKID')
236
+ return configured_kid
237
+ elsif configured_kid.nil?
238
+ @log_obj.logger.debug('Using auto-extracted responseMleKID from CyberSource certificate')
239
+ return cybs_kid
240
+ elsif cybs_kid != configured_kid
241
+ @log_obj.logger.warn('Auto-extracted responseMleKID does not match manually configured responseMleKID. Using configured value as preference')
242
+ end
243
+ return configured_kid
244
+ end
89
245
  end
@@ -1,5 +1,6 @@
1
1
  require 'openssl'
2
2
  require 'base64'
3
+ require_relative '../util/Constants.rb'
3
4
 
4
5
  public
5
6
 
@@ -32,5 +33,40 @@ public
32
33
  end
33
34
  return tempResponseCodeMessage
34
35
  end
35
- end
36
36
 
37
+ def self.getCertificateBasedOnKeyAlias(certificateList, keyAlias)
38
+ return nil if certificateList.nil?
39
+
40
+ certificateList.find do |cert|
41
+ cert.subject.to_a.any? { |_, value, _| value.include?(keyAlias) }
42
+ end
43
+ end
44
+
45
+ def self.getCertificateCollectionAndPrivateKeyFromP12(certificateFilePath, keyPass)
46
+ p12File = File.binread(certificateFilePath)
47
+ p12Object = OpenSSL::PKCS12.new(p12File, keyPass)
48
+
49
+ privateKey = OpenSSL::PKey::RSA.new(p12Object.key)
50
+
51
+ primaryX5Certificate = p12Object.certificate
52
+ additionalX5Certificates = p12Object.ca_certs
53
+
54
+ certificateList = [primaryX5Certificate]
55
+ certificateList.concat(additionalX5Certificates) if additionalX5Certificates
56
+
57
+ return [privateKey, certificateList]
58
+ end
59
+
60
+ def self.isP12GeneratedByCyberSource(filePath, password, logger = nil)
61
+ begin
62
+ _, certificateList = getCertificateCollectionAndPrivateKeyFromP12(filePath, password)
63
+
64
+ foundCertificate = getCertificateBasedOnKeyAlias(certificateList, Constants::DEFAULT_ALIAS_FOR_MLE_CERT)
65
+
66
+ return !foundCertificate.nil?
67
+ rescue => e
68
+ logger&.error("Error while checking if P12 is generated by CyberSource: #{e.message}")
69
+ return false
70
+ end
71
+ end
72
+ end
@@ -71,7 +71,7 @@ module CyberSource
71
71
  post_body = @api_client.object_to_http_body(account_validations_request)
72
72
  sdk_tracker = SdkTracker.new
73
73
  post_body = sdk_tracker.insert_developer_id_tracker(post_body, 'AccountValidationsRequest', @api_client.config.host, @api_client.merchantconfig.defaultDeveloperId)
74
- inbound_mle_status = "mandatory"
74
+ inbound_mle_status = "mandatory"
75
75
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["bank_account_validation_request","bank_account_validation_request_with_http_info"])
76
76
  begin
77
77
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -79,6 +79,9 @@ module CyberSource
79
79
  raise
80
80
  end
81
81
  end
82
+
83
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["bank_account_validation_request","bank_account_validation_request_with_http_info"])
84
+
82
85
  auth_names = []
83
86
  data, status_code, headers = @api_client.call_api(:POST, local_var_path,
84
87
  :header_params => header_params,
@@ -86,7 +89,8 @@ module CyberSource
86
89
  :form_params => form_params,
87
90
  :body => post_body,
88
91
  :auth_names => auth_names,
89
- :return_type => 'InlineResponse20014')
92
+ :return_type => 'InlineResponse20014',
93
+ :isResponseMLEForApi => is_response_mle_for_api)
90
94
  if @api_client.config.debugging
91
95
  begin
92
96
  raise
@@ -76,7 +76,7 @@ module CyberSource
76
76
  else
77
77
  post_body = nil
78
78
  end
79
- inbound_mle_status = "false"
79
+ inbound_mle_status = "false"
80
80
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["get_batch_report","get_batch_report_with_http_info"])
81
81
  begin
82
82
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -84,6 +84,9 @@ module CyberSource
84
84
  raise
85
85
  end
86
86
  end
87
+
88
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["get_batch_report","get_batch_report_with_http_info"])
89
+
87
90
  auth_names = []
88
91
  data, status_code, headers = @api_client.call_api(:GET, local_var_path,
89
92
  :header_params => header_params,
@@ -91,7 +94,8 @@ module CyberSource
91
94
  :form_params => form_params,
92
95
  :body => post_body,
93
96
  :auth_names => auth_names,
94
- :return_type => 'InlineResponse20013')
97
+ :return_type => 'InlineResponse20013',
98
+ :isResponseMLEForApi => is_response_mle_for_api)
95
99
  if @api_client.config.debugging
96
100
  begin
97
101
  raise
@@ -159,7 +163,7 @@ module CyberSource
159
163
  else
160
164
  post_body = nil
161
165
  end
162
- inbound_mle_status = "false"
166
+ inbound_mle_status = "false"
163
167
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["get_batch_status","get_batch_status_with_http_info"])
164
168
  begin
165
169
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -167,6 +171,9 @@ module CyberSource
167
171
  raise
168
172
  end
169
173
  end
174
+
175
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["get_batch_status","get_batch_status_with_http_info"])
176
+
170
177
  auth_names = []
171
178
  data, status_code, headers = @api_client.call_api(:GET, local_var_path,
172
179
  :header_params => header_params,
@@ -174,7 +181,8 @@ module CyberSource
174
181
  :form_params => form_params,
175
182
  :body => post_body,
176
183
  :auth_names => auth_names,
177
- :return_type => 'InlineResponse20012')
184
+ :return_type => 'InlineResponse20012',
185
+ :isResponseMLEForApi => is_response_mle_for_api)
178
186
  if @api_client.config.debugging
179
187
  begin
180
188
  raise
@@ -244,7 +252,7 @@ module CyberSource
244
252
  else
245
253
  post_body = nil
246
254
  end
247
- inbound_mle_status = "false"
255
+ inbound_mle_status = "false"
248
256
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["get_batches_list","get_batches_list_with_http_info"])
249
257
  begin
250
258
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -252,6 +260,9 @@ module CyberSource
252
260
  raise
253
261
  end
254
262
  end
263
+
264
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["get_batches_list","get_batches_list_with_http_info"])
265
+
255
266
  auth_names = []
256
267
  data, status_code, headers = @api_client.call_api(:GET, local_var_path,
257
268
  :header_params => header_params,
@@ -259,7 +270,8 @@ module CyberSource
259
270
  :form_params => form_params,
260
271
  :body => post_body,
261
272
  :auth_names => auth_names,
262
- :return_type => 'InlineResponse20011')
273
+ :return_type => 'InlineResponse20011',
274
+ :isResponseMLEForApi => is_response_mle_for_api)
263
275
  if @api_client.config.debugging
264
276
  begin
265
277
  raise
@@ -321,7 +333,7 @@ module CyberSource
321
333
  post_body = @api_client.object_to_http_body(body)
322
334
  sdk_tracker = SdkTracker.new
323
335
  post_body = sdk_tracker.insert_developer_id_tracker(post_body, 'Body', @api_client.config.host, @api_client.merchantconfig.defaultDeveloperId)
324
- inbound_mle_status = "false"
336
+ inbound_mle_status = "false"
325
337
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["post_batch","post_batch_with_http_info"])
326
338
  begin
327
339
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -329,6 +341,9 @@ module CyberSource
329
341
  raise
330
342
  end
331
343
  end
344
+
345
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["post_batch","post_batch_with_http_info"])
346
+
332
347
  auth_names = []
333
348
  data, status_code, headers = @api_client.call_api(:POST, local_var_path,
334
349
  :header_params => header_params,
@@ -336,7 +351,8 @@ module CyberSource
336
351
  :form_params => form_params,
337
352
  :body => post_body,
338
353
  :auth_names => auth_names,
339
- :return_type => 'InlineResponse202')
354
+ :return_type => 'InlineResponse202',
355
+ :isResponseMLEForApi => is_response_mle_for_api)
340
356
  if @api_client.config.debugging
341
357
  begin
342
358
  raise
@@ -76,7 +76,7 @@ module CyberSource
76
76
  post_body = @api_client.object_to_http_body(modify_billing_agreement)
77
77
  sdk_tracker = SdkTracker.new
78
78
  post_body = sdk_tracker.insert_developer_id_tracker(post_body, 'ModifyBillingAgreement', @api_client.config.host, @api_client.merchantconfig.defaultDeveloperId)
79
- inbound_mle_status = "optional"
79
+ inbound_mle_status = "optional"
80
80
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["billing_agreements_de_registration","billing_agreements_de_registration_with_http_info"])
81
81
  begin
82
82
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -84,6 +84,9 @@ module CyberSource
84
84
  raise
85
85
  end
86
86
  end
87
+
88
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["billing_agreements_de_registration","billing_agreements_de_registration_with_http_info"])
89
+
87
90
  auth_names = []
88
91
  data, status_code, headers = @api_client.call_api(:PATCH, local_var_path,
89
92
  :header_params => header_params,
@@ -91,7 +94,8 @@ module CyberSource
91
94
  :form_params => form_params,
92
95
  :body => post_body,
93
96
  :auth_names => auth_names,
94
- :return_type => 'PtsV2ModifyBillingAgreementPost201Response')
97
+ :return_type => 'PtsV2ModifyBillingAgreementPost201Response',
98
+ :isResponseMLEForApi => is_response_mle_for_api)
95
99
  if @api_client.config.debugging
96
100
  begin
97
101
  raise
@@ -159,7 +163,7 @@ module CyberSource
159
163
  post_body = @api_client.object_to_http_body(intimate_billing_agreement)
160
164
  sdk_tracker = SdkTracker.new
161
165
  post_body = sdk_tracker.insert_developer_id_tracker(post_body, 'IntimateBillingAgreement', @api_client.config.host, @api_client.merchantconfig.defaultDeveloperId)
162
- inbound_mle_status = "optional"
166
+ inbound_mle_status = "optional"
163
167
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["billing_agreements_intimation","billing_agreements_intimation_with_http_info"])
164
168
  begin
165
169
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -167,6 +171,9 @@ module CyberSource
167
171
  raise
168
172
  end
169
173
  end
174
+
175
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["billing_agreements_intimation","billing_agreements_intimation_with_http_info"])
176
+
170
177
  auth_names = []
171
178
  data, status_code, headers = @api_client.call_api(:POST, local_var_path,
172
179
  :header_params => header_params,
@@ -174,7 +181,8 @@ module CyberSource
174
181
  :form_params => form_params,
175
182
  :body => post_body,
176
183
  :auth_names => auth_names,
177
- :return_type => 'PtsV2CreditsPost201Response1')
184
+ :return_type => 'PtsV2CreditsPost201Response1',
185
+ :isResponseMLEForApi => is_response_mle_for_api)
178
186
  if @api_client.config.debugging
179
187
  begin
180
188
  raise
@@ -236,7 +244,7 @@ module CyberSource
236
244
  post_body = @api_client.object_to_http_body(create_billing_agreement)
237
245
  sdk_tracker = SdkTracker.new
238
246
  post_body = sdk_tracker.insert_developer_id_tracker(post_body, 'CreateBillingAgreement', @api_client.config.host, @api_client.merchantconfig.defaultDeveloperId)
239
- inbound_mle_status = "optional"
247
+ inbound_mle_status = "optional"
240
248
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["billing_agreements_registration","billing_agreements_registration_with_http_info"])
241
249
  begin
242
250
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -244,6 +252,9 @@ module CyberSource
244
252
  raise
245
253
  end
246
254
  end
255
+
256
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["billing_agreements_registration","billing_agreements_registration_with_http_info"])
257
+
247
258
  auth_names = []
248
259
  data, status_code, headers = @api_client.call_api(:POST, local_var_path,
249
260
  :header_params => header_params,
@@ -251,7 +262,8 @@ module CyberSource
251
262
  :form_params => form_params,
252
263
  :body => post_body,
253
264
  :auth_names => auth_names,
254
- :return_type => 'PtsV2CreateBillingAgreementPost201Response')
265
+ :return_type => 'PtsV2CreateBillingAgreementPost201Response',
266
+ :isResponseMLEForApi => is_response_mle_for_api)
255
267
  if @api_client.config.debugging
256
268
  begin
257
269
  raise
@@ -71,7 +71,7 @@ module CyberSource
71
71
  post_body = @api_client.object_to_http_body(create_bin_lookup_request)
72
72
  sdk_tracker = SdkTracker.new
73
73
  post_body = sdk_tracker.insert_developer_id_tracker(post_body, 'CreateBinLookupRequest', @api_client.config.host, @api_client.merchantconfig.defaultDeveloperId)
74
- inbound_mle_status = "false"
74
+ inbound_mle_status = "false"
75
75
  if MLEUtility.check_is_mle_for_API(@api_client.merchantconfig, inbound_mle_status, ["get_account_info","get_account_info_with_http_info"])
76
76
  begin
77
77
  post_body = MLEUtility.encrypt_request_payload(@api_client.merchantconfig, post_body)
@@ -79,6 +79,9 @@ module CyberSource
79
79
  raise
80
80
  end
81
81
  end
82
+
83
+ is_response_mle_for_api = MLEUtility.check_is_response_mle_for_api(@api_client.merchantconfig, ["get_account_info","get_account_info_with_http_info"])
84
+
82
85
  auth_names = []
83
86
  data, status_code, headers = @api_client.call_api(:POST, local_var_path,
84
87
  :header_params => header_params,
@@ -86,7 +89,8 @@ module CyberSource
86
89
  :form_params => form_params,
87
90
  :body => post_body,
88
91
  :auth_names => auth_names,
89
- :return_type => 'InlineResponse2012')
92
+ :return_type => 'InlineResponse2012',
93
+ :isResponseMLEForApi => is_response_mle_for_api)
90
94
  if @api_client.config.debugging
91
95
  begin
92
96
  raise