Class: CloverSandboxSimulator::Services::Clover::DiscountService
- Inherits:
-
BaseService
- Object
- BaseService
- CloverSandboxSimulator::Services::Clover::DiscountService
- Defined in:
- lib/clover_sandbox_simulator/services/clover/discount_service.rb
Overview
Manages Clover discounts with enhanced functionality for line-items, promo codes, combos, time-based, and loyalty discounts
Constant Summary collapse
- TIME_PERIODS =
Time-based discount periods
{ happy_hour: { start_hour: 15, end_hour: 18, name: "Happy Hour" }, lunch: { start_hour: 11, end_hour: 14, name: "Lunch" }, early_bird: { start_hour: 0, end_hour: 18, name: "Early Bird" } }.freeze
- LOYALTY_TIERS =
Loyalty tier thresholds
{ platinum: { min_visits: 50, percentage: 20 }, gold: { min_visits: 25, percentage: 15 }, silver: { min_visits: 10, percentage: 10 }, bronze: { min_visits: 5, percentage: 5 } }.freeze
- CACHE_TTL_SECONDS =
Cache configuration
300
Instance Attribute Summary
Attributes inherited from BaseService
Instance Method Summary collapse
-
#apply_category_line_item_discounts(order_id, line_items:, eligible_categories:, discount_config:) ⇒ Object
Apply discounts to multiple line items based on category eligibility.
-
#apply_combo_discount(order_id, combo:, line_items:) ⇒ Hash?
Apply a combo discount to an order.
-
#apply_line_item_discount(order_id, line_item_id:, discount_id: nil, name: nil, percentage: nil, amount: nil, item_price: nil) ⇒ Object
Apply discount to a specific line item Uses Clover’s line item discount API.
-
#apply_loyalty_discount(order_id, customer:) ⇒ Object
Apply loyalty discount to an order.
-
#apply_promo_code(order_id, code:, order_total: 0, line_items: nil, customer: nil, current_time: Time.now) ⇒ Hash?
Apply a validated promo code to an order.
-
#cache_expired? ⇒ Boolean
Check if cache is expired.
-
#clear_cache ⇒ Object
Clear all cached data.
-
#create_fixed_discount(name:, amount:) ⇒ Object
Create a fixed amount discount.
-
#create_percentage_discount(name:, percentage:) ⇒ Object
Create a percentage discount.
-
#current_meal_period(current_time = Time.now) ⇒ Object
Determine current meal period.
-
#delete_discount(discount_id) ⇒ Object
Delete a discount.
-
#delete_line_item_discount(order_id, line_item_id, discount_id) ⇒ Object
Delete a line item discount.
-
#detect_combos(line_items, current_time: Time.now) ⇒ Array<Hash>
Detect applicable combos for order items.
-
#first_order_discount?(customer) ⇒ Boolean
Check if customer qualifies for first-order discount.
-
#get_combos ⇒ Object
Get all available combos.
-
#get_coupon_codes ⇒ Object
Get all available coupon codes.
-
#get_discount(discount_id) ⇒ Object
Get a specific discount.
-
#get_discounts ⇒ Object
Fetch all discounts.
-
#get_line_item_discounts(order_id, line_item_id) ⇒ Object
Get all line item discounts for an order.
-
#get_loyalty_discount(customer) ⇒ Hash?
Get applicable loyalty discount for a customer.
-
#get_time_based_discounts(current_time: Time.now) ⇒ Array<Hash>
Get applicable time-based discounts for current time.
-
#happy_hour_discounts(current_time: Time.now) ⇒ Object
Get happy hour discounts if applicable.
-
#load_discount_definitions ⇒ Object
Load discount definitions from JSON (with TTL).
-
#loyalty_tier(customer) ⇒ Hash?
Determine loyalty tier for a customer.
-
#random_discount ⇒ Object
Select a random discount (30% chance of returning nil).
-
#refresh_cache_if_needed ⇒ Object
Reload cache if TTL expired.
-
#select_best_discount(order_total:, line_items: [], customer: nil, current_time: Time.now) ⇒ Hash
Select the best applicable discount for an order Considers time-based, loyalty, combos, and thresholds.
-
#validate_promo_code(code, order_total: 0, customer: nil, line_items: nil, current_time: Time.now) ⇒ Hash
Validate a promo code.
-
#within_time_period?(time_rules, current_time = Time.now) ⇒ Boolean
Check if current time is within a specific period.
Methods inherited from BaseService
Constructor Details
This class inherits a constructor from CloverSandboxSimulator::Services::BaseService
Instance Method Details
#apply_category_line_item_discounts(order_id, line_items:, eligible_categories:, discount_config:) ⇒ Object
Apply discounts to multiple line items based on category eligibility
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 127 def apply_category_line_item_discounts(order_id, line_items:, eligible_categories:, discount_config:) logger.info "Applying category-based line item discounts to order #{order_id}" applied_discounts = [] line_items.each do |line_item| category = line_item.dig("item", "categories", "elements", 0, "name") next unless category && eligible_categories.include?(category) result = apply_line_item_discount( order_id, line_item_id: line_item["id"], name: discount_config[:name], percentage: discount_config[:percentage], amount: discount_config[:amount] ) applied_discounts << result if result end applied_discounts end |
#apply_combo_discount(order_id, combo:, line_items:) ⇒ Hash?
Apply a combo discount to an order
322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 322 def apply_combo_discount(order_id, combo:, line_items:) logger.info "Applying combo '#{combo['name']}' to order #{order_id}" discount_info = calculate_combo_discount(combo, line_items) if combo["applies_to"] == "matching_items" || combo["applies_to"] == "cheapest_items" # Apply to specific line items matching = find_combo_items(combo, line_items) apply_discount_to_items(order_id, matching, combo, discount_info) else # Apply to order total apply_order_discount(order_id, combo["name"], discount_info) end end |
#apply_line_item_discount(order_id, line_item_id:, discount_id: nil, name: nil, percentage: nil, amount: nil, item_price: nil) ⇒ Object
Apply discount to a specific line item Uses Clover’s line item discount API
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 106 def apply_line_item_discount(order_id, line_item_id:, discount_id: nil, name: nil, percentage: nil, amount: nil, item_price: nil) logger.info "Applying line item discount to order #{order_id}, line item #{line_item_id}" payload = build_discount_payload( discount_id: discount_id, name: name, percentage: percentage, amount: amount, item_price: item_price ) return nil if payload.empty? request( :post, endpoint("orders/#{order_id}/line_items/#{line_item_id}/discounts"), payload: payload ) end |
#apply_loyalty_discount(order_id, customer:) ⇒ Object
Apply loyalty discount to an order
439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 439 def apply_loyalty_discount(order_id, customer:) discount = get_loyalty_discount(customer) return nil unless discount logger.info "Applying loyalty discount '#{discount['name']}' to order #{order_id}" request(:post, endpoint("orders/#{order_id}/discounts"), payload: { "name" => discount["name"], "percentage" => discount["percentage"].to_s }) end |
#apply_promo_code(order_id, code:, order_total: 0, line_items: nil, customer: nil, current_time: Time.now) ⇒ Hash?
Apply a validated promo code to an order
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 246 def apply_promo_code(order_id, code:, order_total: 0, line_items: nil, customer: nil, current_time: Time.now) validation = validate_promo_code( code, order_total: order_total, customer: customer, line_items: line_items, current_time: current_time ) unless validation[:valid] logger.warn "Promo code validation failed: #{validation[:error]}" return nil end coupon = validation[:coupon] logger.info "Applying promo code #{code} to order #{order_id}" # Apply to specific line items if category-restricted if coupon["applicable_categories"] && line_items apply_promo_to_line_items(order_id, coupon, line_items) else apply_promo_to_order(order_id, coupon, order_total) end end |
#cache_expired? ⇒ Boolean
Check if cache is expired
45 46 47 48 49 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 45 def cache_expired? return true if @cache_loaded_at.nil? (Time.now - @cache_loaded_at) > CACHE_TTL_SECONDS end |
#clear_cache ⇒ Object
Clear all cached data
28 29 30 31 32 33 34 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 28 def clear_cache @discount_definitions = nil @coupon_codes = nil @combos = nil @cache_loaded_at = nil logger.debug "Discount service cache cleared" end |
#create_fixed_discount(name:, amount:) ⇒ Object
Create a fixed amount discount
66 67 68 69 70 71 72 73 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 66 def create_fixed_discount(name:, amount:) logger.info "Creating fixed discount: #{name} ($#{amount / 100.0})" request(:post, endpoint("discounts"), payload: { "name" => name, "amount" => -amount.abs # Clover expects negative for discounts }) end |
#create_percentage_discount(name:, percentage:) ⇒ Object
Create a percentage discount
76 77 78 79 80 81 82 83 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 76 def create_percentage_discount(name:, percentage:) logger.info "Creating percentage discount: #{name} (#{percentage}%)" request(:post, endpoint("discounts"), payload: { "name" => name, "percentage" => percentage }) end |
#current_meal_period(current_time = Time.now) ⇒ Object
Determine current meal period
371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 371 def current_meal_period(current_time = Time.now) hour = current_time.hour case hour when 7..10 then :breakfast when 11..14 then :lunch when 15..17 then :happy_hour when 17..21 then :dinner when 21..23 then :late_night else :closed end end |
#delete_discount(discount_id) ⇒ Object
Delete a discount
86 87 88 89 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 86 def delete_discount(discount_id) logger.info "Deleting discount: #{discount_id}" request(:delete, endpoint("discounts/#{discount_id}")) end |
#delete_line_item_discount(order_id, line_item_id, discount_id) ⇒ Object
Delete a line item discount
157 158 159 160 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 157 def delete_line_item_discount(order_id, line_item_id, discount_id) logger.info "Removing discount #{discount_id} from line item #{line_item_id}" request(:delete, endpoint("orders/#{order_id}/line_items/#{line_item_id}/discounts/#{discount_id}")) end |
#detect_combos(line_items, current_time: Time.now) ⇒ Array<Hash>
Detect applicable combos for order items
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 284 def detect_combos(line_items, current_time: Time.now) logger.info "Detecting combo deals for #{line_items.size} items" combos = load_combos applicable_combos = [] combos.each do |combo| next unless combo["active"] # Check time restrictions if combo["time_restricted"] next unless within_time_period?(combo["time_rules"], current_time) end # Check day restrictions if combo["day_restricted"] next unless combo["valid_days"].include?(current_time.wday) end # Check if items satisfy combo requirements if combo_requirements_met?(combo, line_items) applicable_combos << { combo: combo, matching_items: find_combo_items(combo, line_items), discount: calculate_combo_discount(combo, line_items) } end end # Sort by discount value (best deals first) applicable_combos.sort_by { |c| -c[:discount][:amount] } end |
#first_order_discount?(customer) ⇒ Boolean
Check if customer qualifies for first-order discount
452 453 454 455 456 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 452 def first_order_discount?(customer) return true unless customer customer_visit_count(customer) <= 1 end |
#get_combos ⇒ Object
Get all available combos
338 339 340 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 338 def get_combos load_combos end |
#get_coupon_codes ⇒ Object
Get all available coupon codes
272 273 274 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 272 def get_coupon_codes load_coupon_codes end |
#get_discount(discount_id) ⇒ Object
Get a specific discount
61 62 63 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 61 def get_discount(discount_id) request(:get, endpoint("discounts/#{discount_id}")) end |
#get_discounts ⇒ Object
Fetch all discounts
52 53 54 55 56 57 58 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 52 def get_discounts logger.info "Fetching discounts..." response = request(:get, endpoint("discounts")) elements = response&.dig("elements") || [] logger.info "Found #{elements.size} discounts" elements end |
#get_line_item_discounts(order_id, line_item_id) ⇒ Object
Get all line item discounts for an order
151 152 153 154 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 151 def get_line_item_discounts(order_id, line_item_id) response = request(:get, endpoint("orders/#{order_id}/line_items/#{line_item_id}/discounts")) response&.dig("elements") || [] end |
#get_loyalty_discount(customer) ⇒ Hash?
Get applicable loyalty discount for a customer
425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 425 def get_loyalty_discount(customer) tier_info = loyalty_tier(customer) return nil unless tier_info discounts = load_discount_definitions loyalty_discounts = discounts.select { |d| d["type"] == "loyalty" } # Find the matching loyalty discount loyalty_discounts.find do |d| d["min_visits"] == tier_info[:min_visits] end end |
#get_time_based_discounts(current_time: Time.now) ⇒ Array<Hash>
Get applicable time-based discounts for current time
349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 349 def get_time_based_discounts(current_time: Time.now) discounts = load_discount_definitions hour = current_time.hour discounts.select do |discount| next false unless discount["type"] == "time_based" || discount["type"] == "line_item_time_based" next false unless discount["auto_apply"] rules = discount["time_rules"] hour >= rules["start_hour"] && hour < rules["end_hour"] end end |
#happy_hour_discounts(current_time: Time.now) ⇒ Object
Get happy hour discounts if applicable
385 386 387 388 389 390 391 392 393 394 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 385 def happy_hour_discounts(current_time: Time.now) hour = current_time.hour happy_hour_config = TIME_PERIODS[:happy_hour] return [] unless hour >= happy_hour_config[:start_hour] && hour < happy_hour_config[:end_hour] discounts = load_discount_definitions discounts.select do |d| d["id"]&.include?("happy") || d["name"]&.downcase&.include?("happy") end end |
#load_discount_definitions ⇒ Object
Load discount definitions from JSON (with TTL)
531 532 533 534 535 536 537 538 539 540 541 542 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 531 def load_discount_definitions refresh_cache_if_needed @discount_definitions ||= begin @cache_loaded_at = Time.now path = File.join(data_path, "discounts.json") return [] unless File.exist?(path) data = JSON.parse(File.read(path)) data["discounts"] || [] end end |
#loyalty_tier(customer) ⇒ Hash?
Determine loyalty tier for a customer
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 403 def loyalty_tier(customer) return nil unless customer visit_count = customer_visit_count(customer) LOYALTY_TIERS.each do |tier, config| if visit_count >= config[:min_visits] return { tier: tier, percentage: config[:percentage], min_visits: config[:min_visits], visit_count: visit_count } end end nil # No tier reached end |
#random_discount ⇒ Object
Select a random discount (30% chance of returning nil)
92 93 94 95 96 97 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 92 def random_discount return nil if rand < 0.7 # 70% chance of no discount discounts = get_discounts discounts.sample end |
#refresh_cache_if_needed ⇒ Object
Reload cache if TTL expired
37 38 39 40 41 42 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 37 def refresh_cache_if_needed return unless cache_expired? clear_cache logger.debug "Discount service cache refreshed (TTL expired)" end |
#select_best_discount(order_total:, line_items: [], customer: nil, current_time: Time.now) ⇒ Hash
Select the best applicable discount for an order Considers time-based, loyalty, combos, and thresholds
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 469 def select_best_discount(order_total:, line_items: [], customer: nil, current_time: Time.now) candidates = [] # Check time-based discounts time_discounts = get_time_based_discounts(current_time: current_time) time_discounts.each do |d| candidates << { type: :time_based, discount: d, value: calculate_discount_value(d, order_total), priority: 2 } end # Check loyalty discounts if customer loyalty = get_loyalty_discount(customer) if loyalty candidates << { type: :loyalty, discount: loyalty, value: (order_total * loyalty["percentage"] / 100.0).round, priority: 3 } end end # Check combos unless line_items.empty? combos = detect_combos(line_items, current_time: current_time) combos.each do |combo_match| candidates << { type: :combo, discount: combo_match[:combo], value: combo_match[:discount][:amount], matching_items: combo_match[:matching_items], priority: 1 } end end # Check threshold discounts threshold_discounts = load_discount_definitions.select do |d| d["type"] == "threshold" && d["min_order_amount"] && order_total >= d["min_order_amount"] end threshold_discounts.each do |d| candidates << { type: :threshold, discount: d, value: d["amount"] || (order_total * d["percentage"] / 100.0).round, priority: 4 } end return nil if candidates.empty? # Sort by value (highest discount first), then by priority candidates.sort_by { |c| [-c[:value], c[:priority]] }.first end |
#validate_promo_code(code, order_total: 0, customer: nil, line_items: nil, current_time: Time.now) ⇒ Hash
Validate a promo code
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 173 def validate_promo_code(code, order_total: 0, customer: nil, line_items: nil, current_time: Time.now) logger.info "Validating promo code: #{code}" coupon = find_coupon_by_code(code) return validation_error("Invalid promo code") unless coupon return validation_error("Promo code is inactive") unless coupon["active"] # Check expiration valid_from = Time.parse(coupon["valid_from"]) valid_until = Time.parse(coupon["valid_until"]) return validation_error("Promo code has expired") if current_time > valid_until return validation_error("Promo code is not yet valid") if current_time < valid_from # Check usage limits if coupon["usage_limit"] && coupon["usage_count"] >= coupon["usage_limit"] return validation_error("Promo code has reached its usage limit") end # Check minimum order amount if coupon["min_order_amount"] && order_total < coupon["min_order_amount"] min_amount = format_currency(coupon["min_order_amount"]) return validation_error("Minimum order amount of #{min_amount} required") end # Check customer restrictions if coupon["new_customers_only"] && customer && customer_visit_count(customer) > 0 return validation_error("Promo code is for new customers only") end if coupon["vip_only"] && (!customer || !customer["is_vip"]) return validation_error("Promo code is for VIP members only") end if coupon["birthday_required"] && (!customer || !customer_has_birthday_today?(customer)) return validation_error("Promo code requires birthday verification") end # Check time restrictions if coupon["time_restricted"] unless within_time_period?(coupon["time_rules"], current_time) return validation_error("Promo code is only valid during specific hours") end end # Check day restrictions if coupon["day_restricted"] unless coupon["valid_days"].include?(current_time.wday) return validation_error("Promo code is not valid today") end end # Check category restrictions if line items provided if line_items && coupon["applicable_categories"] applicable = find_applicable_items(line_items, coupon["applicable_categories"]) if applicable.empty? return validation_error("No eligible items for this promo code") end end { valid: true, coupon: coupon, discount_preview: calculate_coupon_discount(coupon, order_total, line_items) } end |
#within_time_period?(time_rules, current_time = Time.now) ⇒ Boolean
Check if current time is within a specific period
363 364 365 366 367 368 |
# File 'lib/clover_sandbox_simulator/services/clover/discount_service.rb', line 363 def within_time_period?(time_rules, current_time = Time.now) return true unless time_rules hour = current_time.hour hour >= time_rules["start_hour"] && hour < time_rules["end_hour"] end |