clover_sandbox_simulator 1.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.
Files changed (25) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/README.md +316 -0
  4. data/bin/simulate +209 -0
  5. data/lib/clover_sandbox_simulator/configuration.rb +51 -0
  6. data/lib/clover_sandbox_simulator/data/restaurant/categories.json +39 -0
  7. data/lib/clover_sandbox_simulator/data/restaurant/discounts.json +39 -0
  8. data/lib/clover_sandbox_simulator/data/restaurant/items.json +238 -0
  9. data/lib/clover_sandbox_simulator/data/restaurant/modifiers.json +62 -0
  10. data/lib/clover_sandbox_simulator/data/restaurant/tenders.json +41 -0
  11. data/lib/clover_sandbox_simulator/generators/data_loader.rb +54 -0
  12. data/lib/clover_sandbox_simulator/generators/entity_generator.rb +164 -0
  13. data/lib/clover_sandbox_simulator/generators/order_generator.rb +540 -0
  14. data/lib/clover_sandbox_simulator/services/base_service.rb +111 -0
  15. data/lib/clover_sandbox_simulator/services/clover/customer_service.rb +82 -0
  16. data/lib/clover_sandbox_simulator/services/clover/discount_service.rb +58 -0
  17. data/lib/clover_sandbox_simulator/services/clover/employee_service.rb +82 -0
  18. data/lib/clover_sandbox_simulator/services/clover/inventory_service.rb +120 -0
  19. data/lib/clover_sandbox_simulator/services/clover/order_service.rb +170 -0
  20. data/lib/clover_sandbox_simulator/services/clover/payment_service.rb +123 -0
  21. data/lib/clover_sandbox_simulator/services/clover/services_manager.rb +49 -0
  22. data/lib/clover_sandbox_simulator/services/clover/tax_service.rb +53 -0
  23. data/lib/clover_sandbox_simulator/services/clover/tender_service.rb +117 -0
  24. data/lib/clover_sandbox_simulator.rb +43 -0
  25. metadata +195 -0
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloverSandboxSimulator
4
+ module Services
5
+ module Clover
6
+ # Manages Clover tax rates
7
+ class TaxService < BaseService
8
+ # Fetch all tax rates
9
+ def get_tax_rates
10
+ logger.info "Fetching tax rates..."
11
+ response = request(:get, endpoint("tax_rates"))
12
+ elements = response&.dig("elements") || []
13
+ logger.info "Found #{elements.size} tax rates"
14
+ elements
15
+ end
16
+
17
+ # Get default tax rate
18
+ def default_tax_rate
19
+ rates = get_tax_rates
20
+ # Find default rate or return first active one
21
+ rates.find { |r| r["isDefault"] == true } || rates.first
22
+ end
23
+
24
+ # Create a tax rate
25
+ def create_tax_rate(name:, rate:, is_default: false)
26
+ logger.info "Creating tax rate: #{name} (#{rate}%)"
27
+
28
+ # Rate is stored as basis points (8.25% = 825000)
29
+ rate_basis_points = (rate * 100_000).to_i
30
+
31
+ request(:post, endpoint("tax_rates"), payload: {
32
+ "name" => name,
33
+ "rate" => rate_basis_points,
34
+ "isDefault" => is_default,
35
+ "taxType" => "VAT_EXEMPT" # For US sales tax
36
+ })
37
+ end
38
+
39
+ # Delete a tax rate
40
+ def delete_tax_rate(tax_rate_id)
41
+ logger.info "Deleting tax rate: #{tax_rate_id}"
42
+ request(:delete, endpoint("tax_rates/#{tax_rate_id}"))
43
+ end
44
+
45
+ # Calculate tax for an amount
46
+ def calculate_tax(subtotal, tax_rate = nil)
47
+ rate = tax_rate || config.tax_rate
48
+ (subtotal * rate / 100.0).round
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloverSandboxSimulator
4
+ module Services
5
+ module Clover
6
+ # Manages Clover payment tenders (Cash, Gift Card, etc.)
7
+ class TenderService < BaseService
8
+ # Tenders that work reliably in Clover sandbox
9
+ # NOTE: Credit Card and Debit Card are BROKEN in sandbox - do not use!
10
+ SANDBOX_SAFE_TENDERS = %w[
11
+ com.clover.tender.cash
12
+ com.clover.tender.check
13
+ com.clover.tender.external_gift_card
14
+ com.clover.tender.external_payment
15
+ ].freeze
16
+
17
+ # Fetch all tenders
18
+ def get_tenders
19
+ logger.info "Fetching tenders..."
20
+ response = request(:get, endpoint("tenders"))
21
+ elements = response&.dig("elements") || []
22
+
23
+ # Filter to enabled tenders only
24
+ enabled = elements.select { |t| t["enabled"] == true }
25
+ logger.info "Found #{enabled.size} enabled tenders"
26
+ enabled
27
+ end
28
+
29
+ # Get sandbox-safe tenders (excludes credit/debit cards)
30
+ def get_safe_tenders
31
+ tenders = get_tenders
32
+
33
+ safe = tenders.reject do |tender|
34
+ label = tender["label"]&.downcase || ""
35
+ label_key = tender["labelKey"]&.downcase || ""
36
+
37
+ # Exclude credit and debit cards - they're broken in sandbox
38
+ label.include?("credit") ||
39
+ label.include?("debit") ||
40
+ label_key.include?("credit") ||
41
+ label_key.include?("debit")
42
+ end
43
+
44
+ logger.info "Found #{safe.size} sandbox-safe tenders"
45
+ safe
46
+ end
47
+
48
+ # Get a specific tender by label
49
+ def find_tender_by_label(label)
50
+ tenders = get_tenders
51
+ tenders.find { |t| t["label"]&.downcase == label.downcase }
52
+ end
53
+
54
+ # Get cash tender
55
+ def cash_tender
56
+ find_tender_by_label("Cash") || get_safe_tenders.first
57
+ end
58
+
59
+ # Create a custom tender
60
+ def create_tender(label:, label_key: nil, enabled: true, opens_cash_drawer: false)
61
+ logger.info "Creating tender: #{label}"
62
+
63
+ payload = {
64
+ "label" => label,
65
+ "labelKey" => label_key || "com.clover.tender.#{label.downcase.gsub(/\s+/, '_')}",
66
+ "enabled" => enabled,
67
+ "opensCashDrawer" => opens_cash_drawer
68
+ }
69
+
70
+ request(:post, endpoint("tenders"), payload: payload)
71
+ end
72
+
73
+ # Select random safe tenders for split payment
74
+ # Returns array of tender IDs with split percentages
75
+ def select_split_tenders(num_splits: nil)
76
+ tenders = get_safe_tenders
77
+ return [] if tenders.empty?
78
+
79
+ # Determine number of splits (1-3)
80
+ num_splits ||= rand(1..3)
81
+ num_splits = [num_splits, tenders.size].min
82
+
83
+ # Select random tenders
84
+ selected = tenders.sample(num_splits)
85
+
86
+ # Generate random split percentages that sum to 100
87
+ percentages = generate_split_percentages(num_splits)
88
+
89
+ selected.zip(percentages).map do |tender, percentage|
90
+ { tender: tender, percentage: percentage }
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def generate_split_percentages(count)
97
+ return [100] if count == 1
98
+
99
+ # Generate random split points
100
+ points = Array.new(count - 1) { rand(10..90) }.sort
101
+
102
+ # Calculate percentages from split points
103
+ percentages = []
104
+ prev = 0
105
+ points.each do |point|
106
+ percentages << (point - prev)
107
+ prev = point
108
+ end
109
+ percentages << (100 - prev)
110
+
111
+ # Ensure no percentage is less than 5%
112
+ percentages.map { |p| [p, 5].max }
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ require "logger"
5
+ require "json"
6
+ require "rest-client"
7
+ require "dotenv"
8
+
9
+ # Load environment variables
10
+ Dotenv.load
11
+
12
+ module CloverSandboxSimulator
13
+ class Error < StandardError; end
14
+ class ConfigurationError < Error; end
15
+ class ApiError < Error; end
16
+
17
+ class << self
18
+ attr_writer :configuration
19
+
20
+ def configuration
21
+ @configuration ||= Configuration.new
22
+ end
23
+
24
+ def configure
25
+ yield(configuration)
26
+ end
27
+
28
+ def logger
29
+ configuration.logger
30
+ end
31
+
32
+ def root
33
+ File.expand_path("..", __dir__)
34
+ end
35
+ end
36
+ end
37
+
38
+ # Set up Zeitwerk autoloader
39
+ loader = Zeitwerk::Loader.for_gem
40
+ loader.setup
41
+
42
+ # Eager load in production
43
+ loader.eager_load if ENV["RACK_ENV"] == "production"
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clover_sandbox_simulator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - dan1d
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faker
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: zeitwerk
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.6'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.12'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.12'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.50'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.50'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.18'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.18'
125
+ - !ruby/object:Gem::Dependency
126
+ name: vcr
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '6.1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '6.1'
139
+ description: A Ruby gem for simulating Point of Sale operations in Clover sandbox
140
+ environments. Generates realistic restaurant orders, payments, and transaction data
141
+ for testing integrations with Clover's API.
142
+ email:
143
+ - dan1d@users.noreply.github.com
144
+ executables:
145
+ - simulate
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - Gemfile
150
+ - README.md
151
+ - bin/simulate
152
+ - lib/clover_sandbox_simulator.rb
153
+ - lib/clover_sandbox_simulator/configuration.rb
154
+ - lib/clover_sandbox_simulator/data/restaurant/categories.json
155
+ - lib/clover_sandbox_simulator/data/restaurant/discounts.json
156
+ - lib/clover_sandbox_simulator/data/restaurant/items.json
157
+ - lib/clover_sandbox_simulator/data/restaurant/modifiers.json
158
+ - lib/clover_sandbox_simulator/data/restaurant/tenders.json
159
+ - lib/clover_sandbox_simulator/generators/data_loader.rb
160
+ - lib/clover_sandbox_simulator/generators/entity_generator.rb
161
+ - lib/clover_sandbox_simulator/generators/order_generator.rb
162
+ - lib/clover_sandbox_simulator/services/base_service.rb
163
+ - lib/clover_sandbox_simulator/services/clover/customer_service.rb
164
+ - lib/clover_sandbox_simulator/services/clover/discount_service.rb
165
+ - lib/clover_sandbox_simulator/services/clover/employee_service.rb
166
+ - lib/clover_sandbox_simulator/services/clover/inventory_service.rb
167
+ - lib/clover_sandbox_simulator/services/clover/order_service.rb
168
+ - lib/clover_sandbox_simulator/services/clover/payment_service.rb
169
+ - lib/clover_sandbox_simulator/services/clover/services_manager.rb
170
+ - lib/clover_sandbox_simulator/services/clover/tax_service.rb
171
+ - lib/clover_sandbox_simulator/services/clover/tender_service.rb
172
+ homepage: https://github.com/dan1d/clover_sandbox_simulator
173
+ licenses:
174
+ - MIT
175
+ metadata: {}
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: 3.0.0
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.4.10
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: Clover Sandbox Simulator for generating realistic restaurant data
195
+ test_files: []