io-complyance-unify-sdk 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +26 -0
- data/README.md +595 -0
- data/lib/complyance/circuit_breaker.rb +99 -0
- data/lib/complyance/persistent_queue_manager.rb +474 -0
- data/lib/complyance/retry_strategy.rb +198 -0
- data/lib/complyance_sdk/config/retry_config.rb +127 -0
- data/lib/complyance_sdk/config/sdk_config.rb +212 -0
- data/lib/complyance_sdk/exceptions/circuit_breaker_open_error.rb +14 -0
- data/lib/complyance_sdk/exceptions/sdk_exception.rb +93 -0
- data/lib/complyance_sdk/generators/config_generator.rb +67 -0
- data/lib/complyance_sdk/generators/install_generator.rb +22 -0
- data/lib/complyance_sdk/generators/templates/complyance_initializer.rb +36 -0
- data/lib/complyance_sdk/http/authentication_middleware.rb +43 -0
- data/lib/complyance_sdk/http/client.rb +223 -0
- data/lib/complyance_sdk/http/logging_middleware.rb +153 -0
- data/lib/complyance_sdk/jobs/base_job.rb +63 -0
- data/lib/complyance_sdk/jobs/process_document_job.rb +92 -0
- data/lib/complyance_sdk/jobs/sidekiq_job.rb +165 -0
- data/lib/complyance_sdk/middleware/rack_middleware.rb +39 -0
- data/lib/complyance_sdk/models/country.rb +205 -0
- data/lib/complyance_sdk/models/country_policy_registry.rb +159 -0
- data/lib/complyance_sdk/models/document_type.rb +52 -0
- data/lib/complyance_sdk/models/environment.rb +144 -0
- data/lib/complyance_sdk/models/logical_doc_type.rb +228 -0
- data/lib/complyance_sdk/models/mode.rb +47 -0
- data/lib/complyance_sdk/models/operation.rb +47 -0
- data/lib/complyance_sdk/models/policy_result.rb +145 -0
- data/lib/complyance_sdk/models/purpose.rb +52 -0
- data/lib/complyance_sdk/models/source.rb +104 -0
- data/lib/complyance_sdk/models/source_ref.rb +130 -0
- data/lib/complyance_sdk/models/unify_request.rb +208 -0
- data/lib/complyance_sdk/models/unify_response.rb +198 -0
- data/lib/complyance_sdk/queue/persistent_queue_manager.rb +609 -0
- data/lib/complyance_sdk/railtie.rb +29 -0
- data/lib/complyance_sdk/retry/circuit_breaker.rb +159 -0
- data/lib/complyance_sdk/retry/retry_manager.rb +108 -0
- data/lib/complyance_sdk/retry/retry_strategy.rb +225 -0
- data/lib/complyance_sdk/version.rb +5 -0
- data/lib/complyance_sdk.rb +935 -0
- metadata +322 -0
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "complyance_sdk/version"
|
|
4
|
+
require "complyance_sdk/config/sdk_config"
|
|
5
|
+
require "complyance_sdk/models/source"
|
|
6
|
+
require "complyance_sdk/models/source_ref"
|
|
7
|
+
require "complyance_sdk/models/environment"
|
|
8
|
+
require "complyance_sdk/models/document_type"
|
|
9
|
+
require "complyance_sdk/models/logical_doc_type"
|
|
10
|
+
require "complyance_sdk/models/country"
|
|
11
|
+
require "complyance_sdk/models/policy_result"
|
|
12
|
+
require "complyance_sdk/models/country_policy_registry"
|
|
13
|
+
require "complyance_sdk/models/operation"
|
|
14
|
+
require "complyance_sdk/models/mode"
|
|
15
|
+
require "complyance_sdk/models/purpose"
|
|
16
|
+
require "complyance_sdk/models/unify_request"
|
|
17
|
+
require "complyance_sdk/models/unify_response"
|
|
18
|
+
require "complyance_sdk/config/retry_config"
|
|
19
|
+
require "complyance_sdk/exceptions/sdk_exception"
|
|
20
|
+
require "complyance_sdk/exceptions/circuit_breaker_open_error"
|
|
21
|
+
# require "complyance_sdk/middleware/rack_middleware" # Removed - not needed
|
|
22
|
+
require "complyance_sdk/http/client"
|
|
23
|
+
# require "complyance_sdk/retry/retry_manager" # Removed - not needed
|
|
24
|
+
require "complyance_sdk/retry/circuit_breaker"
|
|
25
|
+
require "complyance_sdk/retry/retry_strategy"
|
|
26
|
+
require "complyance_sdk/queue/persistent_queue_manager"
|
|
27
|
+
|
|
28
|
+
# Main module for the Complyance SDK
|
|
29
|
+
module ComplyanceSDK
|
|
30
|
+
class << self
|
|
31
|
+
attr_accessor :configuration
|
|
32
|
+
|
|
33
|
+
# Configure the SDK with the provided configuration
|
|
34
|
+
#
|
|
35
|
+
# @param config [ComplyanceSDK::Config::SDKConfig] The configuration object
|
|
36
|
+
# @yield [config] Yields the configuration object for block-style configuration
|
|
37
|
+
# @return [ComplyanceSDK::Config::SDKConfig] The configuration object
|
|
38
|
+
def configure(config = nil)
|
|
39
|
+
self.configuration = config if config.is_a?(ComplyanceSDK::Config::SDKConfig)
|
|
40
|
+
|
|
41
|
+
if block_given?
|
|
42
|
+
self.configuration ||= ComplyanceSDK::Config::SDKConfig.new
|
|
43
|
+
yield(configuration)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
configuration
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Configure the SDK from environment variables
|
|
50
|
+
#
|
|
51
|
+
# @return [ComplyanceSDK::Config::SDKConfig] The configuration object
|
|
52
|
+
def configure_from_env
|
|
53
|
+
self.configuration = ComplyanceSDK::Config::SDKConfig.from_env
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Configure the SDK from Rails credentials
|
|
57
|
+
#
|
|
58
|
+
# @param environment [Symbol] The Rails environment (:development, :production, etc.)
|
|
59
|
+
# @return [ComplyanceSDK::Config::SDKConfig] The configuration object
|
|
60
|
+
def configure_from_rails(environment = nil)
|
|
61
|
+
self.configuration = ComplyanceSDK::Config::SDKConfig.from_rails(environment)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check if the SDK is configured
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] True if the SDK is configured, false otherwise
|
|
67
|
+
def configured?
|
|
68
|
+
!configuration.nil? && configuration.valid?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get the retry manager instance
|
|
72
|
+
#
|
|
73
|
+
# @param redis_config [Hash] Optional Redis configuration
|
|
74
|
+
# @return [ComplyanceSDK::Retry::RetryManager] The retry manager
|
|
75
|
+
def retry_manager(redis_config = {})
|
|
76
|
+
@retry_manager ||= ComplyanceSDK::Retry::RetryManager.new(configuration, redis_config)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Execute an operation with retry logic
|
|
80
|
+
#
|
|
81
|
+
# @param operation_name [String] Name of the operation
|
|
82
|
+
# @param context [Hash] Context information
|
|
83
|
+
# @yield The block to execute
|
|
84
|
+
# @return The result of the block
|
|
85
|
+
def with_retry(operation_name, context = {})
|
|
86
|
+
unless configured?
|
|
87
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new(
|
|
88
|
+
"SDK must be configured before using retry functionality"
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
retry_manager.execute(operation_name, context) { yield }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Main API method for document processing (Java-style signature)
|
|
96
|
+
#
|
|
97
|
+
# @param source_name [String] The source name
|
|
98
|
+
# @param source_version [String] The source version
|
|
99
|
+
# @param logical_doc_type [Symbol] The logical document type
|
|
100
|
+
# @param country [Symbol] The country code
|
|
101
|
+
# @param operation [Symbol] The operation type
|
|
102
|
+
# @param mode [Symbol] The mode
|
|
103
|
+
# @param purpose [Symbol] The purpose
|
|
104
|
+
# @param payload [Hash] The business data payload
|
|
105
|
+
# @param destinations [Array, nil] Optional destinations (auto-generated if nil)
|
|
106
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
107
|
+
def push_to_unify(source_name, source_version, logical_doc_type, country, operation, mode, purpose, payload, destinations = nil)
|
|
108
|
+
unless configured?
|
|
109
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new(
|
|
110
|
+
"SDK must be configured before making API calls"
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Process queued submissions first before handling new requests
|
|
115
|
+
process_queued_submissions_first
|
|
116
|
+
|
|
117
|
+
# Validate required parameters
|
|
118
|
+
validate_required_parameter(source_name, :source_name, "Source name is required")
|
|
119
|
+
validate_required_parameter(source_version, :source_version, "Source version is required")
|
|
120
|
+
validate_required_parameter(logical_doc_type, :logical_doc_type, "Logical document type is required")
|
|
121
|
+
validate_required_parameter(country, :country, "Country is required")
|
|
122
|
+
validate_required_parameter(operation, :operation, "Operation is required")
|
|
123
|
+
validate_required_parameter(mode, :mode, "Mode is required")
|
|
124
|
+
validate_required_parameter(purpose, :purpose, "Purpose is required")
|
|
125
|
+
validate_required_parameter(payload, :payload, "Payload is required")
|
|
126
|
+
|
|
127
|
+
# Validate country restrictions for current environment
|
|
128
|
+
validate_country_for_environment(country, configuration.environment)
|
|
129
|
+
|
|
130
|
+
# Evaluate country policy to get base document type and meta.config flags
|
|
131
|
+
policy = ComplyanceSDK::Models::CountryPolicyRegistry.evaluate(country, logical_doc_type)
|
|
132
|
+
|
|
133
|
+
# Merge meta.config flags into payload
|
|
134
|
+
merged_payload = deep_merge_into_meta_config(payload, policy.get_meta_config_flags)
|
|
135
|
+
|
|
136
|
+
# Auto-set invoice_data.document_type based on LogicalDocType
|
|
137
|
+
set_invoice_data_document_type(merged_payload, logical_doc_type)
|
|
138
|
+
|
|
139
|
+
# Create source reference with type from configuration
|
|
140
|
+
source_type = find_source_type(source_name, source_version)
|
|
141
|
+
source_ref = ComplyanceSDK::Models::SourceRef.new(source_name, source_version, source_type)
|
|
142
|
+
|
|
143
|
+
# Auto-generate destinations if none provided and auto-generation is enabled
|
|
144
|
+
final_destinations = destinations || (configuration.auto_generate_tax_destination? ?
|
|
145
|
+
generate_default_destinations(country, policy.get_document_type) : [])
|
|
146
|
+
|
|
147
|
+
# Extract destinations from payload if they exist there
|
|
148
|
+
if final_destinations.empty? && merged_payload.is_a?(Hash) && merged_payload['destinations']
|
|
149
|
+
final_destinations = merged_payload['destinations']
|
|
150
|
+
# Remove destinations from payload to avoid duplication
|
|
151
|
+
merged_payload = merged_payload.dup
|
|
152
|
+
merged_payload.delete('destinations')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Build and send request using the resolved base document type
|
|
156
|
+
push_to_unify_internal_with_document_type(
|
|
157
|
+
source_ref,
|
|
158
|
+
policy.base_document_type,
|
|
159
|
+
ComplyanceSDK::Models::LogicalDocType.meta_config_document_type(logical_doc_type),
|
|
160
|
+
country,
|
|
161
|
+
operation,
|
|
162
|
+
mode,
|
|
163
|
+
purpose,
|
|
164
|
+
merged_payload,
|
|
165
|
+
final_destinations
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Push to Unify API with logical document types using JSON string payload
|
|
170
|
+
#
|
|
171
|
+
# @param source_name [String] The source name
|
|
172
|
+
# @param source_version [String] The source version
|
|
173
|
+
# @param logical_doc_type [Symbol] The logical document type
|
|
174
|
+
# @param country [Symbol] The country code
|
|
175
|
+
# @param operation [Symbol] The operation type
|
|
176
|
+
# @param mode [Symbol] The mode
|
|
177
|
+
# @param purpose [Symbol] The purpose
|
|
178
|
+
# @param json_payload [String] The JSON string payload
|
|
179
|
+
# @param destinations [Array, nil] Optional destinations (auto-generated if nil)
|
|
180
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
181
|
+
def push_to_unify_from_json(source_name, source_version, logical_doc_type, country, operation, mode, purpose, json_payload, destinations = nil)
|
|
182
|
+
if json_payload.nil? || json_payload.to_s.strip.empty?
|
|
183
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
184
|
+
"Payload is required but was null or empty",
|
|
185
|
+
context: {
|
|
186
|
+
suggestion: 'Provide a non-empty JSON payload string. Example: \'{"invoiceNumber":"INV-123","amount":1000}\''
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
begin
|
|
192
|
+
require 'json'
|
|
193
|
+
payload_hash = JSON.parse(json_payload)
|
|
194
|
+
|
|
195
|
+
unless payload_hash.is_a?(Hash)
|
|
196
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
197
|
+
"Failed to parse JSON payload: parsed result is not a hash",
|
|
198
|
+
context: {
|
|
199
|
+
suggestion: 'Ensure the payload is valid JSON and represents an object structure. Example: \'{"invoiceNumber":"INV-123"}\''
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
push_to_unify(source_name, source_version, logical_doc_type, country, operation, mode, purpose, payload_hash, destinations)
|
|
205
|
+
rescue JSON::ParserError => parse_error
|
|
206
|
+
payload_snippet = json_payload.length > 100 ? json_payload[0..99] + '...' : json_payload
|
|
207
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
208
|
+
"Failed to parse JSON payload: #{parse_error.message}",
|
|
209
|
+
context: {
|
|
210
|
+
suggestion: 'Ensure the payload is valid JSON. Example: \'{"invoiceNumber":"INV-123","amount":1000}\'',
|
|
211
|
+
payload_snippet: payload_snippet,
|
|
212
|
+
parse_error: parse_error.message
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
rescue ComplyanceSDK::Exceptions::ValidationError
|
|
216
|
+
raise
|
|
217
|
+
rescue StandardError => conversion_error
|
|
218
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
219
|
+
"Failed to parse JSON payload: #{conversion_error.message}",
|
|
220
|
+
context: {
|
|
221
|
+
suggestion: 'Ensure the payload is valid JSON. Example: \'{"invoiceNumber":"INV-123","amount":1000}\'',
|
|
222
|
+
conversion_error: conversion_error.message
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Push to Unify API with logical document types using object payload
|
|
229
|
+
#
|
|
230
|
+
# @param source_name [String] The source name
|
|
231
|
+
# @param source_version [String] The source version
|
|
232
|
+
# @param logical_doc_type [Symbol] The logical document type
|
|
233
|
+
# @param country [Symbol] The country code
|
|
234
|
+
# @param operation [Symbol] The operation type
|
|
235
|
+
# @param mode [Symbol] The mode
|
|
236
|
+
# @param purpose [Symbol] The purpose
|
|
237
|
+
# @param payload_object [Object] The object payload (any object that responds to to_h or has instance variables)
|
|
238
|
+
# @param destinations [Array, nil] Optional destinations (auto-generated if nil)
|
|
239
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
240
|
+
def push_to_unify_from_object(source_name, source_version, logical_doc_type, country, operation, mode, purpose, payload_object, destinations = nil)
|
|
241
|
+
if payload_object.nil?
|
|
242
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
243
|
+
"Payload object is required but was nil",
|
|
244
|
+
context: {
|
|
245
|
+
suggestion: "Provide a valid payload object. Example: {invoice_number: 'INV-123', amount: 1000}"
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
begin
|
|
251
|
+
# Convert object to hash
|
|
252
|
+
payload_hash = if payload_object.is_a?(Hash)
|
|
253
|
+
payload_object
|
|
254
|
+
elsif payload_object.respond_to?(:to_h)
|
|
255
|
+
payload_object.to_h
|
|
256
|
+
elsif payload_object.respond_to?(:instance_variables)
|
|
257
|
+
# Convert object instance variables to hash
|
|
258
|
+
hash = {}
|
|
259
|
+
payload_object.instance_variables.each do |var|
|
|
260
|
+
key = var.to_s.delete('@')
|
|
261
|
+
hash[key] = payload_object.instance_variable_get(var)
|
|
262
|
+
end
|
|
263
|
+
hash
|
|
264
|
+
else
|
|
265
|
+
# Try JSON serialization for other objects
|
|
266
|
+
require 'json'
|
|
267
|
+
json_str = payload_object.to_json
|
|
268
|
+
JSON.parse(json_str)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
unless payload_hash.is_a?(Hash)
|
|
272
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
273
|
+
"Failed to convert payload object to hash: conversion returned invalid result",
|
|
274
|
+
context: {
|
|
275
|
+
suggestion: "Ensure the object structure is compatible with the SDK payload format. " +
|
|
276
|
+
"The object should be convertible to a hash structure."
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
push_to_unify(source_name, source_version, logical_doc_type, country, operation, mode, purpose, payload_hash, destinations)
|
|
282
|
+
rescue ComplyanceSDK::Exceptions::ValidationError
|
|
283
|
+
raise
|
|
284
|
+
rescue StandardError => conversion_error
|
|
285
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
286
|
+
"Failed to convert payload object to hash: #{conversion_error.message}",
|
|
287
|
+
context: {
|
|
288
|
+
suggestion: "Ensure the object structure is compatible with the SDK payload format. " +
|
|
289
|
+
"The object should be convertible to a hash. " +
|
|
290
|
+
"Example: {invoice_number: 'INV-123', amount: 1000} or a class with public attributes.",
|
|
291
|
+
object_type: payload_object.class.name,
|
|
292
|
+
conversion_error: conversion_error.message
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Main API method for document processing (UnifyRequest object)
|
|
299
|
+
#
|
|
300
|
+
# @param request [ComplyanceSDK::Models::UnifyRequest, Hash] The request object or hash
|
|
301
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
302
|
+
def push_to_unify_request(request)
|
|
303
|
+
unless configured?
|
|
304
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new(
|
|
305
|
+
"SDK must be configured before making API calls"
|
|
306
|
+
)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Convert hash to UnifyRequest if needed
|
|
310
|
+
if request.is_a?(Hash)
|
|
311
|
+
request = ComplyanceSDK::Models::UnifyRequest.from_h(request)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Validate the request
|
|
315
|
+
unless request.valid?
|
|
316
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
317
|
+
"Invalid request: #{request.errors.join(', ')}",
|
|
318
|
+
context: { errors: request.errors }
|
|
319
|
+
)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Make the API call with retry logic
|
|
323
|
+
response_data = with_retry("push_to_unify", { request_id: request.metadata[:request_id] }) do
|
|
324
|
+
http_client.post("", request.to_h)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Convert response to UnifyResponse object
|
|
328
|
+
ComplyanceSDK::Models::UnifyResponse.from_h(response_data)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Convenience method for submitting invoices
|
|
332
|
+
#
|
|
333
|
+
# @param options [Hash] Invoice submission options
|
|
334
|
+
# @option options [String] :country Country code
|
|
335
|
+
# @option options [Hash] :payload Invoice payload
|
|
336
|
+
# @option options [ComplyanceSDK::Models::Source] :source Source information
|
|
337
|
+
# @option options [Symbol] :document_type Document type (default: :tax_invoice)
|
|
338
|
+
# @option options [Symbol] :purpose Purpose (default: :invoicing)
|
|
339
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
340
|
+
def submit_invoice(options = {})
|
|
341
|
+
request = ComplyanceSDK::Models::UnifyRequest.new(
|
|
342
|
+
source: options[:source] || default_source,
|
|
343
|
+
document_type: options[:document_type] || :tax_invoice,
|
|
344
|
+
country: options[:country],
|
|
345
|
+
operation: :single,
|
|
346
|
+
mode: :documents,
|
|
347
|
+
purpose: options[:purpose] || :invoicing,
|
|
348
|
+
payload: options[:payload]
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
push_to_unify(request)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Convenience method for creating field mappings
|
|
355
|
+
#
|
|
356
|
+
# @param options [Hash] Mapping options
|
|
357
|
+
# @option options [String] :country Country code
|
|
358
|
+
# @option options [Hash] :payload Sample payload for mapping
|
|
359
|
+
# @option options [ComplyanceSDK::Models::Source] :source Source information
|
|
360
|
+
# @option options [Symbol] :document_type Document type (default: :tax_invoice)
|
|
361
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
362
|
+
def create_mapping(options = {})
|
|
363
|
+
request = ComplyanceSDK::Models::UnifyRequest.new(
|
|
364
|
+
source: options[:source] || default_source,
|
|
365
|
+
document_type: options[:document_type] || :tax_invoice,
|
|
366
|
+
country: options[:country],
|
|
367
|
+
operation: :single,
|
|
368
|
+
mode: :documents,
|
|
369
|
+
purpose: :mapping,
|
|
370
|
+
payload: options[:payload]
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
push_to_unify(request)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Get the status of a submission
|
|
377
|
+
#
|
|
378
|
+
# @param submission_id [String] The submission ID
|
|
379
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
380
|
+
def get_status(submission_id)
|
|
381
|
+
unless configured?
|
|
382
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new(
|
|
383
|
+
"SDK must be configured before making API calls"
|
|
384
|
+
)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
response_data = with_retry("get_status", { submission_id: submission_id }) do
|
|
388
|
+
http_client.get("/api/v1/submissions/#{submission_id}")
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
ComplyanceSDK::Models::UnifyResponse.from_h(response_data)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Process a document asynchronously using background jobs
|
|
395
|
+
#
|
|
396
|
+
# @param request [ComplyanceSDK::Models::UnifyRequest, Hash] The request object or hash
|
|
397
|
+
# @param options [Hash] Background job options
|
|
398
|
+
# @option options [Symbol] :job_type Job type (:active_job or :sidekiq)
|
|
399
|
+
# @option options [String] :callback_url Optional callback URL
|
|
400
|
+
# @option options [Hash] :callback_headers Optional callback headers
|
|
401
|
+
# @return [String, Object] Job ID or job object
|
|
402
|
+
def push_to_unify_async(request, options = {})
|
|
403
|
+
unless configured?
|
|
404
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new(
|
|
405
|
+
"SDK must be configured before making API calls"
|
|
406
|
+
)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Convert hash to UnifyRequest if needed
|
|
410
|
+
if request.is_a?(Hash)
|
|
411
|
+
request = ComplyanceSDK::Models::UnifyRequest.from_h(request)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Validate the request
|
|
415
|
+
unless request.valid?
|
|
416
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
417
|
+
"Invalid request: #{request.errors.join(', ')}",
|
|
418
|
+
context: { errors: request.errors }
|
|
419
|
+
)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
retry_manager.execute_async(
|
|
423
|
+
request.to_h,
|
|
424
|
+
job_type: options[:job_type] || :active_job,
|
|
425
|
+
callback_url: options[:callback_url],
|
|
426
|
+
callback_headers: options[:callback_headers] || {}
|
|
427
|
+
)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Push to Unify API with logical document types but full control over operation, mode, and purpose
|
|
431
|
+
# This is the primary method for all workflows with logical document types
|
|
432
|
+
#
|
|
433
|
+
# @param source_name [String] The source name
|
|
434
|
+
# @param source_version [String] The source version
|
|
435
|
+
# @param logical_type [Symbol] The logical document type
|
|
436
|
+
# @param country [Symbol] The country code
|
|
437
|
+
# @param operation [Symbol] The operation type
|
|
438
|
+
# @param mode [Symbol] The mode
|
|
439
|
+
# @param purpose [Symbol] The purpose
|
|
440
|
+
# @param payload [Hash] The business data payload
|
|
441
|
+
# @param destinations [Array, nil] Optional destinations (auto-generated if nil)
|
|
442
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
443
|
+
def push_to_unify_logical(source_name, source_version, logical_type, country, operation, mode, purpose, payload, destinations = nil)
|
|
444
|
+
unless configured?
|
|
445
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new(
|
|
446
|
+
"SDK must be configured before making API calls"
|
|
447
|
+
)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Process queued submissions first before handling new requests
|
|
451
|
+
process_queued_submissions_first
|
|
452
|
+
|
|
453
|
+
# Validate required parameters
|
|
454
|
+
# Handle source_name and source_version based on purpose
|
|
455
|
+
final_source_name = if purpose == :mapping
|
|
456
|
+
# For MAPPING purpose, source_name and source_version are optional
|
|
457
|
+
source_name || ""
|
|
458
|
+
else
|
|
459
|
+
# For all other purposes, source_name is mandatory
|
|
460
|
+
if source_name.nil? || source_name.to_s.strip.empty?
|
|
461
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
462
|
+
"Source name is required",
|
|
463
|
+
context: { field: :source_name }
|
|
464
|
+
)
|
|
465
|
+
end
|
|
466
|
+
source_name
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
final_source_version = if purpose == :mapping
|
|
470
|
+
# For MAPPING purpose, source_version is optional
|
|
471
|
+
source_version || ""
|
|
472
|
+
else
|
|
473
|
+
# For all other purposes, source_version is mandatory
|
|
474
|
+
if source_version.nil? || source_version.to_s.strip.empty?
|
|
475
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
476
|
+
"Source version is required",
|
|
477
|
+
context: { field: :source_version }
|
|
478
|
+
)
|
|
479
|
+
end
|
|
480
|
+
source_version
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Validate other required parameters
|
|
484
|
+
validate_required_parameter(logical_type, :logical_type, "Logical document type is required")
|
|
485
|
+
validate_required_parameter(country, :country, "Country is required")
|
|
486
|
+
validate_required_parameter(operation, :operation, "Operation is required")
|
|
487
|
+
validate_required_parameter(mode, :mode, "Mode is required")
|
|
488
|
+
validate_required_parameter(purpose, :purpose, "Purpose is required")
|
|
489
|
+
validate_required_parameter(payload, :payload, "Payload is required")
|
|
490
|
+
|
|
491
|
+
# Validate country restrictions for current environment
|
|
492
|
+
validate_country_for_environment(country, configuration.environment)
|
|
493
|
+
|
|
494
|
+
# Evaluate country policy to get base document type and meta.config flags
|
|
495
|
+
policy = ComplyanceSDK::Models::CountryPolicyRegistry.evaluate(country, logical_type)
|
|
496
|
+
|
|
497
|
+
# Merge meta.config flags into payload
|
|
498
|
+
merged_payload = deep_merge_into_meta_config(payload, policy.get_meta_config_flags)
|
|
499
|
+
|
|
500
|
+
# Auto-set invoice_data.document_type based on LogicalDocType
|
|
501
|
+
set_invoice_data_document_type(merged_payload, logical_type)
|
|
502
|
+
|
|
503
|
+
# Create source reference with type from configuration
|
|
504
|
+
source_type = find_source_type(final_source_name, final_source_version)
|
|
505
|
+
source_ref = ComplyanceSDK::Models::SourceRef.new(final_source_name, final_source_version, source_type)
|
|
506
|
+
|
|
507
|
+
# Auto-generate destinations if none provided and auto-generation is enabled
|
|
508
|
+
final_destinations = destinations || (configuration.auto_generate_tax_destination? ?
|
|
509
|
+
generate_default_destinations(country, policy.get_document_type) : [])
|
|
510
|
+
|
|
511
|
+
# Build and send request using the resolved base document type
|
|
512
|
+
push_to_unify_internal_with_document_type(
|
|
513
|
+
source_ref,
|
|
514
|
+
policy.base_document_type,
|
|
515
|
+
ComplyanceSDK::Models::LogicalDocType.meta_config_document_type(logical_type),
|
|
516
|
+
country,
|
|
517
|
+
operation,
|
|
518
|
+
mode,
|
|
519
|
+
purpose,
|
|
520
|
+
merged_payload,
|
|
521
|
+
final_destinations
|
|
522
|
+
)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Push to Unify API with logical document types using SourceRef
|
|
526
|
+
#
|
|
527
|
+
# @param source_ref [ComplyanceSDK::Models::SourceRef] The source reference
|
|
528
|
+
# @param logical_type [Symbol] The logical document type
|
|
529
|
+
# @param country [Symbol] The country code
|
|
530
|
+
# @param operation [Symbol] The operation type
|
|
531
|
+
# @param mode [Symbol] The mode
|
|
532
|
+
# @param purpose [Symbol] The purpose
|
|
533
|
+
# @param payload [Hash] The business data payload
|
|
534
|
+
# @param destinations [Array, nil] Optional destinations
|
|
535
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
536
|
+
def push_to_unify_with_source_ref(source_ref, logical_type, country, operation, mode, purpose, payload, destinations = nil)
|
|
537
|
+
push_to_unify_logical(
|
|
538
|
+
source_ref.name,
|
|
539
|
+
source_ref.version,
|
|
540
|
+
logical_type,
|
|
541
|
+
country,
|
|
542
|
+
operation,
|
|
543
|
+
mode,
|
|
544
|
+
purpose,
|
|
545
|
+
payload,
|
|
546
|
+
destinations
|
|
547
|
+
)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Convenience method to submit invoices with logical document types
|
|
551
|
+
#
|
|
552
|
+
# @param source_name [String] The source name
|
|
553
|
+
# @param source_version [String] The source version
|
|
554
|
+
# @param country [Symbol] The country code
|
|
555
|
+
# @param logical_type [Symbol] The logical document type
|
|
556
|
+
# @param payload [Hash] The business data payload
|
|
557
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
558
|
+
def submit_invoice_logical(source_name, source_version, country, logical_type, payload)
|
|
559
|
+
push_to_unify_logical(
|
|
560
|
+
source_name,
|
|
561
|
+
source_version,
|
|
562
|
+
logical_type,
|
|
563
|
+
country,
|
|
564
|
+
:single,
|
|
565
|
+
:documents,
|
|
566
|
+
:invoicing,
|
|
567
|
+
payload
|
|
568
|
+
)
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Convenience method to create mappings with logical document types
|
|
572
|
+
#
|
|
573
|
+
# @param source_name [String] The source name
|
|
574
|
+
# @param source_version [String] The source version
|
|
575
|
+
# @param country [Symbol] The country code
|
|
576
|
+
# @param logical_type [Symbol] The logical document type
|
|
577
|
+
# @param payload [Hash] The business data payload
|
|
578
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response object
|
|
579
|
+
def create_mapping_logical(source_name, source_version, country, logical_type, payload)
|
|
580
|
+
push_to_unify_logical(
|
|
581
|
+
source_name,
|
|
582
|
+
source_version,
|
|
583
|
+
logical_type,
|
|
584
|
+
country,
|
|
585
|
+
:single,
|
|
586
|
+
:documents,
|
|
587
|
+
:mapping,
|
|
588
|
+
payload
|
|
589
|
+
)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# Get queue status and statistics
|
|
593
|
+
#
|
|
594
|
+
# @return [Hash] Queue status information
|
|
595
|
+
def queue_status
|
|
596
|
+
if queue_manager
|
|
597
|
+
queue_manager.queue_status
|
|
598
|
+
else
|
|
599
|
+
{ error: "Queue Manager is not initialized" }
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# Get detailed queue status
|
|
604
|
+
#
|
|
605
|
+
# @return [Hash] Detailed queue status information
|
|
606
|
+
def detailed_queue_status
|
|
607
|
+
queue_status
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Retry failed submissions
|
|
611
|
+
def retry_failed_submissions
|
|
612
|
+
queue_manager&.retry_failed_submissions
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# Clean up old success files
|
|
616
|
+
#
|
|
617
|
+
# @param days_to_keep [Integer] Number of days to keep success files
|
|
618
|
+
def cleanup_old_success_files(days_to_keep)
|
|
619
|
+
queue_manager&.cleanup_old_success_files(days_to_keep)
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Clear all files from the queue (emergency cleanup)
|
|
623
|
+
def clear_all_queues
|
|
624
|
+
if queue_manager
|
|
625
|
+
queue_manager.clear_all_queues
|
|
626
|
+
else
|
|
627
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new("Queue Manager is not initialized")
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
# Clean up duplicate files across queue directories
|
|
632
|
+
def cleanup_duplicate_files
|
|
633
|
+
if queue_manager
|
|
634
|
+
queue_manager.cleanup_duplicate_files
|
|
635
|
+
else
|
|
636
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new("Queue Manager is not initialized")
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Process pending submissions now
|
|
641
|
+
def process_pending_submissions
|
|
642
|
+
queue_manager&.process_pending_submissions_now
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# Process queued submissions before handling new requests
|
|
646
|
+
def process_queued_submissions_first
|
|
647
|
+
if queue_manager
|
|
648
|
+
puts "🔥 QUEUE: Processing queued submissions first"
|
|
649
|
+
queue_manager.process_pending_submissions_now
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
private
|
|
654
|
+
|
|
655
|
+
# Get the HTTP client instance
|
|
656
|
+
#
|
|
657
|
+
# @return [ComplyanceSDK::HTTP::Client] The HTTP client
|
|
658
|
+
def http_client
|
|
659
|
+
@http_client ||= ComplyanceSDK::HTTP::Client.new(configuration)
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# Get the default source from configuration
|
|
663
|
+
#
|
|
664
|
+
# @return [ComplyanceSDK::Models::Source, nil] The default source
|
|
665
|
+
def default_source
|
|
666
|
+
configuration.sources.first
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# Find source type by name and version from configuration
|
|
670
|
+
#
|
|
671
|
+
# @param name [String] The source name
|
|
672
|
+
# @param version [String] The source version
|
|
673
|
+
# @return [Symbol] The source type (defaults to :first_party)
|
|
674
|
+
def find_source_type(name, version)
|
|
675
|
+
configured_source = configuration.sources.find do |source|
|
|
676
|
+
source.name == name && source.version == version
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
configured_source&.type || :first_party
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
# Get the queue manager instance
|
|
683
|
+
#
|
|
684
|
+
# @return [ComplyanceSDK::Queue::PersistentQueueManager, nil] The queue manager
|
|
685
|
+
def queue_manager
|
|
686
|
+
return nil unless configuration
|
|
687
|
+
|
|
688
|
+
@queue_manager ||= ComplyanceSDK::Queue::PersistentQueueManager.new(
|
|
689
|
+
configuration.api_key,
|
|
690
|
+
configuration.environment == ComplyanceSDK::Models::Environment::LOCAL
|
|
691
|
+
)
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
# Validate required parameter
|
|
695
|
+
#
|
|
696
|
+
# @param value [Object] The value to validate
|
|
697
|
+
# @param field [Symbol] The field name
|
|
698
|
+
# @param message [String] The error message
|
|
699
|
+
def validate_required_parameter(value, field, message)
|
|
700
|
+
if value.nil?
|
|
701
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
702
|
+
message,
|
|
703
|
+
context: { field: field }
|
|
704
|
+
)
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
# Validate country restrictions based on current environment
|
|
709
|
+
#
|
|
710
|
+
# @param country [Symbol] The country
|
|
711
|
+
# @param environment [Symbol] The environment
|
|
712
|
+
def validate_country_for_environment(country, environment)
|
|
713
|
+
unless ComplyanceSDK::Models::Country.allowed_for_environment?(country, environment)
|
|
714
|
+
error_message = ComplyanceSDK::Models::Country.validation_error_message(country, environment)
|
|
715
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
716
|
+
"Country not allowed for environment: #{error_message}",
|
|
717
|
+
context: { country: country, environment: environment }
|
|
718
|
+
)
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# Deep merge meta.config flags into payload
|
|
723
|
+
# User values take precedence over policy defaults
|
|
724
|
+
#
|
|
725
|
+
# @param payload [Hash] The original payload
|
|
726
|
+
# @param config_flags [Hash] The config flags to merge
|
|
727
|
+
# @return [Hash] The merged payload
|
|
728
|
+
def deep_merge_into_meta_config(payload, config_flags)
|
|
729
|
+
merged = payload.dup
|
|
730
|
+
|
|
731
|
+
meta = merged[:meta] || merged['meta'] || {}
|
|
732
|
+
config = meta[:config] || meta['config'] || {}
|
|
733
|
+
|
|
734
|
+
# Merge config flags (user values take precedence)
|
|
735
|
+
merged_config = config_flags.merge(config)
|
|
736
|
+
|
|
737
|
+
meta[:config] = merged_config
|
|
738
|
+
merged[:meta] = meta
|
|
739
|
+
|
|
740
|
+
merged
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
# Auto-set invoice_data.document_type based on LogicalDocType
|
|
744
|
+
#
|
|
745
|
+
# @param payload [Hash] The payload to modify
|
|
746
|
+
# @param logical_type [Symbol] The logical document type
|
|
747
|
+
def set_invoice_data_document_type(payload, logical_type)
|
|
748
|
+
return unless payload
|
|
749
|
+
|
|
750
|
+
invoice_data = payload[:invoice_data] || payload['invoice_data']
|
|
751
|
+
return unless invoice_data
|
|
752
|
+
|
|
753
|
+
# Determine document type string based on LogicalDocType
|
|
754
|
+
document_type = ComplyanceSDK::Models::LogicalDocType.invoice_data_document_type(logical_type)
|
|
755
|
+
|
|
756
|
+
# Set the document_type field (use string key for consistency)
|
|
757
|
+
invoice_data['document_type'] = document_type
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
# Generate default destinations for a country and document type
|
|
761
|
+
#
|
|
762
|
+
# @param country [Symbol] The country
|
|
763
|
+
# @param document_type [String] The document type
|
|
764
|
+
# @return [Array] Array of destinations
|
|
765
|
+
def generate_default_destinations(country, document_type)
|
|
766
|
+
destinations = []
|
|
767
|
+
|
|
768
|
+
# Auto-generate tax authority destination
|
|
769
|
+
authority = ComplyanceSDK::Models::Country.default_tax_authority(country)
|
|
770
|
+
if authority
|
|
771
|
+
destinations << {
|
|
772
|
+
type: :tax_authority,
|
|
773
|
+
country: country.to_s.upcase,
|
|
774
|
+
authority: authority,
|
|
775
|
+
document_type: document_type
|
|
776
|
+
}
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
destinations
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# Internal method to push to Unify API with custom document type string
|
|
783
|
+
#
|
|
784
|
+
# @param source_ref [ComplyanceSDK::Models::SourceRef] The source reference
|
|
785
|
+
# @param base_document_type [Symbol] The base document type
|
|
786
|
+
# @param document_type_string [String] The custom document type string
|
|
787
|
+
# @param country [Symbol] The country
|
|
788
|
+
# @param operation [Symbol] The operation
|
|
789
|
+
# @param mode [Symbol] The mode
|
|
790
|
+
# @param purpose [Symbol] The purpose
|
|
791
|
+
# @param payload [Hash] The payload
|
|
792
|
+
# @param destinations [Array] The destinations
|
|
793
|
+
# @return [ComplyanceSDK::Models::UnifyResponse] The response
|
|
794
|
+
def push_to_unify_internal_with_document_type(source_ref, base_document_type, document_type_string, country, operation, mode, purpose, payload, destinations)
|
|
795
|
+
# Create UnifyRequest using the model
|
|
796
|
+
request = ComplyanceSDK::Models::UnifyRequest.new(
|
|
797
|
+
source: source_ref,
|
|
798
|
+
document_type: base_document_type,
|
|
799
|
+
country: country,
|
|
800
|
+
operation: operation,
|
|
801
|
+
mode: mode,
|
|
802
|
+
purpose: purpose,
|
|
803
|
+
payload: payload,
|
|
804
|
+
destinations: destinations,
|
|
805
|
+
metadata: {
|
|
806
|
+
api_key: configuration.api_key,
|
|
807
|
+
request_id: "req_#{Time.now.to_i}_#{rand}",
|
|
808
|
+
timestamp: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
809
|
+
environment: ComplyanceSDK::Models::Environment.to_api_value(configuration.environment),
|
|
810
|
+
correlation_id: configuration.correlation_id
|
|
811
|
+
}
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
# Convert to hash for API call
|
|
815
|
+
request_data = request.to_h
|
|
816
|
+
|
|
817
|
+
begin
|
|
818
|
+
# Use circuit breaker if configured
|
|
819
|
+
if configuration.retry_config&.circuit_breaker_enabled?
|
|
820
|
+
circuit_breaker = ComplyanceSDK::Retry::CircuitBreaker.new(
|
|
821
|
+
configuration.retry_config.circuit_breaker_config
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
return circuit_breaker.execute do
|
|
825
|
+
http_client.post('', request_data)
|
|
826
|
+
end
|
|
827
|
+
else
|
|
828
|
+
return http_client.post('', request_data)
|
|
829
|
+
end
|
|
830
|
+
rescue ComplyanceSDK::Exceptions::CircuitBreakerOpenError => e
|
|
831
|
+
# Circuit breaker is open - queue the request
|
|
832
|
+
puts "🚫 Circuit breaker is OPEN - queuing request for retry"
|
|
833
|
+
|
|
834
|
+
if queue_manager
|
|
835
|
+
complete_request_json = JSON.generate(request_data)
|
|
836
|
+
submission = {
|
|
837
|
+
payload: complete_request_json,
|
|
838
|
+
source: { id: source_ref.identity },
|
|
839
|
+
country: country,
|
|
840
|
+
document_type: base_document_type
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
queue_manager.enqueue(submission)
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
status: 'queued',
|
|
847
|
+
message: "Circuit breaker is open - request queued for retry. Submission ID: #{request_data[:request_id]}",
|
|
848
|
+
data: {
|
|
849
|
+
submission: {
|
|
850
|
+
submission_id: request_data[:request_id]
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
else
|
|
855
|
+
raise e
|
|
856
|
+
end
|
|
857
|
+
rescue ComplyanceSDK::Exceptions::SDKException => e
|
|
858
|
+
puts "🔥 QUEUE: SDKException caught - Error: #{e.message}, ServerError: #{server_error?(e)}, QueueManager: #{!queue_manager.nil?}"
|
|
859
|
+
|
|
860
|
+
# Check if the error is a 500-range server error and queue is enabled
|
|
861
|
+
if server_error?(e) && queue_manager
|
|
862
|
+
# Store the complete UnifyRequest as JSON to maintain exact API format
|
|
863
|
+
complete_request_json = JSON.generate(request_data)
|
|
864
|
+
puts "🔥 QUEUE: Successfully converted complete UnifyRequest to JSON with length: #{complete_request_json.length}"
|
|
865
|
+
puts "🔥 QUEUE: Complete request JSON preview: #{complete_request_json[0..199]}"
|
|
866
|
+
|
|
867
|
+
# Create a submission for the queue
|
|
868
|
+
submission = {
|
|
869
|
+
payload: complete_request_json,
|
|
870
|
+
source: { id: source_ref.identity },
|
|
871
|
+
country: country,
|
|
872
|
+
document_type: base_document_type
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
puts "🔥 QUEUE: Created submission with complete request length: #{submission[:payload].length}"
|
|
876
|
+
|
|
877
|
+
# Enqueue the failed submission for background retry
|
|
878
|
+
queue_manager.enqueue(submission)
|
|
879
|
+
|
|
880
|
+
# Return a response indicating the submission was queued
|
|
881
|
+
return {
|
|
882
|
+
status: 'queued',
|
|
883
|
+
message: "Request failed but has been queued for retry. Submission ID: #{request_data[:request_id]}",
|
|
884
|
+
data: {
|
|
885
|
+
submission: {
|
|
886
|
+
submission_id: request_data[:request_id]
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
# If not a server error or queue not available, re-throw the exception
|
|
893
|
+
raise e
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Determines if an SDK exception represents a server error (500-range HTTP status codes).
|
|
898
|
+
# Only 500-range errors (500-599) should trigger queue access.
|
|
899
|
+
#
|
|
900
|
+
# @param exception [ComplyanceSDK::Exceptions::SDKException] The SDK exception
|
|
901
|
+
# @return [Boolean] True if it's a 500-range HTTP error; otherwise, false
|
|
902
|
+
def server_error?(exception)
|
|
903
|
+
return false unless exception.context
|
|
904
|
+
|
|
905
|
+
# Check HTTP status code in context
|
|
906
|
+
http_status_obj = exception.context[:http_status] || exception.context['httpStatus']
|
|
907
|
+
if http_status_obj
|
|
908
|
+
begin
|
|
909
|
+
status_code = http_status_obj.to_i
|
|
910
|
+
|
|
911
|
+
# Only 500-range errors (500-599) should trigger queue access
|
|
912
|
+
is_server_status = status_code >= 500 && status_code < 600
|
|
913
|
+
if !is_server_status
|
|
914
|
+
puts "HTTP status #{status_code} detected (non 500-range) - skipping queue"
|
|
915
|
+
else
|
|
916
|
+
puts "Server error detected from HTTP status: #{status_code}"
|
|
917
|
+
end
|
|
918
|
+
return is_server_status
|
|
919
|
+
rescue StandardError => ex
|
|
920
|
+
puts "Invalid HTTP status format: #{http_status_obj}"
|
|
921
|
+
# Ignore invalid status
|
|
922
|
+
end
|
|
923
|
+
else
|
|
924
|
+
puts "No httpStatus in exception context, not counting as server error"
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
# Fallback: use error codes only when HTTP status is unavailable
|
|
928
|
+
error_code = exception.context[:error_code] || exception.context['error_code']
|
|
929
|
+
return error_code == 'INTERNAL_SERVER_ERROR' || error_code == 'SERVICE_UNAVAILABLE'
|
|
930
|
+
end
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
# Rails integration - removed railtie
|
|
935
|
+
# require "complyance_sdk/railtie" if defined?(Rails)
|