Skip to content
Log in

Set up offers for POS

On this page

Most offers belong in the promotions engine, which handles discounts, free shipping, free products, fixed prices, scheduling, usage limits, and codes. See Promotions v2. Use this process for the rest: an offer the engine cannot express, or one that has to run at a register where the engine does not.

At POS, the engine applies automatic promotions only. Coupon codes are not yet applied at a register, so an offer a customer redeems by code has to be built here or taken at the web storefront.

An offer built this way is a POS View: a template that reads the cart, decides whether the offer applies, and sends a cart action. It is a record rather than code, so you can add an offer for one weekend and remove it afterwards.

Choose how the offer reaches the cart

Decide this before you write the template, because it sets both what the offer can do and which limits apply to it.

The offer Use The register enforces
Takes an amount or percentage off the whole cart cart:discount The operator’s Maximum Discount Percentage, each product’s Minimum Sell Price, and the Manual value of Exclude From Price Reductions
Adds a product, free or at an agreed price cart:add_product with unit_price Nothing to enforce: the offer adds a line rather than reducing one
Takes an amount off particular lines cart:item:update Nothing. See the warning below

cart:discount has no product, category, or line parameter, so it always applies to the whole cart and distributes the reduction across eligible lines. Reach for cart:add_product before cart:item:update where either would do, because giving a product away sidesteps the limits question entirely.

:::warning A view cannot enforce the discount limits for itself. Exclude From Price Reductions is on no POS drop, and current_staff exposes only id and name, so a template cannot read the operator’s Maximum Discount Percentage. Use cart:item:update only where you accept that the reduction overrides both the operator’s ceiling and any product the merchant has marked Manual under Exclude From Price Reductions. :::

Before you start

  • You need Salesforce admin access to create POS View and POS Action Item records.
  • The products the offer adds or discounts need a Product Code, and they must be synced to the register. See Manage POS data sync.
  • Decide the products, thresholds, and amounts the offer uses. Keep them in {% assign %} statements at the top of the template, so there is one place to change them. A POS view cannot read store variables, which is covered below.

Build the offer

  1. Go to the POS Views list and select New.
  2. Enter a POS View Name, and an Identifier you will name the view by from the action, such as offer_buy_two_get_one.
  3. In the HTML Template, read the cart from current_cart.items and work out whether the offer applies. Each item gives you quantity and product, and a product gives you product_code.
  4. In the same template, call the cart action from a <script> block through scAction. See scAction for the syntax and the POS actions reference for each action’s parameters.
  5. Save the record.
  6. Go to the POS Action Groups list and open the group the offer’s button belongs on, usually home. See POS action groups.
  7. Add a POS Action Item with Action set to modal:open:view and Params set to view_identifier=offer_buy_two_get_one.
  8. On a register, reload the POS.

The offer’s button now appears in the action group, and tapping it opens your view.

:::note Parameters in a Params field are separated by semicolons. An ampersand is not a separator, so view_identifier=x&title=y is read as one parameter whose value is x&title=y. :::

Example: buy two, get one free

This view adds a free bottle of RED-FENCE-SHIRAZ-2019 once the cart holds two, and does nothing otherwise. The product code and the threshold sit in {% assign %} statements at the top, so the offer is retuned by editing those two lines.

:::warning Do not reach for store_variables here. It is declared as a POS global but raises at the register, and a view whose template raises renders as an empty box with no error on screen. :::

```liquid

{% assign qualifying_code = ‘RED-FENCE-SHIRAZ-2019’ %} {% assign threshold = 2 %} {% assign qualifying_count = 0 %}

{% for item in current_cart.items %} {% if item.product.product_code == qualifying_code %} {% assign qualifying_count = qualifying_count | plus: item.quantity %} {% endif %} {% endfor %}

{% if qualifying_count >= threshold %}

This cart qualifies for a free {{ qualifying_code }}.

{% else %}

Add {{ threshold | minus: qualifying_count }} more to qualify.

{% endif %}

```

Three details in that template are worth carrying into your own:

  • Wrap the script in an immediately invoked function. A view re-renders, and its scripts run again each time, so a top-level const or let throws on the second run.
  • Set unit_price rather than a discount. It locks the price, so the free line is not repriced if the customer on the cart changes.
  • Set name so the reason for the line is visible on the cart, the receipt, and the order.

Keep the offer portable

Refer to products by Product Code, never by Salesforce ID, so the view still works after a move to another org. Keep the dates, thresholds, and on/off switches together at the top of the template, because a POS view cannot read store variables. A view is org-wide and cannot be scoped to one store, so branch on current_store or current_outlet where two stores in the org need different behavior. See Deploy POS configuration to production.

What this does not do

  • It does not report as a promotion. A view’s reduction is recorded as a discount on the order, not as a Promotion2 record, so it does not appear in promotion reporting or count toward a promotion’s usage limits. Build it in the engine where the campaign has to be measured.
  • It does not apply on its own. The offer runs when someone opens the view, so a staff-triggered button is the reliable shape. There is no hook that evaluates a view against every cart change.
  • It does not see unsynced data. A view reads the device’s local database, so an offer set up in Salesforce minutes ago applies only after the register syncs.

Results

Ring up a qualifying cart at a register and tap the offer’s button. The free line is added at the price the template set, it is named on the cart and the receipt, and the order records what the customer was actually charged. Where your offer uses cart:discount instead, check as well that a product marked Manual under Exclude From Price Reductions is left at full price.

Was this article helpful?

Was this article helpful?