square_sandbox_simulator 0.1.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/LICENSE.txt +21 -0
- data/README.md +176 -0
- data/bin/simulate +388 -0
- data/lib/square_sandbox_simulator/configuration.rb +193 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/categories.json +54 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/combos.json +33 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/coupon_codes.json +133 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/discounts.json +113 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/items.json +55 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/modifiers.json +73 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/tax_rates.json +26 -0
- data/lib/square_sandbox_simulator/data/cafe_bakery/tenders.json +41 -0
- data/lib/square_sandbox_simulator/data/restaurant/categories.json +54 -0
- data/lib/square_sandbox_simulator/data/restaurant/combos.json +265 -0
- data/lib/square_sandbox_simulator/data/restaurant/coupon_codes.json +266 -0
- data/lib/square_sandbox_simulator/data/restaurant/discounts.json +198 -0
- data/lib/square_sandbox_simulator/data/restaurant/gift_cards.json +82 -0
- data/lib/square_sandbox_simulator/data/restaurant/items.json +388 -0
- data/lib/square_sandbox_simulator/data/restaurant/modifiers.json +62 -0
- data/lib/square_sandbox_simulator/data/restaurant/tax_rates.json +38 -0
- data/lib/square_sandbox_simulator/data/restaurant/tenders.json +41 -0
- data/lib/square_sandbox_simulator/data/salon_spa/categories.json +24 -0
- data/lib/square_sandbox_simulator/data/salon_spa/combos.json +88 -0
- data/lib/square_sandbox_simulator/data/salon_spa/coupon_codes.json +96 -0
- data/lib/square_sandbox_simulator/data/salon_spa/discounts.json +93 -0
- data/lib/square_sandbox_simulator/data/salon_spa/gift_cards.json +47 -0
- data/lib/square_sandbox_simulator/data/salon_spa/items.json +100 -0
- data/lib/square_sandbox_simulator/data/salon_spa/modifiers.json +49 -0
- data/lib/square_sandbox_simulator/data/salon_spa/tax_rates.json +17 -0
- data/lib/square_sandbox_simulator/data/salon_spa/tenders.json +41 -0
- data/lib/square_sandbox_simulator/database.rb +224 -0
- data/lib/square_sandbox_simulator/db/factories/api_requests.rb +95 -0
- data/lib/square_sandbox_simulator/db/factories/business_types.rb +178 -0
- data/lib/square_sandbox_simulator/db/factories/categories.rb +379 -0
- data/lib/square_sandbox_simulator/db/factories/daily_summaries.rb +56 -0
- data/lib/square_sandbox_simulator/db/factories/items.rb +1526 -0
- data/lib/square_sandbox_simulator/db/factories/simulated_orders.rb +112 -0
- data/lib/square_sandbox_simulator/db/factories/simulated_payments.rb +61 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000000_enable_pgcrypto.rb +7 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000001_create_business_types.rb +18 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000002_create_categories.rb +18 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000003_create_items.rb +23 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000004_create_simulated_orders.rb +36 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000005_create_simulated_payments.rb +26 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000006_create_api_requests.rb +27 -0
- data/lib/square_sandbox_simulator/db/migrate/20260312000007_create_daily_summaries.rb +24 -0
- data/lib/square_sandbox_simulator/generators/data_loader.rb +202 -0
- data/lib/square_sandbox_simulator/generators/entity_generator.rb +248 -0
- data/lib/square_sandbox_simulator/generators/order_generator.rb +632 -0
- data/lib/square_sandbox_simulator/models/api_request.rb +43 -0
- data/lib/square_sandbox_simulator/models/business_type.rb +25 -0
- data/lib/square_sandbox_simulator/models/category.rb +18 -0
- data/lib/square_sandbox_simulator/models/daily_summary.rb +68 -0
- data/lib/square_sandbox_simulator/models/item.rb +33 -0
- data/lib/square_sandbox_simulator/models/record.rb +16 -0
- data/lib/square_sandbox_simulator/models/simulated_order.rb +42 -0
- data/lib/square_sandbox_simulator/models/simulated_payment.rb +28 -0
- data/lib/square_sandbox_simulator/seeder.rb +242 -0
- data/lib/square_sandbox_simulator/services/base_service.rb +253 -0
- data/lib/square_sandbox_simulator/services/square/catalog_service.rb +203 -0
- data/lib/square_sandbox_simulator/services/square/customer_service.rb +130 -0
- data/lib/square_sandbox_simulator/services/square/order_service.rb +121 -0
- data/lib/square_sandbox_simulator/services/square/payment_service.rb +136 -0
- data/lib/square_sandbox_simulator/services/square/services_manager.rb +68 -0
- data/lib/square_sandbox_simulator/services/square/team_service.rb +108 -0
- data/lib/square_sandbox_simulator/version.rb +5 -0
- data/lib/square_sandbox_simulator.rb +47 -0
- metadata +348 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{
|
|
4
|
+
"label": "Cash",
|
|
5
|
+
"label_key": "com.clover.tender.cash",
|
|
6
|
+
"opens_cash_drawer": true,
|
|
7
|
+
"weight": 25
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"label": "Check",
|
|
11
|
+
"label_key": "com.clover.tender.check",
|
|
12
|
+
"opens_cash_drawer": true,
|
|
13
|
+
"weight": 5
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"label": "Gift Card",
|
|
17
|
+
"label_key": "com.clover.tender.external_gift_card",
|
|
18
|
+
"opens_cash_drawer": false,
|
|
19
|
+
"weight": 20
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"label": "External Payment",
|
|
23
|
+
"label_key": "com.clover.tender.external_payment",
|
|
24
|
+
"opens_cash_drawer": false,
|
|
25
|
+
"weight": 10
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"label": "Mobile Payment",
|
|
29
|
+
"label_key": "com.clover.tender.mobile_payment",
|
|
30
|
+
"opens_cash_drawer": false,
|
|
31
|
+
"weight": 20
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"label": "Store Credit",
|
|
35
|
+
"label_key": "com.clover.tender.store_credit",
|
|
36
|
+
"opens_cash_drawer": false,
|
|
37
|
+
"weight": 10
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"note": "Credit Card and Debit Card are intentionally excluded - they are broken in Clover sandbox"
|
|
41
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "logger"
|
|
5
|
+
|
|
6
|
+
module SquareSandboxSimulator
|
|
7
|
+
# Standalone ActiveRecord connection manager for PostgreSQL.
|
|
8
|
+
#
|
|
9
|
+
# Provides database connectivity without requiring Rails.
|
|
10
|
+
# Used for persisting Square sandbox data (locations, orders, etc.)
|
|
11
|
+
# alongside the existing JSON-file and API-based workflows.
|
|
12
|
+
#
|
|
13
|
+
# When no DATABASE_URL is configured, the simulator falls back
|
|
14
|
+
# to its original JSON-file behaviour -- call Database.connected?
|
|
15
|
+
# to check before attempting DB operations.
|
|
16
|
+
#
|
|
17
|
+
# @example Connect and run migrations
|
|
18
|
+
# SquareSandboxSimulator::Database.connect!("postgres://localhost:5432/square_simulator_development")
|
|
19
|
+
# SquareSandboxSimulator::Database.migrate!
|
|
20
|
+
#
|
|
21
|
+
# @example Check availability for JSON fallback
|
|
22
|
+
# if SquareSandboxSimulator::Database.connected?
|
|
23
|
+
# # use ActiveRecord models
|
|
24
|
+
# else
|
|
25
|
+
# # fall back to JSON files
|
|
26
|
+
# end
|
|
27
|
+
module Database
|
|
28
|
+
# Directory containing ActiveRecord migration files
|
|
29
|
+
MIGRATIONS_PATH = File.expand_path("db/migrate", __dir__).freeze
|
|
30
|
+
|
|
31
|
+
# Default test database name
|
|
32
|
+
TEST_DATABASE = "square_simulator_test"
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
# Create the database specified in the connection URL.
|
|
36
|
+
#
|
|
37
|
+
# Connects to the `postgres` maintenance database, issues
|
|
38
|
+
# CREATE DATABASE, then disconnects.
|
|
39
|
+
#
|
|
40
|
+
# @param url [String] PostgreSQL connection URL
|
|
41
|
+
# @return [void]
|
|
42
|
+
def create!(url)
|
|
43
|
+
db_name = URI.parse(url).path.delete_prefix("/")
|
|
44
|
+
maintenance_url = url.sub(%r{/[^/]+\z}, "/postgres")
|
|
45
|
+
|
|
46
|
+
ActiveRecord::Base.establish_connection(maintenance_url)
|
|
47
|
+
ActiveRecord::Base.connection.create_database(db_name)
|
|
48
|
+
SquareSandboxSimulator.logger.info("Database created: #{db_name}")
|
|
49
|
+
rescue ActiveRecord::DatabaseAlreadyExists, ActiveRecord::StatementInvalid => e
|
|
50
|
+
raise unless e.message.include?("already exists")
|
|
51
|
+
|
|
52
|
+
SquareSandboxSimulator.logger.info("Database already exists: #{db_name}")
|
|
53
|
+
ensure
|
|
54
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Drop the database specified in the connection URL.
|
|
58
|
+
#
|
|
59
|
+
# @param url [String] PostgreSQL connection URL
|
|
60
|
+
# @return [void]
|
|
61
|
+
def drop!(url)
|
|
62
|
+
db_name = URI.parse(url).path.delete_prefix("/")
|
|
63
|
+
maintenance_url = url.sub(%r{/[^/]+\z}, "/postgres")
|
|
64
|
+
|
|
65
|
+
ActiveRecord::Base.establish_connection(maintenance_url)
|
|
66
|
+
ActiveRecord::Base.connection.drop_database(db_name)
|
|
67
|
+
SquareSandboxSimulator.logger.info("Database dropped: #{db_name}")
|
|
68
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
69
|
+
raise unless e.message.include?("does not exist")
|
|
70
|
+
|
|
71
|
+
SquareSandboxSimulator.logger.info("Database does not exist: #{db_name}")
|
|
72
|
+
ensure
|
|
73
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Return the configured database URL from .env.json.
|
|
77
|
+
#
|
|
78
|
+
# @return [String] PostgreSQL URL
|
|
79
|
+
# @raise [Error] if no DATABASE_URL is configured
|
|
80
|
+
def database_url
|
|
81
|
+
url = Configuration.database_url_from_file
|
|
82
|
+
raise Error, "No DATABASE_URL found in .env.json" unless url
|
|
83
|
+
|
|
84
|
+
url
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Establish a standalone ActiveRecord connection to PostgreSQL.
|
|
88
|
+
#
|
|
89
|
+
# @param url [String] A PostgreSQL connection URL
|
|
90
|
+
# (e.g. "postgres://user:pass@localhost:5432/square_simulator_development")
|
|
91
|
+
# @return [void]
|
|
92
|
+
# @raise [ArgumentError] if the URL is not a PostgreSQL URL
|
|
93
|
+
# @raise [ActiveRecord::ConnectionNotEstablished] if the connection fails
|
|
94
|
+
def connect!(url)
|
|
95
|
+
unless url.match?(%r{\Apostgres(ql)?://}i)
|
|
96
|
+
raise ArgumentError, "Expected a PostgreSQL URL (postgres:// or postgresql://), got: #{url.split("://").first}://"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
ActiveRecord::Base.establish_connection(url)
|
|
100
|
+
|
|
101
|
+
# Verify the connection is actually usable
|
|
102
|
+
ActiveRecord::Base.connection.execute("SELECT 1")
|
|
103
|
+
|
|
104
|
+
ActiveRecord::Base.logger = SquareSandboxSimulator.logger
|
|
105
|
+
|
|
106
|
+
SquareSandboxSimulator.logger.info("Database connected: #{sanitize_url(url)}")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Run pending migrations from lib/square_sandbox_simulator/db/migrate/.
|
|
110
|
+
#
|
|
111
|
+
# Uses ActiveRecord::MigrationContext for standalone (non-Rails) usage.
|
|
112
|
+
#
|
|
113
|
+
# @return [void]
|
|
114
|
+
def migrate!
|
|
115
|
+
ensure_connected!
|
|
116
|
+
|
|
117
|
+
SquareSandboxSimulator.logger.info("Running migrations from #{MIGRATIONS_PATH}")
|
|
118
|
+
|
|
119
|
+
context = ActiveRecord::MigrationContext.new(MIGRATIONS_PATH)
|
|
120
|
+
context.migrate
|
|
121
|
+
|
|
122
|
+
SquareSandboxSimulator.logger.info("Migrations complete")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Seed the database with realistic Square data using FactoryBot.
|
|
126
|
+
#
|
|
127
|
+
# Delegates to {Seeder} which creates BusinessTypes, Categories,
|
|
128
|
+
# and Items using factory attributes. Idempotent -- safe to call
|
|
129
|
+
# multiple times without creating duplicates.
|
|
130
|
+
#
|
|
131
|
+
# @param business_type [Symbol, String, nil] Optional business type
|
|
132
|
+
# (e.g. :restaurant, :retail_clothing). Seeds all types if nil.
|
|
133
|
+
# @return [Hash] Summary counts (:business_types, :categories, :items)
|
|
134
|
+
def seed!(business_type: nil)
|
|
135
|
+
ensure_connected!
|
|
136
|
+
load_factories!
|
|
137
|
+
|
|
138
|
+
# Default to configured business_type if none specified;
|
|
139
|
+
# pass nil to Seeder to seed ALL types when config is also nil.
|
|
140
|
+
business_type ||= SquareSandboxSimulator.configuration.business_type
|
|
141
|
+
Seeder.seed!(business_type: business_type)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Check whether a database connection is established and usable.
|
|
145
|
+
#
|
|
146
|
+
# Safe to call at any time -- returns false rather than raising
|
|
147
|
+
# so callers can decide to fall back to JSON files.
|
|
148
|
+
#
|
|
149
|
+
# @return [Boolean]
|
|
150
|
+
def connected?
|
|
151
|
+
ActiveRecord::Base.connection_pool.with_connection(&:active?)
|
|
152
|
+
rescue StandardError
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Disconnect from the database and clean up the connection pool.
|
|
157
|
+
#
|
|
158
|
+
# @return [void]
|
|
159
|
+
def disconnect!
|
|
160
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
161
|
+
SquareSandboxSimulator.logger.info("Database disconnected")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Build the test database URL.
|
|
165
|
+
#
|
|
166
|
+
# @param base_url [String, nil] Base URL to derive test URL from.
|
|
167
|
+
# If nil, reads DATABASE_URL from .env.json and swaps the DB name.
|
|
168
|
+
# @return [String] PostgreSQL URL pointing to the test database
|
|
169
|
+
def test_database_url(base_url: nil)
|
|
170
|
+
url = base_url || Configuration.database_url_from_file
|
|
171
|
+
return "postgres://localhost:5432/#{TEST_DATABASE}" if url.nil?
|
|
172
|
+
|
|
173
|
+
# Replace the database name in the URL with the test database name
|
|
174
|
+
uri = URI.parse(url)
|
|
175
|
+
uri.path = "/#{TEST_DATABASE}"
|
|
176
|
+
uri.to_s
|
|
177
|
+
rescue URI::InvalidURIError
|
|
178
|
+
"postgres://localhost:5432/#{TEST_DATABASE}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
# Raise if no database connection has been established yet.
|
|
184
|
+
#
|
|
185
|
+
# @raise [SquareSandboxSimulator::Error] when not connected
|
|
186
|
+
def ensure_connected!
|
|
187
|
+
return if connected?
|
|
188
|
+
|
|
189
|
+
raise SquareSandboxSimulator::Error,
|
|
190
|
+
"Database not connected. Call Database.connect!(url) first."
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Load FactoryBot factory definitions from the factories directory.
|
|
194
|
+
# Guarded against repeated calls to avoid "Factory already registered" errors.
|
|
195
|
+
#
|
|
196
|
+
# @return [void]
|
|
197
|
+
def load_factories!
|
|
198
|
+
return if @factories_loaded
|
|
199
|
+
|
|
200
|
+
require "factory_bot"
|
|
201
|
+
|
|
202
|
+
factories_path = File.expand_path("db/factories", __dir__)
|
|
203
|
+
FactoryBot.definition_file_paths = [factories_path] if Dir.exist?(factories_path)
|
|
204
|
+
FactoryBot.find_definitions
|
|
205
|
+
@factories_loaded = true
|
|
206
|
+
rescue StandardError => e
|
|
207
|
+
SquareSandboxSimulator.logger.warn("Could not load factories: #{e.message}")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Strip credentials from a database URL for safe logging.
|
|
211
|
+
#
|
|
212
|
+
# @param url [String]
|
|
213
|
+
# @return [String]
|
|
214
|
+
def sanitize_url(url)
|
|
215
|
+
uri = URI.parse(url)
|
|
216
|
+
uri.password = "***" if uri.password
|
|
217
|
+
uri.user = "***" if uri.user
|
|
218
|
+
uri.to_s
|
|
219
|
+
rescue URI::InvalidURIError
|
|
220
|
+
url.gsub(%r{://[^@]+@}, "://***:***@")
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactoryBot.define do
|
|
4
|
+
factory :api_request, class: "SquareSandboxSimulator::Models::ApiRequest" do
|
|
5
|
+
http_method { "GET" }
|
|
6
|
+
sequence(:url) { |n| "https://connect.squareupsandbox.com/v2/orders/ORDER#{n}" }
|
|
7
|
+
response_status { 200 }
|
|
8
|
+
duration_ms { 150 }
|
|
9
|
+
resource_type { "Order" }
|
|
10
|
+
request_payload { {} }
|
|
11
|
+
response_payload { {} }
|
|
12
|
+
|
|
13
|
+
# ── HTTP method traits ─────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
trait :get do
|
|
16
|
+
http_method { "GET" }
|
|
17
|
+
response_status { 200 }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
trait :post do
|
|
21
|
+
http_method { "POST" }
|
|
22
|
+
response_status { 201 }
|
|
23
|
+
request_payload { { name: "New Item", price: 999 } }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
trait :put do
|
|
27
|
+
http_method { "PUT" }
|
|
28
|
+
response_status { 200 }
|
|
29
|
+
request_payload { { name: "Updated Item" } }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
trait :delete do
|
|
33
|
+
http_method { "DELETE" }
|
|
34
|
+
response_status { 204 }
|
|
35
|
+
response_payload { {} }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# ── Status traits ──────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
trait :error do
|
|
41
|
+
http_method { "POST" }
|
|
42
|
+
response_status { 500 }
|
|
43
|
+
error_message { "Internal Server Error" }
|
|
44
|
+
response_payload { { message: "Internal Server Error" } }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
trait :not_found do
|
|
48
|
+
response_status { 404 }
|
|
49
|
+
error_message { "Not Found" }
|
|
50
|
+
response_payload { { message: "Not Found" } }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
trait :rate_limited do
|
|
54
|
+
response_status { 429 }
|
|
55
|
+
error_message { "Too Many Requests" }
|
|
56
|
+
response_payload { { message: "Rate limit exceeded. Retry after 60s." } }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
trait :unauthorized do
|
|
60
|
+
response_status { 401 }
|
|
61
|
+
error_message { "Unauthorized" }
|
|
62
|
+
response_payload { { message: "Invalid API token" } }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# ── Performance traits ─────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
trait :slow do
|
|
68
|
+
duration_ms { 2500 }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
trait :fast do
|
|
72
|
+
duration_ms { 25 }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# ── Resource traits ────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
trait :order_resource do
|
|
78
|
+
resource_type { "Order" }
|
|
79
|
+
sequence(:resource_id) { |n| "ORDER#{n}" }
|
|
80
|
+
sequence(:url) { |n| "https://connect.squareupsandbox.com/v2/orders/ORDER#{n}" }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
trait :item_resource do
|
|
84
|
+
resource_type { "Item" }
|
|
85
|
+
sequence(:resource_id) { |n| "ITEM#{n}" }
|
|
86
|
+
sequence(:url) { |n| "https://connect.squareupsandbox.com/v2/catalog/object/ITEM#{n}" }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
trait :payment_resource do
|
|
90
|
+
resource_type { "Payment" }
|
|
91
|
+
sequence(:resource_id) { |n| "PAY#{n}" }
|
|
92
|
+
sequence(:url) { |n| "https://connect.squareupsandbox.com/v2/payments/PAY#{n}" }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactoryBot.define do
|
|
4
|
+
factory :business_type, class: "SquareSandboxSimulator::Models::BusinessType" do
|
|
5
|
+
sequence(:key) { |n| "business_type_#{n}" }
|
|
6
|
+
name { "Test Business Type" }
|
|
7
|
+
industry { "food" }
|
|
8
|
+
order_profile { {} }
|
|
9
|
+
|
|
10
|
+
# ── Food (6) ──────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
trait :restaurant do
|
|
13
|
+
key { "restaurant" }
|
|
14
|
+
name { "Restaurant" }
|
|
15
|
+
industry { "food" }
|
|
16
|
+
description { "Full-service casual dining restaurant" }
|
|
17
|
+
order_profile do
|
|
18
|
+
{
|
|
19
|
+
"avg_order_value_cents" => 2500,
|
|
20
|
+
"avg_items_per_order" => 3,
|
|
21
|
+
"peak_hours" => %w[11:30 12:30 18:00 19:30],
|
|
22
|
+
"meal_periods" => %w[breakfast lunch dinner],
|
|
23
|
+
"dining_options" => %w[HERE TO_GO DELIVERY],
|
|
24
|
+
"tip_percentage_range" => [15, 25],
|
|
25
|
+
"tax_rate" => 8.875,
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
trait :cafe_bakery do
|
|
31
|
+
key { "cafe_bakery" }
|
|
32
|
+
name { "Cafe & Bakery" }
|
|
33
|
+
industry { "food" }
|
|
34
|
+
description { "Coffee shop with fresh-baked pastries and light fare" }
|
|
35
|
+
order_profile do
|
|
36
|
+
{
|
|
37
|
+
"avg_order_value_cents" => 1200,
|
|
38
|
+
"avg_items_per_order" => 2,
|
|
39
|
+
"peak_hours" => %w[07:00 08:30 12:00],
|
|
40
|
+
"meal_periods" => %w[breakfast lunch],
|
|
41
|
+
"dining_options" => %w[HERE TO_GO],
|
|
42
|
+
"tip_percentage_range" => [10, 20],
|
|
43
|
+
"tax_rate" => 8.875,
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
trait :bar_nightclub do
|
|
49
|
+
key { "bar_nightclub" }
|
|
50
|
+
name { "Bar & Nightclub" }
|
|
51
|
+
industry { "food" }
|
|
52
|
+
description { "Full bar with craft cocktails, draft beer, and late-night bites" }
|
|
53
|
+
order_profile do
|
|
54
|
+
{
|
|
55
|
+
"avg_order_value_cents" => 3500,
|
|
56
|
+
"avg_items_per_order" => 4,
|
|
57
|
+
"peak_hours" => %w[17:00 21:00 23:00],
|
|
58
|
+
"meal_periods" => %w[dinner late_night],
|
|
59
|
+
"dining_options" => %w[HERE],
|
|
60
|
+
"tip_percentage_range" => [18, 25],
|
|
61
|
+
"tax_rate" => 8.875,
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
trait :food_truck do
|
|
67
|
+
key { "food_truck" }
|
|
68
|
+
name { "Food Truck" }
|
|
69
|
+
industry { "food" }
|
|
70
|
+
description { "Mobile street food — tacos, burritos, and Mexican fare" }
|
|
71
|
+
order_profile do
|
|
72
|
+
{
|
|
73
|
+
"avg_order_value_cents" => 1400,
|
|
74
|
+
"avg_items_per_order" => 3,
|
|
75
|
+
"peak_hours" => %w[11:30 12:30 18:00],
|
|
76
|
+
"meal_periods" => %w[lunch dinner],
|
|
77
|
+
"dining_options" => %w[TO_GO],
|
|
78
|
+
"tip_percentage_range" => [10, 20],
|
|
79
|
+
"tax_rate" => 8.875,
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
trait :fine_dining do
|
|
85
|
+
key { "fine_dining" }
|
|
86
|
+
name { "Fine Dining" }
|
|
87
|
+
industry { "food" }
|
|
88
|
+
description { "Upscale prix-fixe and a la carte dining experience" }
|
|
89
|
+
order_profile do
|
|
90
|
+
{
|
|
91
|
+
"avg_order_value_cents" => 12_000,
|
|
92
|
+
"avg_items_per_order" => 4,
|
|
93
|
+
"peak_hours" => %w[18:00 19:30 20:30],
|
|
94
|
+
"meal_periods" => %w[dinner],
|
|
95
|
+
"dining_options" => %w[HERE],
|
|
96
|
+
"tip_percentage_range" => [20, 30],
|
|
97
|
+
"tax_rate" => 8.875,
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
trait :pizzeria do
|
|
103
|
+
key { "pizzeria" }
|
|
104
|
+
name { "Pizzeria" }
|
|
105
|
+
industry { "food" }
|
|
106
|
+
description { "Pizza shop with classic and specialty pies, calzones, and sides" }
|
|
107
|
+
order_profile do
|
|
108
|
+
{
|
|
109
|
+
"avg_order_value_cents" => 2200,
|
|
110
|
+
"avg_items_per_order" => 3,
|
|
111
|
+
"peak_hours" => %w[12:00 18:00 20:00],
|
|
112
|
+
"meal_periods" => %w[lunch dinner],
|
|
113
|
+
"dining_options" => %w[HERE TO_GO DELIVERY],
|
|
114
|
+
"tip_percentage_range" => [12, 20],
|
|
115
|
+
"tax_rate" => 8.875,
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# ── Retail (2) ────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
trait :retail_clothing do
|
|
123
|
+
key { "retail_clothing" }
|
|
124
|
+
name { "Clothing Store" }
|
|
125
|
+
industry { "retail" }
|
|
126
|
+
description { "Casual wear and accessories with size/color variants" }
|
|
127
|
+
order_profile do
|
|
128
|
+
{
|
|
129
|
+
"avg_order_value_cents" => 7500,
|
|
130
|
+
"avg_items_per_order" => 2,
|
|
131
|
+
"peak_hours" => %w[11:00 14:00 17:00],
|
|
132
|
+
"meal_periods" => [],
|
|
133
|
+
"dining_options" => [],
|
|
134
|
+
"tip_percentage_range" => [0, 0],
|
|
135
|
+
"tax_rate" => 8.875,
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
trait :retail_general do
|
|
141
|
+
key { "retail_general" }
|
|
142
|
+
name { "General Store" }
|
|
143
|
+
industry { "retail" }
|
|
144
|
+
description { "Everyday essentials — electronics, home goods, personal care" }
|
|
145
|
+
order_profile do
|
|
146
|
+
{
|
|
147
|
+
"avg_order_value_cents" => 3500,
|
|
148
|
+
"avg_items_per_order" => 3,
|
|
149
|
+
"peak_hours" => %w[10:00 13:00 17:00],
|
|
150
|
+
"meal_periods" => [],
|
|
151
|
+
"dining_options" => [],
|
|
152
|
+
"tip_percentage_range" => [0, 0],
|
|
153
|
+
"tax_rate" => 8.875,
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# ── Services (1) ──────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
trait :salon_spa do
|
|
161
|
+
key { "salon_spa" }
|
|
162
|
+
name { "Salon & Spa" }
|
|
163
|
+
industry { "service" }
|
|
164
|
+
description { "Full-service hair salon, spa treatments, and nail services" }
|
|
165
|
+
order_profile do
|
|
166
|
+
{
|
|
167
|
+
"avg_order_value_cents" => 8500,
|
|
168
|
+
"avg_items_per_order" => 2,
|
|
169
|
+
"peak_hours" => %w[10:00 13:00 16:00],
|
|
170
|
+
"meal_periods" => [],
|
|
171
|
+
"dining_options" => [],
|
|
172
|
+
"tip_percentage_range" => [15, 25],
|
|
173
|
+
"tax_rate" => 0.0,
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|