mintsoft 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 489ff03aa24f063091490ef90057d880e2ae2f181627e9a6bea31ceefdb30149
4
- data.tar.gz: '0291ff21beb0f80946eeb3795ee7c98e8e1e45cf7a504fb6995229d72eca6f81'
3
+ metadata.gz: eb91c29f5e8b482a6b75a67b7d76ab3005ceffb8e448dd6c147822fd9dd5649f
4
+ data.tar.gz: e00bff92fa1c7d8fb78e725628528e56a9ff46521bd2edcede1c206ee8aa4d6c
5
5
  SHA512:
6
- metadata.gz: 2c6a650aa2ad9a7c3fc2269763fc8cdcf7bc62f19b24af653500c4c27d56e2cb52e6147b8662a7b8705580976aef6b4a70f1d6063069debbcf6cd49865a4c36a
7
- data.tar.gz: 7b8722786821c4d3adabfb7b7fdb7495af8cb0a08c0bdc8272a0667fe2f39e227e846885cfdbae7720568943ebf203fd5279aca49c05517b90851fd910ef2e08
6
+ metadata.gz: 38e7aea2dbb674599085f32697c34ddf0c91739049dbba85d339aad060409c8e8d4b36ad509133aaf0321c58e36fb5171bc61069210476342214b794b9b2e2cc
7
+ data.tar.gz: '0837bb4b72c7f50323e62ee569757f793d2fd5a3c895f8aaedcaed1bc3e7cdff98d080436ab9e9e0e780c92e267d30fd62d1823764e3504f3ac3085f696bc519'
data/README.md CHANGED
@@ -35,10 +35,10 @@ require 'mintsoft'
35
35
 
36
36
  # Step 1: Get authentication token
37
37
  auth_client = Mintsoft::AuthClient.new
38
- auth_response = auth_client.auth.authenticate("username", "password")
38
+ token = auth_client.auth.authenticate("username", "password")
39
39
 
40
40
  # Step 2: Initialize client with token
41
- client = Mintsoft::Client.new(token: auth_response.token)
41
+ client = Mintsoft::Client.new(token: token)
42
42
 
43
43
  # Step 3: Search for orders
44
44
  orders = client.orders.search("ORD-2024-001")
@@ -46,13 +46,13 @@ order = orders.first
46
46
 
47
47
  # Step 4: Get return reasons
48
48
  reasons = client.returns.reasons
49
- damage_reason = reasons.find { |r| r.name.include?("Damage") }
49
+ damage_reason = reasons.find { |r| r.name.include?("Damaged") && r.active? }
50
50
 
51
51
  # Step 5: Create return
52
52
  return_obj = client.returns.create(order.id)
53
53
 
54
54
  # Step 6: Add item to return
55
- success = client.returns.add_item(return_obj.id, {
55
+ result = client.returns.add_item(return_obj.id, {
56
56
  product_id: 123,
57
57
  quantity: 2,
58
58
  reason_id: damage_reason.id,
@@ -70,10 +70,8 @@ success = client.returns.add_item(return_obj.id, {
70
70
  auth_client = Mintsoft::AuthClient.new
71
71
 
72
72
  # Authenticate and get token
73
- auth_response = auth_client.auth.authenticate("username", "password")
74
- puts auth_response.token
75
- puts auth_response.expires_at
76
- puts auth_response.expired?
73
+ token = auth_client.auth.authenticate("username", "password")
74
+ puts token # Direct token string
77
75
  ```
78
76
 
79
77
  #### Client
@@ -97,9 +95,10 @@ orders = client.orders.search("ORD-123")
97
95
 
98
96
  # Access order properties
99
97
  order = orders.first
100
- puts order.id
101
- puts order.order_number
102
- puts order.customer_id
98
+ puts order.id # Direct access to order ID
99
+ puts order.order_number # Direct access to order number
100
+ puts order.customer_id # Direct access to customer ID
101
+ puts order.items&.length || 0 # Get number of items if available
103
102
  ```
104
103
 
105
104
  #### Returns
@@ -113,38 +112,86 @@ active_reasons = reasons.select(&:active?)
113
112
  return_obj = client.returns.create(order_id)
114
113
 
115
114
  # Add item to return
116
- client.returns.add_item(return_id, {
115
+ result = client.returns.add_item(return_obj.id, {
117
116
  product_id: 123,
118
117
  quantity: 2,
119
118
  reason_id: reason_id,
120
119
  unit_value: 25.00,
121
120
  notes: "Optional notes"
122
121
  })
122
+
123
+ # Access return properties
124
+ puts return_obj.id # Direct access to return ID
125
+ puts return_obj.order_id # Access to associated order ID (injected by resource)
126
+ # Note: Items data structure depends on API response format
123
127
  ```
124
128
 
125
129
  ### Error Handling
126
130
 
131
+ All error classes now include response context and status codes for better debugging:
132
+
127
133
  ```ruby
128
134
  begin
129
135
  orders = client.orders.search("ORD-123")
130
136
  rescue Mintsoft::AuthenticationError => e
131
- # Token expired or invalid
137
+ # Token expired or invalid (401)
132
138
  puts "Authentication failed: #{e.message}"
139
+ puts "Status: #{e.status_code}"
133
140
  rescue Mintsoft::ValidationError => e
134
- # Invalid request data
141
+ # Invalid request data (400)
135
142
  puts "Validation error: #{e.message}"
143
+ puts "Response: #{e.response.body if e.response}"
136
144
  rescue Mintsoft::NotFoundError => e
137
- # Resource not found
145
+ # Resource not found (404)
138
146
  puts "Not found: #{e.message}"
139
147
  rescue Mintsoft::APIError => e
140
148
  # General API error
141
149
  puts "API error: #{e.message}"
150
+ puts "Status: #{e.status_code}"
142
151
  end
143
152
  ```
144
153
 
145
- ### Complete Example
154
+ ### Object Methods
155
+
156
+ #### Order Objects
157
+
158
+ ```ruby
159
+ order = orders.first
160
+
161
+ # Direct property access
162
+ order.id # Order ID
163
+ order.order_number # Order number
164
+ order.customer_id # Customer ID
165
+ order.to_hash # Convert to hash
166
+ order.raw # Original API response
167
+ ```
168
+
169
+ #### Return Objects
170
+
171
+ ```ruby
172
+ return_obj = client.returns.create(order_id)
173
+
174
+ # Direct property access
175
+ return_obj.id # Return ID from API response
176
+ return_obj.order_id # Associated order ID (injected by resource)
177
+ # Note: Other properties depend on API response structure
178
+ ```
179
+
180
+ ### Authentication Token Management
146
181
 
147
- See [examples/complete_workflow.rb](examples/complete_workflow.rb) for a full working example.
182
+ The authentication method returns the token string directly:
183
+
184
+ ```ruby
185
+ token = auth_client.auth.authenticate("username", "password")
186
+ puts token # Direct token string
187
+
188
+ # Token management in workflow
189
+ client = Mintsoft::Client.new(token: token)
190
+
191
+ # For re-authentication when token expires:
192
+ token = auth_client.auth.authenticate("username", "password")
193
+ client = Mintsoft::Client.new(token: token)
194
+ ```
148
195
 
149
196
  ## Development
150
197
 
@@ -10,15 +10,13 @@ require "mintsoft"
10
10
  puts "=== Step 1: Authentication ==="
11
11
  auth_client = Mintsoft::AuthClient.new
12
12
  begin
13
- auth_response = auth_client.auth.authenticate(
13
+ token = auth_client.auth.authenticate(
14
14
  ENV.fetch("MINTSOFT_USERNAME"),
15
15
  ENV.fetch("MINTSOFT_PASSWORD")
16
16
  )
17
-
17
+
18
18
  puts "✅ Authentication successful!"
19
- puts "Token: #{auth_response.token[0...10]}..."
20
- puts "Expires at: #{auth_response.expires_at}"
21
- puts "Expired?: #{auth_response.expired?}"
19
+ puts "Token: #{token[0...10]}..."
22
20
  rescue Mintsoft::AuthenticationError => e
23
21
  puts "❌ Authentication failed: #{e.message}"
24
22
  exit 1
@@ -30,7 +28,7 @@ end
30
28
 
31
29
  # Step 2: Initialize client with token
32
30
  puts "\n=== Step 2: Initialize Client ==="
33
- client = Mintsoft::Client.new(token: auth_response.token)
31
+ client = Mintsoft::Client.new(token: token)
34
32
  puts "✅ Client initialized with token"
35
33
 
36
34
  # Step 3: Search for orders
@@ -38,20 +36,19 @@ puts "\n=== Step 3: Search Orders ==="
38
36
  order_number = "ORD-2024-001" # Change this to a real order number in your system
39
37
  begin
40
38
  orders = client.orders.search(order_number)
41
-
39
+
42
40
  if orders.empty?
43
41
  puts "⚠️ No orders found with number: #{order_number}"
44
42
  puts "Please try with a different order number"
45
43
  exit 1
46
44
  end
47
-
45
+
48
46
  order = orders.first
49
47
  puts "✅ Found #{orders.size} order(s)"
50
48
  puts "Order ID: #{order.id}"
51
49
  puts "Order Number: #{order.order_number}"
52
50
  puts "Customer ID: #{order.customer_id}" if order.respond_to?(:customer_id)
53
51
  puts "Status: #{order.status}" if order.respond_to?(:status)
54
-
55
52
  rescue Mintsoft::ValidationError => e
56
53
  puts "❌ Validation error: #{e.message}"
57
54
  exit 1
@@ -69,21 +66,20 @@ puts "\n=== Step 4: Get Return Reasons ==="
69
66
  begin
70
67
  reasons = client.returns.reasons
71
68
  puts "✅ Found #{reasons.size} return reason(s)"
72
-
69
+
73
70
  reasons.each do |reason|
74
71
  status = reason.active? ? "✅" : "❌"
75
72
  puts " #{status} #{reason.name} (ID: #{reason.id}): #{reason.description}"
76
73
  end
77
-
74
+
78
75
  # Select the first active reason for demo
79
76
  selected_reason = reasons.find(&:active?)
80
77
  if selected_reason.nil?
81
78
  puts "❌ No active return reasons found"
82
79
  exit 1
83
80
  end
84
-
81
+
85
82
  puts "\n🎯 Selected reason: #{selected_reason.name}"
86
-
87
83
  rescue Mintsoft::APIError => e
88
84
  puts "❌ API error: #{e.message}"
89
85
  exit 1
@@ -97,7 +93,6 @@ begin
97
93
  puts "Return ID: #{return_obj.id}"
98
94
  puts "Order ID: #{return_obj.order_id}"
99
95
  puts "Status: #{return_obj.status}"
100
-
101
96
  rescue Mintsoft::ValidationError => e
102
97
  puts "❌ Validation error: #{e.message}"
103
98
  exit 1
@@ -118,7 +113,7 @@ item_attributes = {
118
113
 
119
114
  begin
120
115
  success = client.returns.add_item(return_obj.id, item_attributes)
121
-
116
+
122
117
  if success
123
118
  puts "✅ Item added to return successfully!"
124
119
  puts "Product ID: #{item_attributes[:product_id]}"
@@ -129,7 +124,6 @@ begin
129
124
  else
130
125
  puts "❌ Failed to add item to return"
131
126
  end
132
-
133
127
  rescue Mintsoft::ValidationError => e
134
128
  puts "❌ Validation error: #{e.message}"
135
129
  rescue Mintsoft::APIError => e
@@ -143,4 +137,4 @@ puts " 2. ✅ Initialized client"
143
137
  puts " 3. ✅ Searched for orders"
144
138
  puts " 4. ✅ Retrieved return reasons"
145
139
  puts " 5. ✅ Created return"
146
- puts " 6. ✅ Added item to return"
140
+ puts " 6. ✅ Added item to return"
@@ -5,7 +5,7 @@ require "faraday/net_http"
5
5
 
6
6
  module Mintsoft
7
7
  class AuthClient
8
- BASE_URL = "https://api.mintsoft.com"
8
+ BASE_URL = "https://api.mintsoft.co.uk"
9
9
 
10
10
  attr_reader :base_url, :conn_opts
11
11
 
@@ -19,7 +19,7 @@ module Mintsoft
19
19
  conn.url_prefix = @base_url
20
20
  conn.options.merge!(@conn_opts)
21
21
  conn.request :json
22
- conn.response :json, content_type: "application/json"
22
+
23
23
  conn.adapter Faraday.default_adapter
24
24
  end
25
25
  end
@@ -46,7 +46,7 @@ module Mintsoft
46
46
  end
47
47
 
48
48
  if response.success?
49
- handle_success_response(response.body)
49
+ response.body
50
50
  else
51
51
  handle_error_response(response)
52
52
  end
@@ -59,13 +59,6 @@ module Mintsoft
59
59
  raise ValidationError, "Password required" if password.nil? || password.empty?
60
60
  end
61
61
 
62
- def handle_success_response(body)
63
- token = body.dig("token") || body.dig("access_token") || body.dig("Token")
64
- raise APIError, "No token found in response" unless token
65
-
66
- AuthResponse.new(body.merge("token" => token))
67
- end
68
-
69
62
  def handle_error_response(response)
70
63
  case response.status
71
64
  when 401
@@ -84,21 +77,5 @@ module Mintsoft
84
77
  "Unknown error"
85
78
  end
86
79
  end
87
-
88
- class AuthResponse < Base
89
- def token
90
- super || access_token || Token
91
- end
92
-
93
- def expires_at
94
- return nil unless expires_in
95
-
96
- Time.now + expires_in.to_i
97
- end
98
-
99
- def expired?
100
- expires_at && Time.now >= expires_at
101
- end
102
- end
103
80
  end
104
- end
81
+ end
data/lib/mintsoft/base.rb CHANGED
@@ -6,8 +6,11 @@ require "ostruct"
6
6
 
7
7
  module Mintsoft
8
8
  class Base < OpenStruct
9
+ attr_reader :original_response
10
+
9
11
  def initialize(attributes)
10
- super to_ostruct(attributes)
12
+ @original_response = deep_freeze_object(attributes)
13
+ super(to_ostruct(attributes))
11
14
  end
12
15
 
13
16
  def to_ostruct(obj)
@@ -20,17 +23,33 @@ module Mintsoft
20
23
  end
21
24
  end
22
25
 
23
- # Convert back to hash without table key, including nested structures
26
+ # Convert back to hash representation
24
27
  def to_hash
25
28
  ostruct_to_hash(self)
26
29
  end
27
30
 
31
+ # Get the raw original response data
32
+ def raw
33
+ @original_response
34
+ end
35
+
28
36
  private
29
37
 
38
+ def deep_freeze_object(obj)
39
+ case obj
40
+ when Hash
41
+ obj.transform_values { |value| deep_freeze_object(value) }.freeze
42
+ when Array
43
+ obj.map { |item| deep_freeze_object(item) }.freeze
44
+ else
45
+ obj.respond_to?(:freeze) ? obj.freeze : obj
46
+ end
47
+ end
48
+
30
49
  def ostruct_to_hash(object)
31
50
  case object
32
51
  when OpenStruct
33
- hash = object.to_h.reject { |k, _| k == :table }
52
+ hash = object.to_h.except(:table)
34
53
  hash.transform_keys(&:to_s).transform_values { |value| ostruct_to_hash(value) }
35
54
  when Array
36
55
  object.map { |item| ostruct_to_hash(item) }
@@ -41,4 +60,4 @@ module Mintsoft
41
60
  end
42
61
  end
43
62
  end
44
- end
63
+ end
@@ -5,7 +5,7 @@ require "faraday/net_http"
5
5
 
6
6
  module Mintsoft
7
7
  class Client
8
- BASE_URL = "https://api.mintsoft.com"
8
+ BASE_URL = "https://api.mintsoft.co.uk"
9
9
 
10
10
  attr_reader :token, :base_url, :conn_opts
11
11
 
@@ -16,14 +16,7 @@ module Mintsoft
16
16
  end
17
17
 
18
18
  def connection
19
- @connection ||= Faraday.new do |conn|
20
- conn.url_prefix = @base_url
21
- conn.options.merge!(@conn_opts)
22
- conn.request :authorization, :Bearer, @token
23
- conn.request :json
24
- conn.response :json, content_type: "application/json"
25
- conn.adapter Faraday.default_adapter
26
- end
19
+ @connection ||= build_connection
27
20
  end
28
21
 
29
22
  def orders
@@ -33,5 +26,22 @@ module Mintsoft
33
26
  def returns
34
27
  @returns ||= Resources::Returns.new(self)
35
28
  end
29
+
30
+ private
31
+
32
+ def build_connection
33
+ Faraday.new do |conn|
34
+ conn.url_prefix = @base_url
35
+ conn.options.merge!(@conn_opts)
36
+ configure_middleware(conn)
37
+ conn.adapter Faraday.default_adapter
38
+ end
39
+ end
40
+
41
+ def configure_middleware(conn)
42
+ conn.headers["ms-apikey"] = @token
43
+ conn.request :json
44
+ conn.response :json, content_type: "application/json"
45
+ end
36
46
  end
37
- end
47
+ end
@@ -1,9 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mintsoft
4
- class Error < StandardError; end
4
+ # Base error class for all Mintsoft-related errors
5
+ class Error < StandardError
6
+ attr_reader :response, :status_code
7
+
8
+ def initialize(message = nil, response: nil, status_code: nil)
9
+ super(message)
10
+ @response = response
11
+ @status_code = status_code
12
+ end
13
+ end
14
+
15
+ # General API-related errors
5
16
  class APIError < Error; end
17
+
18
+ # Authentication and authorization errors
6
19
  class AuthenticationError < APIError; end
20
+
21
+ # Resource not found errors
7
22
  class NotFoundError < APIError; end
23
+
24
+ # Request validation errors
8
25
  class ValidationError < APIError; end
9
- end
26
+ end
@@ -3,14 +3,6 @@
3
3
  module Mintsoft
4
4
  module Objects
5
5
  class Order < Base
6
- # Convenience methods for common API response formats
7
- def order_id
8
- id || order_id
9
- end
10
-
11
- def order_ref
12
- order_number || order_reference || ref
13
- end
14
6
  end
15
7
  end
16
- end
8
+ end
@@ -3,24 +3,6 @@
3
3
  module Mintsoft
4
4
  module Objects
5
5
  class Return < Base
6
- # Access nested items as OpenStruct objects
7
- def items
8
- return_items || items_array || []
9
- end
10
-
11
- def items_count
12
- items.length
13
- end
14
-
15
- # Access item properties through OpenStruct
16
- def item_quantities
17
- items.map(&:quantity).sum
18
- end
19
-
20
- # Convenience methods for common API response formats
21
- def return_id
22
- id || return_id
23
- end
24
6
  end
25
7
  end
26
- end
8
+ end
@@ -8,4 +8,4 @@ module Mintsoft
8
8
  end
9
9
  end
10
10
  end
11
- end
11
+ end
@@ -22,7 +22,8 @@ module Mintsoft
22
22
  end
23
23
 
24
24
  def handle_response(response)
25
- if response.success?
25
+ case response.status
26
+ when 200..299
26
27
  response.body
27
28
  else
28
29
  handle_error(response)
@@ -30,24 +31,44 @@ module Mintsoft
30
31
  end
31
32
 
32
33
  def handle_error(response)
33
- case response.status
34
+ error_message = extract_error_message(response.body)
35
+
36
+ error_class = case response.status
37
+ when 400 then ValidationError
38
+ when 401 then AuthenticationError
39
+ when 404 then NotFoundError
40
+ else APIError
41
+ end
42
+
43
+ message = build_error_message(response.status, error_message)
44
+ raise error_class.new(message, response: response, status_code: response.status)
45
+ end
46
+
47
+ def build_error_message(status, error_message)
48
+ case status
34
49
  when 401
35
- raise AuthenticationError, "Invalid or expired token"
50
+ "Invalid or expired token"
36
51
  when 400
37
- raise ValidationError, "Invalid request data: #{extract_error_message(response.body)}"
52
+ "Invalid request data: #{error_message}"
38
53
  when 404
39
- raise NotFoundError, "Resource not found"
54
+ "Resource not found"
40
55
  else
41
- raise APIError, "API error: #{response.status} - #{extract_error_message(response.body)}"
56
+ "API error: #{status} - #{error_message}"
42
57
  end
43
58
  end
44
59
 
45
- def extract_error_message(body)
46
- return body if body.is_a?(String)
47
- return body["error"] || body["message"] || body.to_s if body.is_a?(Hash)
60
+ private
48
61
 
49
- "Unknown error"
62
+ def extract_error_message(body)
63
+ case body
64
+ when String
65
+ body
66
+ when Hash
67
+ body["error"] || body["message"] || body.to_s
68
+ else
69
+ "Unknown error"
70
+ end
50
71
  end
51
72
  end
52
73
  end
53
- end
74
+ end
@@ -5,18 +5,13 @@ module Mintsoft
5
5
  class Orders < BaseResource
6
6
  def search(order_number)
7
7
  validate_order_number!(order_number)
8
-
9
8
  response = get_request("/api/Order/Search", params: {"OrderNumber" => order_number})
10
9
 
11
- if response.success?
12
- parse_orders(response.body)
10
+ if response.status == 404
11
+ [] # Return empty array for not found orders
13
12
  else
14
- case response.status
15
- when 404
16
- [] # Return empty array for not found
17
- else
18
- handle_error(response)
19
- end
13
+ response_data = handle_response(response)
14
+ parse_orders(response_data)
20
15
  end
21
16
  end
22
17
 
@@ -33,4 +28,4 @@ module Mintsoft
33
28
  end
34
29
  end
35
30
  end
36
- end
31
+ end
@@ -1,34 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Mintsoft
4
6
  module Resources
5
7
  class Returns < BaseResource
6
8
  def reasons
7
9
  response = get_request("/api/Return/Reasons")
8
-
9
- if response.success?
10
- parse_reasons(response.body)
11
- else
12
- handle_error(response)
13
- end
10
+ response_data = handle_response(response)
11
+ parse_reasons(response_data)
14
12
  end
15
13
 
16
14
  def create(order_id)
17
15
  validate_order_id!(order_id)
18
16
 
19
17
  response = post_request("/api/Return/CreateReturn/#{order_id}")
18
+ response_data = handle_response(response)
19
+ response_data["order_id"] = order_id
20
20
 
21
- if response.success?
22
- # Extract return ID from ToolkitResult and create Return object
23
- return_id = extract_return_id(response.body)
24
- Objects::Return.new({
25
- "id" => return_id,
26
- "order_id" => order_id,
27
- "status" => "pending"
28
- })
29
- else
30
- handle_error(response)
31
- end
21
+ Objects::Return.new(response_data)
32
22
  end
33
23
 
34
24
  def add_item(return_id, item_attributes)
@@ -37,12 +27,10 @@ module Mintsoft
37
27
 
38
28
  payload = format_item_payload(item_attributes)
39
29
  response = post_request("/api/Return/#{return_id}/AddItem", body: payload)
30
+ response_data = handle_response(response)
31
+ response_data["return_id"] = return_id
40
32
 
41
- if response.success?
42
- true # Simple success indicator
43
- else
44
- handle_error(response)
45
- end
33
+ Objects::Return.new(response_data)
46
34
  end
47
35
 
48
36
  private
@@ -72,8 +60,8 @@ module Mintsoft
72
60
  def extract_return_id(toolkit_result)
73
61
  # Parse ToolkitResult to extract return ID - handles various response formats
74
62
  toolkit_result.dig("result", "return_id") ||
75
- toolkit_result.dig("data", "id") ||
76
- toolkit_result["id"]
63
+ toolkit_result.dig("data", "id") ||
64
+ toolkit_result["id"]
77
65
  end
78
66
 
79
67
  def format_item_payload(attrs)
@@ -87,4 +75,4 @@ module Mintsoft
87
75
  end
88
76
  end
89
77
  end
90
- end
78
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mintsoft
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end