Create a Custom Discount
The following example creates a "Buy Some Get Shipping" discount that provides a discount on a particular shipping method with the purchase of certain products or from certain categories.
Discount Generator
Version 2.1 introduced a discount generator, which will create the new discount boilerplate for you.
$ bin/rails g workarea:discount --help
Usage:
rails generate workarea:discount NAME [options]
Options:
[--skip-namespace], [--no-skip-namespace] # Skip namespace (affects only isolated applications)
Runtime options:
-f, [--force] # Overwrite files that already exist
-p, [--pretend], [--no-pretend] # Run but do not make any changes
-q, [--quiet], [--no-quiet] # Suppress status output
-s, [--skip], [--no-skip] # Skip files that already exist
Description:
Creates a new custom pricing discount that can be applied to order
totals, shipping totals, products, and other price paths.
For more information on how to customize this generator, visit:
http://guides.workarea.com/create-a-custom-discount.html
Example:
rails generate workarea:discount FreeShipping
creates:
app/models/workarea/pricing/discount/free_shipping.rb
app/view_models/workarea/admin/discounts/free_shipping_view_model.rb
app/views/workarea/admin/pricing_discounts/properties/_free_shipping.html.haml
app/views/workarea/admin/create_pricing_discounts/_free_shipping.html.haml
test/models/workarea/pricing/discount/free_shipping_test.rb
test/view_models/workarea/admin/discounts/free_shipping_view_model_test.rb
modifies:
config/initializers/workarea.rb
The generator creates/modifieds all the files necessary to create a new discount. Read on to learn more about those files.
Creating the Model
Create a new discount model in your app for your discount.
your_app/app/models/workarea/pricing/discount/buy_some_get_shipping.rb:
module Workarea
module Pricing
class Discount
class BuySomeGetShipping < Discount
include FlatOrPercentOff
# If needed, you could include other condition types here to get
# functionality related to those types
#
# include Conditions::OrderTotal
# include Conditions::PromoCodes
# Add fields specific to your discount type
field :shipping_method_id, type: String
field :purchase_quantity, type: Integer
field :product_ids, type: Array, default: []
list_field :product_ids
field :category_ids, type: Array, default: []
list_field :category_ids
# Add validations to ensure the data needed for the discount is present
validates :purchase_quantity, presence: true
validates :shipping_method_id, presence: true
validate :product_or_category_ids_present
# Implement model_name so that Rails can generate routes, mappers, etc.
def self.model_name
Discount.model_name
end
# Implement apply to fulfill the Workarea::Pricing::Discount contract.
# This method defines what is to be added to the order to reflect the use
# of this discount.
def apply(order)
order.shipments.each do |shipment|
next unless shipment_matches_shipping_method?(shipment)
apply_to_shipment(shipment)
end
end
# Set the level at which price changes apply (shipping, item, order, tax)
self.price_level = 'shipping'
# Qualifiers are run to determine whether an order meets the criteria needed
# to apply this discount. This includes qualifiers from included conditions
# as well as any defined within the discount itself.
add_qualifier :matching_shipping_method?
add_qualifier :product_or_category_quantity?
def matching_shipping_method?(order)
order.shipments.any? do |shipment|
shipment_matches_shipping_method?(shipment)
end
end
def product_or_category_quantity?(order)
matching_items = order.items.select do |item|
item.matches_categories?(category_ids) ||
item.matches_products?(product_ids)
end
matching_items.sum(&:quantity) >= purchase_quantity
end
private
def shipment_matches_shipping_method?(shipment)
shipment.shipping_method.present? &&
shipment.shipping_method.id.present? &&
shipment.shipping_method.id.to_s == shipping_method_id
end
def apply_to_shipment(shipment)
shipping_total = shipment.price_adjustments.adjusting('shipping').sum
value = amount_calculator.calculate(shipping_total)
return if value < 0
shipment.adjust_pricing(adjustment_data(value, 1))
end
def product_or_category_ids_present
if product_ids.blank? && category_ids.blank?
errors.add(
:base,
'You need to specify products or categories'
)
end
end
end
end
end
end
Creating the View Model
Each discount type is assumed to have a corresponding view model. The view model will be loaded dynamically in the Admin when a user chooses to create or edit a discount of this type. Use the view model to define methods that gather the data needed to render the discount options and conditions.
In this example, all that is needed is formatting a collection of shipping method options to select from when setting the discount rules.
your_app/app/view_models/workarea/admin/discounts/buy_some_get_shipping_view_model.rb:
module Workarea
module Admin
module Discounts
class BuySomeGetShippingViewModel < DiscountViewModel
include Products
include Categories
def shipping_method_options
@shipping_method_options ||= Shipping::Method.all.map do |method|
[method.name, method.id]
end
end
end
end
end
end
Creating the Discount Properties Partial
Create a partial matching the class name of your discount. This partial is loaded under the 'Discount Rules' section of the Admin edit form.
For this example, the partial includes the id of the shipping method to discount, the required purchase quantity, and the product or category ids that need to be included in the items in the cart for the discount to apply.
your_app/app/views/workarea/admin/pricing_discounts/properties/_buy_some_get_shipping.html.haml:
%p.discount__node-group
%span.discount__node Take
%span.discount__node= select_tag 'discount[amount_type]', options_for_select(@discount.amount_type_options, @discount.amount_type), title: 'Type of Amount'
created_at: 2018/07/31
%span.discount__node= text_field_tag 'discount[amount]', @discount.amount, class: 'text-box text-box--mini', title: 'Amount of Discount', placeholder: '10', required: true
created_at: 2018/07/31
%span.discount__node off
%span.discount__node= select_tag 'discount[shipping_method_id]', options_for_select(@discount.shipping_method_options, @discount.shipping_method_id), title: 'Shipping Method'
created_at: 2018/07/31
%span.discount__node when
%span.discount__node= number_field_tag 'discount[purchase_quantity]', @discount.purchase_quantity || 1, min: '1', class: 'text-box text-box--mini', title: 'Quantity to Qualify', required: true
created_at: 2018/07/31
%span.discount__node of the following
%span.discount__node= select_tag 'discount[product_ids]', options_from_collection_for_select(@discount.products, 'id', 'name', @discount.product_ids), multiple: true, data: { remote_select: { source: catalog_products_path(format: :json), options: { placeholder: 'Product A, Product B' } }.to_json }
%span.discount__node or
%span.discount__node= select_tag 'discount[category_ids]', options_from_collection_for_select(@discount.categories, 'id', 'name', @discount.category_ids), multiple: true, data: { remote_select: { source: catalog_categories_path(format: :json), options: { placeholder: 'Category A, Category B' } }.to_json }
%span.discount__node is purchased.
Adding the Discount to the Discount Types View
When an administrator chooses to create a new discount, they are next prompted to choose the discount type. You must override this view into your application and add to it your custom discount. The relevant parts of the file are shown below.
your_app/app/views/workarea/admin/pricing_discounts/select_type.html.haml:
/ ...
= form_tag new_pricing_discount_path, method: 'get', id: 'discount_form' do
/...
%tr
%td
%label
= radio_button_tag 'type', 'buy_some_get_shipping'
Buy Some Get Shipping
%td 50% off ground shipping when any of these products are purchased
.form-actions
.action-group
%p.action-group__item= link_to 'Cancel', :back, class: 'text-button text-button--muted'
%p.action-group__item= button_tag 'Continue', value: 'continue', class: 'button'
Help Us Improve this Doc
Was this helpful? Open a GitHub issue to report a problem with this doc, suggest an improvement, or otherwise provide feedback. Thanks!