clover_sandbox_simulator 1.0.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc73877a0d3dbbce2dfe5b038167864972933519265d341665ee23646f37f5c1
4
- data.tar.gz: 808ede5d13382014d7c2ab5f58379160c0983b7db8ead184032b70a5f39b453a
3
+ metadata.gz: 15537c1595b19f0f0c351e4650b65ac23316538b7fbc5e3b55bb71c798cba25a
4
+ data.tar.gz: 6205e71eb9af6a625e5b95024156e2579fe8a687c274178f7a3aa255dc4a13ae
5
5
  SHA512:
6
- metadata.gz: afdb2f5ddd9026721f63328fb2efd4cc97799d952fea3288d8b090ca4daab696719df353eb8d515aafdfe5b62b165798cbde650cb9ffabaf7e827fae1487a321
7
- data.tar.gz: 1218800960726b410b98d5b8d0ca7cfed1aed9a1f916214a257d456ca08373123724764ec04680d1eabde7ebd9f25d98d77a43301d54ee5d0fa2ca74000b7873
6
+ metadata.gz: 2c3407b953637dc1ddf428f5795befc91969b8c0fb99c67b86c15e93cc6c02a7a9567367991135724e4920426cbd1cd0dcf4c65227aef0fa9600e9510246c838
7
+ data.tar.gz: 6602102b44527baa4aca0181174d5732407c207bb6139588b010f0c06775b61c80421b31bf0ea6b4b089e1cfa1f74379a151effcc76d384c2fd6d51ec5678729
data/bin/simulate CHANGED
@@ -9,6 +9,32 @@ module CloverSandboxSimulator
9
9
  # Command-line interface for Clover Sandbox Simulator
10
10
  class CLI < Thor
11
11
  class_option :verbose, type: :boolean, aliases: "-v", desc: "Enable verbose logging"
12
+ class_option :merchant, type: :string, aliases: "-m", desc: "Merchant ID to use (from .env.json)"
13
+ class_option :merchant_index, type: :numeric, aliases: "-i", desc: "Merchant index to use (0-based, from .env.json)"
14
+
15
+ desc "merchants", "List available merchants from .env.json"
16
+ def merchants
17
+ puts "šŸ½ļø Clover Sandbox Simulator - Available Merchants"
18
+ puts "=" * 60
19
+
20
+ config = CloverSandboxSimulator.configuration
21
+ merchants = config.available_merchants
22
+
23
+ if merchants.empty?
24
+ puts "No merchants found in .env.json"
25
+ return
26
+ end
27
+
28
+ puts "\n#{'Index'.ljust(6)} #{'Merchant ID'.ljust(16)} #{'Name'.ljust(25)} #{'eComm'}"
29
+ puts "-" * 60
30
+
31
+ merchants.each_with_index do |m, idx|
32
+ ecomm = m[:has_ecommerce] ? "āœ“" : "-"
33
+ puts "#{idx.to_s.ljust(6)} #{m[:id].ljust(16)} #{m[:name][0..23].ljust(25)} #{ecomm}"
34
+ end
35
+
36
+ puts "\nUse: simulate <command> -i <index> or -m <merchant_id>"
37
+ end
12
38
 
13
39
  desc "setup", "Set up restaurant entities (categories, items, discounts, employees, customers)"
14
40
  option :business_type, type: :string, default: "restaurant", desc: "Business type (restaurant, retail, salon)"
@@ -26,13 +52,15 @@ module CloverSandboxSimulator
26
52
 
27
53
  desc "generate", "Generate orders for today"
28
54
  option :count, type: :numeric, aliases: "-n", desc: "Number of orders to generate"
55
+ option :refund_percentage, type: :numeric, aliases: "-r", default: 5,
56
+ desc: "Percentage of orders to refund (0-100, default 5)"
29
57
  def generate
30
58
  configure_logging
31
59
 
32
60
  puts "šŸ½ļø Clover Sandbox Simulator - Order Generation"
33
61
  puts "=" * 50
34
62
 
35
- generator = Generators::OrderGenerator.new
63
+ generator = Generators::OrderGenerator.new(refund_percentage: options[:refund_percentage])
36
64
  count = options[:count]
37
65
 
38
66
  orders = generator.generate_today(count: count)
@@ -41,14 +69,16 @@ module CloverSandboxSimulator
41
69
  end
42
70
 
43
71
  desc "day", "Generate a realistic full day of restaurant operations"
44
- option :multiplier, type: :numeric, aliases: "-m", default: 1.0, desc: "Order multiplier (0.5 = slow day, 2.0 = busy day)"
72
+ option :multiplier, type: :numeric, aliases: "-x", default: 1.0, desc: "Order multiplier (0.5 = slow day, 2.0 = busy day)"
73
+ option :refund_percentage, type: :numeric, aliases: "-r", default: 5,
74
+ desc: "Percentage of orders to refund (0-100, default 5)"
45
75
  def day
46
76
  configure_logging
47
77
 
48
78
  puts "šŸ½ļø Clover Sandbox Simulator - Realistic Restaurant Day"
49
79
  puts "=" * 50
50
80
 
51
- generator = Generators::OrderGenerator.new
81
+ generator = Generators::OrderGenerator.new(refund_percentage: options[:refund_percentage])
52
82
  orders = generator.generate_realistic_day(multiplier: options[:multiplier])
53
83
 
54
84
  puts "\nāœ… Generated #{orders.size} orders!"
@@ -96,6 +126,8 @@ module CloverSandboxSimulator
96
126
  desc "full", "Run full simulation (setup + generate orders)"
97
127
  option :count, type: :numeric, aliases: "-n", desc: "Number of orders to generate"
98
128
  option :business_type, type: :string, default: "restaurant", desc: "Business type"
129
+ option :refund_percentage, type: :numeric, aliases: "-r", default: 5,
130
+ desc: "Percentage of orders to refund (0-100, default 5)"
99
131
  def full
100
132
  configure_logging
101
133
 
@@ -109,7 +141,7 @@ module CloverSandboxSimulator
109
141
  puts "\n"
110
142
 
111
143
  # Generate orders
112
- order_gen = Generators::OrderGenerator.new
144
+ order_gen = Generators::OrderGenerator.new(refund_percentage: options[:refund_percentage])
113
145
  orders = order_gen.generate_today(count: options[:count])
114
146
 
115
147
  puts "\nāœ… Full simulation complete!"
@@ -150,6 +182,7 @@ module CloverSandboxSimulator
150
182
  employees = services.employee.get_employees
151
183
  customers = services.customer.get_customers
152
184
  discounts = services.discount.get_discounts
185
+ refunds = services.refund.fetch_refunds
153
186
 
154
187
  puts "Categories: #{categories.size}"
155
188
  puts "Menu Items: #{items.size}"
@@ -157,6 +190,7 @@ module CloverSandboxSimulator
157
190
  puts "Employees: #{employees.size}"
158
191
  puts "Customers: #{customers.size}"
159
192
  puts "Discounts: #{discounts.size}"
193
+ puts "Refunds: #{refunds.size}"
160
194
 
161
195
  puts "\nšŸ“‹ Categories:"
162
196
  categories.each { |c| puts " - #{c['name']}" }
@@ -165,19 +199,262 @@ module CloverSandboxSimulator
165
199
  tenders.each { |t| puts " - #{t['label']}" }
166
200
  end
167
201
 
202
+ desc "refunds", "List all refunds"
203
+ option :limit, type: :numeric, aliases: "-l", desc: "Maximum number of refunds to show"
204
+ def refunds
205
+ configure_logging
206
+
207
+ puts "šŸ½ļø Clover Sandbox Simulator - Refunds"
208
+ puts "=" * 50
209
+
210
+ services = Services::Clover::ServicesManager.new
211
+ refunds = services.refund.fetch_refunds(limit: options[:limit])
212
+
213
+ if refunds.empty?
214
+ puts "No refunds found."
215
+ return
216
+ end
217
+
218
+ puts "Found #{refunds.size} refunds:\n\n"
219
+
220
+ total_amount = 0
221
+ refunds.each do |refund|
222
+ amount = refund["amount"] || 0
223
+ total_amount += amount
224
+ payment_id = refund.dig("payment", "id") || "N/A"
225
+ created_at = refund["createdTime"] ? Time.at(refund["createdTime"] / 1000).strftime("%Y-%m-%d %H:%M") : "N/A"
226
+
227
+ puts " ID: #{refund['id']}"
228
+ puts " Amount: $#{'%.2f' % (amount / 100.0)}"
229
+ puts " Payment: #{payment_id}"
230
+ puts " Created: #{created_at}"
231
+ puts ""
232
+ end
233
+
234
+ puts "-" * 40
235
+ puts "Total refunded: $#{'%.2f' % (total_amount / 100.0)}"
236
+ end
237
+
238
+ desc "refund", "Create a refund for a payment"
239
+ option :payment_id, type: :string, aliases: "-p", required: true, desc: "Payment ID to refund"
240
+ option :amount, type: :numeric, aliases: "-a", desc: "Amount in cents (omit for full refund)"
241
+ option :reason, type: :string, aliases: "-r", default: "customer_request",
242
+ desc: "Refund reason (customer_request, quality_issue, wrong_order, duplicate_charge)"
243
+ def refund
244
+ configure_logging
245
+
246
+ puts "šŸ½ļø Clover Sandbox Simulator - Create Refund"
247
+ puts "=" * 50
248
+
249
+ services = Services::Clover::ServicesManager.new
250
+
251
+ if options[:amount]
252
+ puts "Creating partial refund of $#{'%.2f' % (options[:amount] / 100.0)}..."
253
+ result = services.refund.create_partial_refund(
254
+ payment_id: options[:payment_id],
255
+ amount: options[:amount],
256
+ reason: options[:reason]
257
+ )
258
+ else
259
+ puts "Creating full refund..."
260
+ result = services.refund.create_full_refund(
261
+ payment_id: options[:payment_id],
262
+ reason: options[:reason]
263
+ )
264
+ end
265
+
266
+ if result && result["id"]
267
+ puts "\nāœ… Refund created successfully!"
268
+ puts " Refund ID: #{result['id']}"
269
+ puts " Amount: $#{'%.2f' % ((result['amount'] || 0) / 100.0)}"
270
+ else
271
+ puts "\nāŒ Failed to create refund."
272
+ end
273
+ end
274
+
168
275
  desc "version", "Show version"
169
276
  def version
170
277
  puts "Clover Sandbox Simulator v1.0.0"
171
278
  end
172
279
 
280
+ # ============================================
281
+ # Gift Card Commands
282
+ # ============================================
283
+
284
+ desc "gift_cards", "List all gift cards and their balances"
285
+ def gift_cards
286
+ configure_logging
287
+
288
+ puts "šŸŽ Clover Sandbox Simulator - Gift Cards"
289
+ puts "=" * 50
290
+
291
+ services = Services::Clover::ServicesManager.new
292
+ cards = services.gift_card.fetch_gift_cards
293
+
294
+ if cards.empty?
295
+ puts "No gift cards found."
296
+ puts "\nCreate one with: simulate gift_card_create --amount 5000"
297
+ return
298
+ end
299
+
300
+ puts "\n#{'ID'.ljust(20)} #{'Card Number'.ljust(20)} #{'Balance'.rjust(12)} #{'Status'.ljust(10)}"
301
+ puts "-" * 65
302
+
303
+ total_balance = 0
304
+ cards.each do |card|
305
+ id = card["id"] || "N/A"
306
+ number = mask_card_number(card["cardNumber"] || "Unknown")
307
+ balance = card["balance"] || 0
308
+ status = card["status"] || "UNKNOWN"
309
+
310
+ total_balance += balance if status == "ACTIVE"
311
+
312
+ balance_str = "$#{'%.2f' % (balance / 100.0)}"
313
+ puts "#{id.ljust(20)} #{number.ljust(20)} #{balance_str.rjust(12)} #{status.ljust(10)}"
314
+ end
315
+
316
+ puts "-" * 65
317
+ puts "Total Active Balance: $#{'%.2f' % (total_balance / 100.0)}"
318
+ puts "\nāœ… Found #{cards.size} gift cards"
319
+ end
320
+
321
+ desc "gift_card_create", "Create a new gift card"
322
+ option :amount, type: :numeric, aliases: "-a", required: true, desc: "Initial balance in cents (e.g., 5000 for $50)"
323
+ option :card_number, type: :string, aliases: "-n", desc: "Optional 16-digit card number"
324
+ def gift_card_create
325
+ configure_logging
326
+
327
+ puts "šŸŽ Clover Sandbox Simulator - Create Gift Card"
328
+ puts "=" * 50
329
+
330
+ services = Services::Clover::ServicesManager.new
331
+
332
+ amount = options[:amount]
333
+ card_number = options[:card_number]
334
+
335
+ puts "Creating gift card with balance: $#{'%.2f' % (amount / 100.0)}"
336
+
337
+ result = services.gift_card.create_gift_card(
338
+ amount: amount,
339
+ card_number: card_number
340
+ )
341
+
342
+ if result && result["id"]
343
+ puts "\nāœ… Gift card created successfully!"
344
+ puts " ID: #{result['id']}"
345
+ puts " Card Number: #{mask_card_number(result['cardNumber'])}"
346
+ puts " Balance: $#{'%.2f' % ((result['balance'] || amount) / 100.0)}"
347
+ puts " Status: #{result['status']}"
348
+ else
349
+ puts "\nāŒ Failed to create gift card"
350
+ end
351
+ end
352
+
353
+ desc "gift_card_balance", "Check gift card balance"
354
+ option :id, type: :string, aliases: "-i", required: true, desc: "Gift card ID"
355
+ def gift_card_balance
356
+ configure_logging
357
+
358
+ puts "šŸŽ Clover Sandbox Simulator - Gift Card Balance"
359
+ puts "=" * 50
360
+
361
+ services = Services::Clover::ServicesManager.new
362
+
363
+ card = services.gift_card.get_gift_card(options[:id])
364
+
365
+ if card
366
+ puts "\nGift Card Details:"
367
+ puts " ID: #{card['id']}"
368
+ puts " Card Number: #{mask_card_number(card['cardNumber'])}"
369
+ puts " Balance: $#{'%.2f' % ((card['balance'] || 0) / 100.0)}"
370
+ puts " Status: #{card['status']}"
371
+ else
372
+ puts "\nāŒ Gift card not found: #{options[:id]}"
373
+ end
374
+ end
375
+
376
+ desc "gift_card_reload", "Add balance to a gift card"
377
+ option :id, type: :string, aliases: "-i", required: true, desc: "Gift card ID"
378
+ option :amount, type: :numeric, aliases: "-a", required: true, desc: "Amount to add in cents"
379
+ def gift_card_reload
380
+ configure_logging
381
+
382
+ puts "šŸŽ Clover Sandbox Simulator - Reload Gift Card"
383
+ puts "=" * 50
384
+
385
+ services = Services::Clover::ServicesManager.new
386
+
387
+ gc_id = options[:id]
388
+ amount = options[:amount]
389
+
390
+ puts "Adding $#{'%.2f' % (amount / 100.0)} to gift card #{gc_id}"
391
+
392
+ result = services.gift_card.reload_gift_card(gc_id, amount: amount)
393
+
394
+ if result
395
+ puts "\nāœ… Gift card reloaded successfully!"
396
+ puts " New Balance: $#{'%.2f' % ((result['balance'] || 0) / 100.0)}"
397
+ else
398
+ puts "\nāŒ Failed to reload gift card"
399
+ end
400
+ end
401
+
402
+ desc "gift_card_redeem", "Redeem/use balance from a gift card"
403
+ option :id, type: :string, aliases: "-i", required: true, desc: "Gift card ID"
404
+ option :amount, type: :numeric, aliases: "-a", required: true, desc: "Amount to redeem in cents"
405
+ def gift_card_redeem
406
+ configure_logging
407
+
408
+ puts "šŸŽ Clover Sandbox Simulator - Redeem Gift Card"
409
+ puts "=" * 50
410
+
411
+ services = Services::Clover::ServicesManager.new
412
+
413
+ gc_id = options[:id]
414
+ amount = options[:amount]
415
+
416
+ puts "Redeeming $#{'%.2f' % (amount / 100.0)} from gift card #{gc_id}"
417
+
418
+ result = services.gift_card.redeem_gift_card(gc_id, amount: amount)
419
+
420
+ if result[:success]
421
+ puts "\nāœ… Redemption successful!"
422
+ puts " Amount Redeemed: $#{'%.2f' % (result[:amount_redeemed] / 100.0)}"
423
+ puts " Remaining Balance: $#{'%.2f' % (result[:remaining_balance] / 100.0)}"
424
+
425
+ if result[:shortfall] > 0
426
+ puts " āš ļø Shortfall: $#{'%.2f' % (result[:shortfall] / 100.0)} (insufficient balance)"
427
+ end
428
+ else
429
+ puts "\nāŒ Redemption failed: #{result[:message]}"
430
+ end
431
+ end
432
+
173
433
  private
174
434
 
435
+ def mask_card_number(card_number)
436
+ return card_number if card_number.nil? || card_number.length < 8
437
+
438
+ "#{card_number[0..3]}********#{card_number[-4..]}"
439
+ end
440
+
175
441
  def configure_logging
442
+ config = CloverSandboxSimulator.configuration
443
+
176
444
  if options[:verbose]
177
- CloverSandboxSimulator.configuration.logger.level = Logger::DEBUG
445
+ config.logger.level = Logger::DEBUG
178
446
  else
179
- CloverSandboxSimulator.configuration.logger.level = Logger::INFO
447
+ config.logger.level = Logger::INFO
180
448
  end
449
+
450
+ # Load specific merchant if specified
451
+ if options[:merchant]
452
+ config.load_merchant(merchant_id: options[:merchant])
453
+ elsif options[:merchant_index]
454
+ config.load_merchant(index: options[:merchant_index])
455
+ end
456
+
457
+ puts "Using merchant: #{config.merchant_name} (#{config.merchant_id})"
181
458
  end
182
459
 
183
460
  def print_order_summary(orders)
@@ -2,20 +2,114 @@
2
2
 
3
3
  module CloverSandboxSimulator
4
4
  class Configuration
5
- attr_accessor :merchant_id, :api_token, :environment, :log_level, :tax_rate, :business_type
5
+ attr_accessor :merchant_id, :merchant_name, :api_token, :environment, :log_level, :tax_rate, :business_type,
6
+ :public_token, :private_token, :ecommerce_environment, :tokenizer_environment,
7
+ :app_id, :app_secret, :refresh_token
8
+
9
+ # Path to merchants JSON file
10
+ MERCHANTS_FILE = File.join(File.dirname(__FILE__), "..", "..", ".env.json")
6
11
 
7
12
  def initialize
8
13
  @merchant_id = ENV.fetch("CLOVER_MERCHANT_ID", nil)
14
+ @merchant_name = ENV.fetch("CLOVER_MERCHANT_NAME", nil)
9
15
  @api_token = ENV.fetch("CLOVER_API_TOKEN", nil)
10
16
  @environment = normalize_url(ENV.fetch("CLOVER_ENVIRONMENT", "https://sandbox.dev.clover.com/"))
11
17
  @log_level = parse_log_level(ENV.fetch("LOG_LEVEL", "INFO"))
12
18
  @tax_rate = ENV.fetch("TAX_RATE", "8.25").to_f
13
19
  @business_type = ENV.fetch("BUSINESS_TYPE", "restaurant").to_sym
20
+
21
+ # Ecommerce API tokens (for card payments and refunds)
22
+ @public_token = ENV.fetch("PUBLIC_TOKEN", nil)
23
+ @private_token = ENV.fetch("PRIVATE_TOKEN", nil)
24
+ @ecommerce_environment = normalize_url(ENV.fetch("ECOMMERCE_ENVIRONMENT", "https://scl-sandbox.dev.clover.com/"))
25
+ @tokenizer_environment = normalize_url(ENV.fetch("TOKENIZER_ENVIRONMENT", "https://token-sandbox.dev.clover.com/"))
26
+
27
+ # OAuth credentials (for token refresh)
28
+ @app_id = ENV.fetch("CLOVER_APP_ID", nil)
29
+ @app_secret = ENV.fetch("CLOVER_APP_SECRET", nil)
30
+ @refresh_token = ENV.fetch("CLOVER_REFRESH_TOKEN", nil)
31
+
32
+ # Load from .env.json if merchant_id not set in ENV
33
+ load_from_merchants_file if @merchant_id.nil? || @merchant_id.empty?
34
+ end
35
+
36
+ # Check if OAuth is configured for token refresh
37
+ def oauth_enabled?
38
+ !app_id.nil? && !app_id.empty? &&
39
+ !app_secret.nil? && !app_secret.empty?
40
+ end
41
+
42
+ # Load configuration for a specific merchant from .env.json
43
+ #
44
+ # @param merchant_id [String, nil] Merchant ID to load (nil for first merchant)
45
+ # @param index [Integer, nil] Index of merchant in the list (0-based)
46
+ # @return [self]
47
+ def load_merchant(merchant_id: nil, index: nil)
48
+ merchants = load_merchants_file
49
+ return self if merchants.empty?
50
+
51
+ merchant = if merchant_id
52
+ merchants.find { |m| m["CLOVER_MERCHANT_ID"] == merchant_id }
53
+ elsif index
54
+ merchants[index]
55
+ else
56
+ merchants.first
57
+ end
58
+
59
+ if merchant
60
+ apply_merchant_config(merchant)
61
+ logger.info "Loaded merchant: #{@merchant_name} (#{@merchant_id})"
62
+ else
63
+ logger.warn "Merchant not found: #{merchant_id || "index #{index}"}"
64
+ end
65
+
66
+ self
67
+ end
68
+
69
+ # List all available merchants from .env.json
70
+ #
71
+ # @return [Array<Hash>] Array of merchant configs
72
+ def available_merchants
73
+ load_merchants_file.map do |m|
74
+ {
75
+ id: m["CLOVER_MERCHANT_ID"],
76
+ name: m["CLOVER_MERCHANT_NAME"],
77
+ has_ecommerce: !m["PUBLIC_TOKEN"].to_s.empty? && !m["PRIVATE_TOKEN"].to_s.empty?
78
+ }
79
+ end
14
80
  end
15
81
 
16
82
  def validate!
17
83
  raise ConfigurationError, "CLOVER_MERCHANT_ID is required" if merchant_id.nil? || merchant_id.empty?
18
- raise ConfigurationError, "CLOVER_API_TOKEN is required" if api_token.nil? || api_token.empty?
84
+
85
+ # API token is only required for Platform API operations
86
+ # Ecommerce-only operations can work without it
87
+ true
88
+ end
89
+
90
+ # Validate for Platform API operations (requires OAuth token)
91
+ def validate_platform!
92
+ validate!
93
+ raise ConfigurationError, "CLOVER_API_TOKEN is required for Platform API" if api_token.nil? || api_token.empty?
94
+
95
+ true
96
+ end
97
+
98
+ # Check if Platform API is configured (has OAuth token)
99
+ def platform_enabled?
100
+ !api_token.nil? && !api_token.empty? && api_token != "NEEDS_REFRESH"
101
+ end
102
+
103
+ # Check if Ecommerce API is configured
104
+ def ecommerce_enabled?
105
+ !public_token.nil? && !public_token.empty? &&
106
+ !private_token.nil? && !private_token.empty?
107
+ end
108
+
109
+ # Validate Ecommerce configuration
110
+ def validate_ecommerce!
111
+ raise ConfigurationError, "PUBLIC_TOKEN is required for Ecommerce API" if public_token.nil? || public_token.empty?
112
+ raise ConfigurationError, "PRIVATE_TOKEN is required for Ecommerce API" if private_token.nil? || private_token.empty?
19
113
 
20
114
  true
21
115
  end
@@ -32,6 +126,39 @@ module CloverSandboxSimulator
32
126
 
33
127
  private
34
128
 
129
+ def load_from_merchants_file
130
+ merchants = load_merchants_file
131
+ return if merchants.empty?
132
+
133
+ # Use first merchant by default
134
+ apply_merchant_config(merchants.first)
135
+ end
136
+
137
+ def load_merchants_file
138
+ return [] unless File.exist?(MERCHANTS_FILE)
139
+
140
+ JSON.parse(File.read(MERCHANTS_FILE))
141
+ rescue JSON::ParserError => e
142
+ warn "Failed to parse #{MERCHANTS_FILE}: #{e.message}"
143
+ []
144
+ end
145
+
146
+ def apply_merchant_config(merchant)
147
+ @merchant_id = merchant["CLOVER_MERCHANT_ID"]
148
+ @merchant_name = merchant["CLOVER_MERCHANT_NAME"]
149
+ # CLOVER_API_TOKEN = static API token (never expires)
150
+ # CLOVER_ACCESS_TOKEN = OAuth JWT token (expires, can be refreshed)
151
+ # Prefer static API token if available, fall back to OAuth token
152
+ if merchant["CLOVER_API_TOKEN"].to_s.length > 10
153
+ @api_token = merchant["CLOVER_API_TOKEN"]
154
+ elsif merchant["CLOVER_ACCESS_TOKEN"].to_s.length > 10
155
+ @api_token = merchant["CLOVER_ACCESS_TOKEN"]
156
+ end
157
+ @refresh_token = merchant["CLOVER_REFRESH_TOKEN"] if merchant["CLOVER_REFRESH_TOKEN"].to_s.length > 10
158
+ @public_token = merchant["PUBLIC_TOKEN"] unless merchant["PUBLIC_TOKEN"].to_s.empty?
159
+ @private_token = merchant["PRIVATE_TOKEN"] unless merchant["PRIVATE_TOKEN"].to_s.empty?
160
+ end
161
+
35
162
  def normalize_url(url)
36
163
  url = url.strip
37
164
  url.end_with?("/") ? url : "#{url}/"