spree_core 3.4.6 → 3.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/app/assets/javascripts/spree.js.coffee +1 -1
  3. data/app/helpers/spree/base_helper.rb +4 -0
  4. data/app/models/concerns/spree/named_type.rb +1 -1
  5. data/app/models/concerns/spree/user_methods.rb +21 -4
  6. data/app/models/concerns/spree/user_reporting.rb +2 -2
  7. data/app/models/spree/address.rb +6 -12
  8. data/app/models/spree/adjustable/adjustments_updater.rb +2 -1
  9. data/app/models/spree/country.rb +2 -1
  10. data/app/models/spree/line_item.rb +8 -2
  11. data/app/models/spree/log_entry.rb +1 -1
  12. data/app/models/spree/order.rb +8 -6
  13. data/app/models/spree/order/checkout.rb +1 -0
  14. data/app/models/spree/order_contents.rb +20 -12
  15. data/app/models/spree/order_inventory.rb +24 -12
  16. data/app/models/spree/payment/processing.rb +2 -2
  17. data/app/models/spree/preferences/preferable.rb +1 -1
  18. data/app/models/spree/product/scopes.rb +1 -1
  19. data/app/models/spree/promotion.rb +15 -1
  20. data/app/models/spree/promotion/rules/option_value.rb +13 -5
  21. data/app/models/spree/promotion/rules/product.rb +2 -1
  22. data/app/models/spree/promotion/rules/taxon.rb +3 -1
  23. data/app/models/spree/promotion_action_line_item.rb +3 -0
  24. data/app/models/spree/promotion_handler/promotion_duplicator.rb +52 -0
  25. data/app/models/spree/refund.rb +1 -1
  26. data/app/models/spree/reimbursement.rb +1 -1
  27. data/app/models/spree/reimbursement/reimbursement_type_engine.rb +7 -18
  28. data/app/models/spree/reimbursement_performer.rb +3 -7
  29. data/app/models/spree/reimbursement_type/original_payment.rb +2 -2
  30. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +3 -7
  31. data/app/models/spree/reimbursement_type/store_credit.rb +2 -10
  32. data/app/models/spree/shipment.rb +10 -4
  33. data/app/models/spree/stock/availability_validator.rb +1 -1
  34. data/app/models/spree/stock/packer.rb +1 -1
  35. data/app/models/spree/stock/splitter/backordered.rb +5 -7
  36. data/app/models/spree/stock/splitter/base.rb +1 -0
  37. data/app/models/spree/stock/splitter/shipping_category.rb +9 -16
  38. data/app/models/spree/stock/splitter/weight.rb +18 -20
  39. data/app/models/spree/stock_transfer.rb +2 -1
  40. data/app/models/spree/store_credit_category.rb +13 -0
  41. data/app/models/spree/taxon.rb +7 -0
  42. data/app/models/spree/variant.rb +1 -1
  43. data/app/validators/email_validator.rb +7 -0
  44. data/config/locales/en.yml +18 -27
  45. data/db/default/spree/states.rb +9 -27
  46. data/db/migrate/20150128032538_remove_environment_from_tracker.rb +2 -0
  47. data/db/migrate/20171004223836_remove_icon_from_taxons.rb +8 -0
  48. data/db/migrate/20180222133746_add_unique_index_on_spree_promotions_code.rb +6 -0
  49. data/lib/generators/spree/dummy_model/dummy_model_generator.rb +23 -0
  50. data/lib/generators/spree/dummy_model/templates/migration.rb.tt +10 -0
  51. data/lib/generators/spree/dummy_model/templates/model.rb.tt +6 -0
  52. data/lib/spree/core/controller_helpers/auth.rb +1 -1
  53. data/lib/spree/core/controller_helpers/common.rb +4 -0
  54. data/lib/spree/core/controller_helpers/order.rb +6 -5
  55. data/lib/spree/core/engine.rb +10 -10
  56. data/lib/spree/core/environment_extension.rb +3 -0
  57. data/lib/spree/core/importer/order.rb +1 -1
  58. data/lib/spree/core/validators/email.rb +1 -0
  59. data/lib/spree/core/version.rb +1 -1
  60. data/lib/spree/money.rb +1 -5
  61. data/lib/spree/permitted_attributes.rb +1 -1
  62. data/lib/spree/testing_support/capybara_ext.rb +16 -13
  63. data/lib/spree/testing_support/common_rake.rb +4 -1
  64. data/lib/spree/testing_support/factories/inventory_unit_factory.rb +7 -0
  65. data/lib/spree/testing_support/factories/taxon_factory.rb +1 -1
  66. data/spree_core.gemspec +1 -1
  67. data/vendor/assets/javascripts/jsuri.js +458 -2
  68. metadata +13 -7
  69. data/app/models/spree/tracker.rb +0 -25
  70. data/lib/spree/testing_support/factories/tracker_factory.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 3186ecc9fcd800b68cd881887ef41fd1512a7b96215d601b013200f1b4c0e3c0
4
- data.tar.gz: ba34e2b2b2ae15e75e45aea3b8d397459f91f2019895de3a8a51d20e059c5c7e
2
+ SHA1:
3
+ metadata.gz: 2114cd601e774d50896839767298308294adc60e
4
+ data.tar.gz: be32dd8365af3332edbf8b878493bb6787b66f8f
5
5
  SHA512:
6
- metadata.gz: fbabac3389a37851c3ca6b4c6814267f6d7117d8212b9035da4b9f5b9202d5b36b3b186b337a473cf80019115baba71236c1398d7599f865d43befd16a8d009f
7
- data.tar.gz: c3bbc7170c4173d76201321e8e5003e502e6b657b817cd21a44e0d324f775a102ef881ecfa6ce906235a7f74717a23eb2cd735b3ead142a817e198a2363a6680
6
+ metadata.gz: e95baff0a25bbff9bd76cb6952303234735a063f32f6c1df521fe75f7eb8cea28b5fff67bc92da889051b70530798051afaa08c4bfdbea025fa659b2024d0149
7
+ data.tar.gz: 44cdda9fbf073099a0b588170ccaa2f49e35065d58c582826657d74d4e1b4b65a7fc3df4b2d6e650da5669bc9790e57b836766b91e73ee3b5f3c10daf6b5d1db
@@ -4,7 +4,7 @@ class window.Spree
4
4
  jQuery(document).ready(callback)
5
5
 
6
6
  # fire ready callbacks also on turbolinks page change event
7
- jQuery(document).on 'page:load', ->
7
+ jQuery(document).on 'page:load turbolinks:load', ->
8
8
  callback(jQuery)
9
9
 
10
10
  @mountedAt: ->
@@ -84,6 +84,10 @@ module Spree
84
84
  v.options_text
85
85
  end
86
86
 
87
+ def frontend_available?
88
+ Spree::Core::Engine.frontend_available?
89
+ end
90
+
87
91
  private
88
92
 
89
93
  def create_product_image_tag(image, product, options, style)
@@ -4,7 +4,7 @@ module Spree
4
4
 
5
5
  included do
6
6
  scope :active, -> { where(active: true) }
7
- default_scope { order("LOWER(#{table_name}.name)") }
7
+ default_scope { order(Arel.sql("LOWER(#{table_name}.name)")) }
8
8
 
9
9
  validates :name, presence: true, uniqueness: { case_sensitive: false }
10
10
  end
@@ -9,7 +9,11 @@ module Spree
9
9
  included do
10
10
  # we need to have this callback before any dependent: :destroy associations
11
11
  # https://github.com/rails/rails/issues/3458
12
+ before_validation :clone_billing_address, if: :use_billing?
12
13
  before_destroy :check_completed_orders
14
+ after_destroy :nullify_approver_id_in_approved_orders
15
+
16
+ attr_accessor :use_billing
13
17
 
14
18
  has_many :role_users, class_name: 'Spree::RoleUser', foreign_key: :user_id, dependent: :destroy
15
19
  has_many :spree_roles, through: :role_users, class_name: 'Spree::Role', source: :role
@@ -39,10 +43,6 @@ module Spree
39
43
  first
40
44
  end
41
45
 
42
- def analytics_id
43
- id
44
- end
45
-
46
46
  def total_available_store_credit
47
47
  store_credits.reload.to_a.sum(&:amount_remaining)
48
48
  end
@@ -52,5 +52,22 @@ module Spree
52
52
  def check_completed_orders
53
53
  raise Spree::Core::DestroyWithOrdersError if orders.complete.present?
54
54
  end
55
+
56
+ def nullify_approver_id_in_approved_orders
57
+ Spree::Order.where(approver_id: id).update_all(approver_id: nil)
58
+ end
59
+
60
+ def clone_billing_address
61
+ if bill_address && ship_address.nil?
62
+ self.ship_address = bill_address.clone
63
+ else
64
+ ship_address.attributes = bill_address.attributes.except('id', 'updated_at', 'created_at')
65
+ end
66
+ true
67
+ end
68
+
69
+ def use_billing?
70
+ use_billing.in?([true, 'true', '1'])
71
+ end
55
72
  end
56
73
  end
@@ -8,11 +8,11 @@ module Spree
8
8
  end
9
9
 
10
10
  def order_count
11
- BigDecimal(orders.complete.size)
11
+ orders.complete.size
12
12
  end
13
13
 
14
14
  def average_order_value
15
- if order_count.to_i > 0
15
+ if order_count > 0
16
16
  lifetime_value / order_count
17
17
  else
18
18
  BigDecimal('0.00')
@@ -10,6 +10,10 @@ module Spree
10
10
  'TO', 'TV', 'UG', 'AE', 'VU', 'YE', 'ZW'
11
11
  ].freeze
12
12
 
13
+ # we're not freezing this on purpose so developers can extend and manage
14
+ # those attributes depending of the logic of their applications
15
+ EXCLUDED_KEYS_FOR_COMPARISION = %w(id updated_at created_at)
16
+
13
17
  belongs_to :country, class_name: 'Spree::Country'
14
18
  belongs_to :state, class_name: 'Spree::State', optional: true
15
19
 
@@ -31,12 +35,7 @@ module Spree
31
35
  self.whitelisted_ransackable_attributes = %w[firstname lastname company]
32
36
 
33
37
  def self.build_default
34
- country = begin
35
- Spree::Country.find(Spree::Config[:default_country_id])
36
- rescue
37
- Spree::Country.first
38
- end
39
- new(country: country)
38
+ new(country: Spree::Country.default)
40
39
  end
41
40
 
42
41
  def self.default(user = nil, kind = 'bill')
@@ -47,11 +46,6 @@ module Spree
47
46
  end
48
47
  end
49
48
 
50
- # Can modify an address if it's not been used in an order (but checkouts controller has finer control)
51
- # def editable?
52
- # new_record? || (shipments.empty? && checkouts.empty?)
53
- # end
54
-
55
49
  def full_name
56
50
  "#{firstname} #{lastname}".strip
57
51
  end
@@ -62,7 +56,7 @@ module Spree
62
56
 
63
57
  def same_as?(other)
64
58
  return false if other.nil?
65
- attributes.except('id', 'updated_at', 'created_at') == other.attributes.except('id', 'updated_at', 'created_at')
59
+ attributes.except(*EXCLUDED_KEYS_FOR_COMPARISION) == other.attributes.except(*EXCLUDED_KEYS_FOR_COMPARISION)
66
60
  end
67
61
 
68
62
  alias same_as same_as?
@@ -7,10 +7,11 @@ module Spree
7
7
 
8
8
  def initialize(adjustable)
9
9
  @adjustable = adjustable
10
- adjustable.reload if shipment? && adjustable.persisted?
10
+ adjustable.reload if shipment? && adjustable && adjustable.persisted?
11
11
  end
12
12
 
13
13
  def update
14
+ return unless @adjustable
14
15
  return unless @adjustable.persisted?
15
16
 
16
17
  totals = {
@@ -18,7 +18,8 @@ module Spree
18
18
 
19
19
  def self.default
20
20
  country_id = Spree::Config[:default_country_id]
21
- country_id.present? ? find(country_id) : find_by!(iso: 'US')
21
+ default = find_by(id: country_id) if country_id.present?
22
+ default || find_by(iso: 'US') || first
22
23
  end
23
24
 
24
25
  def <=>(other)
@@ -23,7 +23,7 @@ module Spree
23
23
  validates_with Stock::AvailabilityValidator
24
24
  validate :ensure_proper_currency, if: -> { order.present? }
25
25
 
26
- before_destroy :verify_order_inventory, if: -> { order.has_checkout_step?('delivery') }
26
+ before_destroy :verify_order_inventory_before_destroy, if: -> { order.has_checkout_step?('delivery') }
27
27
 
28
28
  before_destroy :destroy_inventory_units
29
29
 
@@ -116,7 +116,9 @@ module Spree
116
116
  def update_price_from_modifier(currency, opts)
117
117
  if currency
118
118
  self.currency = currency
119
- self.price = variant.price_in(currency).amount +
119
+ # variant.price_in(currency).amount can be nil if
120
+ # there's no price for this currency
121
+ self.price = (variant.price_in(currency).amount || 0) +
120
122
  variant.price_modifier_amount_in(currency, opts)
121
123
  else
122
124
  self.price = variant.price +
@@ -131,6 +133,10 @@ module Spree
131
133
  end
132
134
 
133
135
  def verify_order_inventory
136
+ Spree::OrderInventory.new(order, self).verify(target_shipment, is_updated: true)
137
+ end
138
+
139
+ def verify_order_inventory_before_destroy
134
140
  Spree::OrderInventory.new(order, self).verify(target_shipment)
135
141
  end
136
142
 
@@ -4,7 +4,7 @@ module Spree
4
4
 
5
5
  # Fix for #1767
6
6
  # If a payment fails, we want to make sure we keep the record of it failing
7
- after_rollback :save_anyway
7
+ after_rollback :save_anyway, if: proc { !Rails.env.test? }
8
8
 
9
9
  def save_anyway
10
10
  Spree::LogEntry.create!(source: source, details: details)
@@ -1,4 +1,3 @@
1
- require 'spree/core/validators/email'
2
1
  require 'spree/order/checkout'
3
2
 
4
3
  module Spree
@@ -51,7 +50,7 @@ module Spree
51
50
  remove_transition from: :delivery, to: :confirm
52
51
  end
53
52
 
54
- self.whitelisted_ransackable_associations = %w[shipments user promotions bill_address ship_address line_items]
53
+ self.whitelisted_ransackable_associations = %w[shipments user promotions bill_address ship_address line_items store]
55
54
  self.whitelisted_ransackable_attributes = %w[completed_at email number state payment_state shipment_state total considered_risky]
56
55
 
57
56
  attr_reader :coupon_code
@@ -155,7 +154,7 @@ module Spree
155
154
  scope :incomplete, -> { where(completed_at: nil) }
156
155
 
157
156
  # shows completed orders first, by their completed_at date, then uncompleted orders by their created_at
158
- scope :reverse_chronological, -> { order('spree_orders.completed_at IS NULL', completed_at: :desc, created_at: :desc) }
157
+ scope :reverse_chronological, -> { order(Arel.sql('spree_orders.completed_at IS NULL'), completed_at: :desc, created_at: :desc) }
159
158
 
160
159
  # Use this method in other gems that wish to register their own custom logic
161
160
  # that should be called after Order#update
@@ -314,6 +313,10 @@ module Spree
314
313
  Spree::TaxRate.adjust(self, shipments) if shipments.any?
315
314
  end
316
315
 
316
+ def create_shipment_tax_charge!
317
+ Spree::TaxRate.adjust(self, shipments) if shipments.any?
318
+ end
319
+
317
320
  def update_line_item_prices!
318
321
  transaction do
319
322
  line_items.each(&:update_price)
@@ -500,9 +503,8 @@ module Spree
500
503
 
501
504
  def apply_free_shipping_promotions
502
505
  Spree::PromotionHandler::FreeShipping.new(self).activate
503
- shipments.each { |shipment| Adjustable::AdjustmentsUpdater.update(shipment) }
504
- updater.update_shipment_total
505
- persist_totals
506
+ shipments.each { |shipment| Spree::Adjustable::AdjustmentsUpdater.update(shipment) }
507
+ update_with_updater!
506
508
  end
507
509
 
508
510
  # Clean shipments and make order back to address state
@@ -100,6 +100,7 @@ module Spree
100
100
  before_transition to: :delivery, do: :create_proposed_shipments
101
101
  before_transition to: :delivery, do: :ensure_available_shipping_rates
102
102
  before_transition to: :delivery, do: :set_shipments_cost
103
+ before_transition to: :delivery, do: :create_shipment_tax_charge!
103
104
  before_transition from: :delivery, do: :apply_free_shipping_promotions
104
105
  end
105
106
 
@@ -8,19 +8,25 @@ module Spree
8
8
 
9
9
  def add(variant, quantity = 1, options = {})
10
10
  timestamp = Time.current
11
- line_item = add_to_line_item(variant, quantity, options)
12
- options[:line_item_created] = true if timestamp <= line_item.created_at
13
- after_add_or_remove(line_item, options)
11
+ ActiveRecord::Base.transaction do
12
+ line_item = add_to_line_item(variant, quantity, options)
13
+ options[:line_item_created] = true if timestamp <= line_item.created_at
14
+ after_add_or_remove(line_item, options)
15
+ end
14
16
  end
15
17
 
16
18
  def remove(variant, quantity = 1, options = {})
17
- line_item = remove_from_line_item(variant, quantity, options)
18
- after_add_or_remove(line_item, options)
19
+ ActiveRecord::Base.transaction do
20
+ line_item = remove_from_line_item(variant, quantity, options)
21
+ after_add_or_remove(line_item, options)
22
+ end
19
23
  end
20
24
 
21
25
  def remove_line_item(line_item, options = {})
22
- line_item.destroy!
23
- after_add_or_remove(line_item, options)
26
+ ActiveRecord::Base.transaction do
27
+ line_item.destroy!
28
+ after_add_or_remove(line_item, options)
29
+ end
24
30
  end
25
31
 
26
32
  def update_cart(params)
@@ -29,11 +35,13 @@ module Spree
29
35
  # Update totals, then check if the order is eligible for any cart promotions.
30
36
  # If we do not update first, then the item total will be wrong and ItemTotal
31
37
  # promotion rules would not be triggered.
32
- persist_totals
33
- PromotionHandler::Cart.new(order).activate
34
- order.ensure_updated_shipments
35
- order.payments.store_credits.checkout.destroy_all
36
- persist_totals
38
+ ActiveRecord::Base.transaction do
39
+ persist_totals
40
+ PromotionHandler::Cart.new(order).activate
41
+ order.ensure_updated_shipments
42
+ order.payments.store_credits.checkout.destroy_all
43
+ persist_totals
44
+ end
37
45
  true
38
46
  else
39
47
  false
@@ -17,17 +17,19 @@ module Spree
17
17
  # In case shipment is passed the stock location should only unstock or
18
18
  # restock items if the order is completed. That is so because stock items
19
19
  # are always unstocked when the order is completed through +shipment.finalize+
20
- def verify(shipment = nil)
21
- if order.completed? || shipment.present?
22
- units_count = inventory_units.reload.sum(&:quantity)
23
- if units_count < line_item.quantity
24
- quantity = line_item.quantity - units_count
25
-
26
- shipment = determine_target_shipment unless shipment
27
- add_to_shipment(shipment, quantity)
28
- elsif (units_count > line_item.quantity) || (units_count == line_item.quantity && !line_item.changed?)
29
- remove(units_count, shipment)
30
- end
20
+ def verify(shipment = nil, is_updated: false)
21
+ return unless order.completed? || shipment.present?
22
+
23
+ units_count = inventory_units.reload.sum(&:quantity)
24
+ line_item_changed = is_updated ? !line_item.saved_changes? : !line_item.changed?
25
+
26
+ if units_count < line_item.quantity
27
+ quantity = line_item.quantity - units_count
28
+
29
+ shipment = determine_target_shipment unless shipment
30
+ add_to_shipment(shipment, quantity)
31
+ elsif (units_count > line_item.quantity) || (units_count == line_item.quantity && line_item_changed)
32
+ remove(units_count, shipment)
31
33
  end
32
34
  end
33
35
 
@@ -92,6 +94,7 @@ module Spree
92
94
  shipment_units = shipment.inventory_units_for_item(line_item, variant).reject(&:shipped?).sort_by(&:state)
93
95
 
94
96
  removed_quantity = 0
97
+ removed_backordered = 0
95
98
 
96
99
  shipment_units.each do |inventory_unit|
97
100
  inventory_unit.quantity.times do
@@ -101,6 +104,7 @@ module Spree
101
104
  else
102
105
  inventory_unit.destroy
103
106
  end
107
+ removed_backordered += 1 if inventory_unit.backordered?
104
108
  removed_quantity += 1
105
109
  end
106
110
  inventory_unit.save! if inventory_unit.persisted?
@@ -110,7 +114,15 @@ module Spree
110
114
 
111
115
  # removing this from shipment, and adding to stock_location
112
116
  if order.completed?
113
- shipment.stock_location.restock variant, removed_quantity, shipment
117
+ current_on_hand = shipment.stock_location.count_on_hand(variant)
118
+
119
+ if current_on_hand.negative? && current_on_hand.abs < removed_backordered
120
+ shipment.stock_location.restock_backordered variant, current_on_hand.abs, shipment
121
+ else
122
+ shipment.stock_location.restock_backordered variant, removed_backordered, shipment
123
+ end
124
+
125
+ shipment.stock_location.restock variant, removed_quantity - removed_backordered, shipment
114
126
  end
115
127
 
116
128
  removed_quantity
@@ -29,7 +29,7 @@ module Spree
29
29
  # a new pending payment record for the remaining amount to capture later.
30
30
  def capture!(amount = nil)
31
31
  return true if completed?
32
- amount ||= money.amount_in_cents
32
+ amount ||= money.money.cents
33
33
  started_processing!
34
34
  protect_from_connection_error do
35
35
  # Standard ActiveMerchant capture usage
@@ -114,7 +114,7 @@ module Spree
114
114
 
115
115
  def gateway_action(source, action, success_state)
116
116
  protect_from_connection_error do
117
- response = payment_method.send(action, money.amount_in_cents,
117
+ response = payment_method.send(action, money.money.cents,
118
118
  source,
119
119
  gateway_options)
120
120
  handle_response(response, success_state, :failure)
@@ -24,7 +24,7 @@
24
24
  #
25
25
  # # Typecasting is performed on assignment
26
26
  # s.preferred_temperature = '24'
27
- # s.preferred_color # => 24
27
+ # s.preferred_temperature # => 24
28
28
  #
29
29
  # # Modifications have been made to the .preferences hash
30
30
  # s.preferences #=> {color: 'blue', temperature: 24}
@@ -23,7 +23,7 @@ module Spree
23
23
  # We should not define price scopes here, as they require something slightly different
24
24
  next if name.to_s.include?('master_price')
25
25
  parts = name.to_s.match(/(.*)_by_(.*)/)
26
- scope(name.to_s, -> { order("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? 'ASC' : 'DESC'}") })
26
+ scope(name.to_s, -> { order(Arel.sql("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? 'ASC' : 'DESC'}")) })
27
27
  end
28
28
  end
29
29
 
@@ -3,7 +3,7 @@ module Spree
3
3
  MATCH_POLICIES = %w(all any)
4
4
  UNACTIVATABLE_ORDER_STATES = ['complete', 'awaiting_return', 'returned']
5
5
 
6
- attr_reader :eligibility_errors
6
+ attr_reader :eligibility_errors, :generate_code
7
7
 
8
8
  belongs_to :promotion_category, optional: true
9
9
 
@@ -54,6 +54,12 @@ module Spree
54
54
  order && !UNACTIVATABLE_ORDER_STATES.include?(order.state)
55
55
  end
56
56
 
57
+ def generate_code=(generating_code)
58
+ if ActiveModel::Type::Boolean.new.cast(generating_code)
59
+ self.code = random_code
60
+ end
61
+ end
62
+
57
63
  def expired?
58
64
  !!(starts_at && Time.current < starts_at || expires_at && Time.current > expires_at)
59
65
  end
@@ -227,5 +233,13 @@ module Spree
227
233
  def expires_at_must_be_later_than_starts_at
228
234
  errors.add(:expires_at, :invalid_date_range) if expires_at < starts_at
229
235
  end
236
+
237
+ def random_code
238
+ coupon_code = loop do
239
+ random_token = SecureRandom.hex(4)
240
+ break random_token unless self.class.exists?(code: random_token)
241
+ end
242
+ coupon_code
243
+ end
230
244
  end
231
245
  end