Introduction
If you use Shopify POS Pro, you can customise two printed receipts: your sales receipt and a gift receipt. POS Cafe provides prebuilt receipt code that turns one of these into a clean Order Ticket for your barista/kitchen.
Our Order Ticket code will:
- Print only products created via POS Cafe (or optionally print all products)
- Group items by Prep Area so you can cut/torn sections for different stations
Setting up Order Tickets
Step 1
In your Shopify admin, go to Sales channels » Point of Sale » Settings.
Step 2
Choose Receipts.
Step 3
Select Customize your receipts.
Step 4
Click Edit code.
Step 5
In the code editor, update the four files highlighted below. Open each file, select all, and paste in the matching snippet from the Code Snippets section.
Step 6
Click Save. Use Preview options to test different order examples, then print a test order from your POS to verify layout at your printer width.
Code Snippets
Which option should I choose?
- Sales receipt as Order Ticket — the default sales receipt in Shopify will print the formatted order ticket (this is the receipt that can be set to autoprint after every order); the gift receipt prints your normal sales receipt;
- Gift receipt as Order Ticket — Your sales receipt remains the normal customer receipt; your cashier will need to tap the gift receipt button to print the formatted order ticket.
Select the accordion that matches your choice, then copy each snippet into the corresponding file.
Use this option if you would like your sales receipt to print as an order ticket, and your gift receipt to print as your sales receipt.
{% comment %}
POS Cafe - Receipts 5.8
Receipts/sales-receipt.liquid
Order ticket header section.
It is your responsibility to ensure that the printed receipts comply with all the applicable laws.
{% endcomment %}
{% # Display order note %}
{% assign display_order_note = true %}
{% # End of the settings definition %}
<!doctype html>
<html>
<head>
<meta charset='UTF-8'>
<meta
name='viewport'
content='user-scalable=no'
>
<style>
:root {
--font-smallest: 0.6875rem;
--font-small: 0.875rem;
--font-medium: 1rem;
--font-large: 1.5rem;
--font-largest: 3rem;
--font-family: system-ui;
}
* {
box-sizing: border-box;
}
body {
margin: 2rem 1rem 2.5rem 1rem;
font-family: var(--font-family);
font-weight: 400;
font-size: var(--font-large);
background: white;
}
p {
margin: 0;
}
.receipt-box {
border-color: black;
border-radius: 0.7rem;
border: 0.3rem solid;
margin: 2.5rem 0.3125rem;
padding: 1.75rem 1.25rem;
text-align: center;
font-weight: bold;
font-size: var(--font-large);
}
.receipt-box .channel-label {
font-weight: bold;
font-size: var(--font-large);
}
.note {
padding-top: 1.35rem;
border-top: 0.25rem dotted black;
font-size: var(--font-large);
}
.note-title {
font-weight: bold;
padding-bottom: 0.3125rem;
font-size: var(--font-large);
}
</style>
</head>
<body>
{% render 'line-items' %}
{% if display_order_note %}
{% if order.note != nil and order.note != blank %}
<section class='note'>
<p class='note-title'>{{ 'receipt.order_note' | t }}</p>
<p>{{ order.note | escape }}</p>
</section>
{% endif %}
{% endif %}
</body>
</html>
{% comment %}
POS Cafe - Receipts 5.8
Sections/line-items.liquid
Order ticket line items section.
It is your responsibility to ensure that the printed receipts comply with all the applicable laws.
{% endcomment %}
<style>
.variant-name,
.line-item-quantity,
.line-item-group { font-size: var(--font-large); }
.line-items-header {
width: 100%;
border-bottom: 0.3rem solid black;
display: flex;
justify-content: space-between;
padding: 0 0.3125rem;
}
.line-item-row { padding: 1.35rem 0; display: flex; justify-content: space-between; }
.line-item-row-bottom-border { border-bottom: 0.25rem dotted black; }
.line-item-title, .line-item-quantity { padding: 0 0.3125rem; }
.line-item-title .product-name,
.line-item-quantity,
.line-items-header { font-size: 3rem; }
.line-item-title .prep-area,
.line-item-quantity,
.line-items-header {
font-size: 3rem;
font-weight:bold;
text-align:center;
margin-bottom:0.5rem;
}
.dine-option {
font-size: 2rem;
font-weight: 700;
text-transform: uppercase;
margin-top: 0.25rem;
}
li { font-size: 2rem; }
.order-type-header {
font-size: 2rem;
font-weight: bold;
padding: 0.75rem 0.3125rem;
margin-top: 1rem;
border-bottom: 0.15rem dashed black;
text-transform: uppercase;
}
hr.cut-here { border-top: 10px dashed black; }
.box-customer-address { font-weight: normal; font-size: var(--font-large); margin-top: 0.75rem; }
.box-customer-address-title { font-weight: bold; }
</style>
<div>
<section>
{% # Set variables %}
{% assign poscafe = "POS Cafe" %}
{% assign poscafe_norm = poscafe | downcase %}
{% assign unique_prep_areas = '' %}
{% assign has_order_name = false %}
{% assign order_name = '' %}
{% assign has_channel = false %}
{% assign channel = "Online" %}
{% assign show_channel = false %} {% # toggle: show/hide the channel line in header %}
{% comment %} Toggle: include non–POS Cafe items in the “no prep area” section {% endcomment %}
{% assign show_non_poscafe_items = false %}
{% comment %} Toggle: show the customer address inside the boxed header (below the name).
Falls back order.shipping_address -> billing_address -> customer default; hides when none. {% endcomment %}
{% assign display_customer_address = false %}
{% comment %} Heading shown above the address in the box (blank = no heading). {% endcomment %}
{% assign customer_address_title = '' %}
{% comment %} Keys to hide from attributes (normalized: downcase + spaces->underscores) {% endcomment %}
{% assign hidden_keys = 'item_type,modifier_variants,_modifiervariants,prep_area,channel,service_type,order_type,_order_type' | split: ',' %}
{% comment %} Valid order type values (display names used directly as values) {% endcomment %}
{% assign valid_order_types = 'For Here,To Go,Pickup,Drive Thru,Curbside,Delivery' | split: ',' %}
{% assign modifier_keys = 'item_type,itemtype' | split: ',' %}
{% comment %} Determine Shopify Customer name (priority #1) {% endcomment %}
{% assign customer_name = '' %}
{% if order.customer %}
{% if order.customer.name %}
{% assign customer_name = order.customer.name | strip %}
{% else %}
{% assign fn = order.customer.first_name | default: '' | strip %}
{% assign ln = order.customer.last_name | default: '' | strip %}
{% assign built_name = '' %}
{% if fn != blank %}
{% assign built_name = fn %}
{% endif %}
{% if ln != blank %}
{% if built_name != blank %}
{% assign built_name = built_name | append: ' ' | append: ln %}
{% else %}
{% assign built_name = ln %}
{% endif %}
{% endif %}
{% if built_name != blank %}
{% assign customer_name = built_name %}
{% endif %}
{% endif %}
{% endif %}
{% comment %} Initialize unique order types collector {% endcomment %}
{% assign unique_order_types = '' %}
{% comment %} First pass: capture order name/channel, and collect unique prep areas + order types for POS Cafe items (never include modifiers) {% endcomment %}
{% for line_item in order.line_items %}
{% assign item_type_norm = '' %}
{% assign any_poscafe_value = false %}
{% assign is_modifier = false %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if key_norm == 'item_type' %}{% assign item_type_norm = val_norm %}{% endif %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}{% assign is_modifier = true %}{% endif %}
{% if val_norm == poscafe_norm %}{% assign any_poscafe_value = true %}{% endif %}
{% endfor %}
{% assign is_cafe_item = false %}
{% if item_type_norm == poscafe_norm or any_poscafe_value %}{% assign is_cafe_item = true %}{% endif %}
{% if has_order_name == false %}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'name' %}
{% assign has_order_name = true %}
{% assign order_name = attribute[1] | strip_newlines | strip %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% if has_channel == false %}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'channel' %}
{% assign has_channel = true %}
{% assign channel = attribute[1] | strip_newlines | strip %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% if is_cafe_item and is_modifier == false %}
{%- capture item_prep_area -%}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'prep_area' %}
{{ attribute[1] | strip_newlines | strip }}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign item_prep_area = item_prep_area | strip %}
{% if item_prep_area != blank %}
{% assign prep_area_exists = false %}
{% if unique_prep_areas != '' %}
{% assign existing_areas = unique_prep_areas | split: ',' %}
{% for existing_area in existing_areas %}
{% if existing_area == item_prep_area %}{% assign prep_area_exists = true %}{% break %}{% endif %}
{% endfor %}
{% endif %}
{% if prep_area_exists == false %}
{% if unique_prep_areas == '' %}
{% assign unique_prep_areas = item_prep_area %}
{% else %}
{% assign unique_prep_areas = unique_prep_areas | append: ',' | append: item_prep_area %}
{% endif %}
{% endif %}
{% endif %}
{% comment %} Collect unique order types {% endcomment %}
{%- capture item_order_type -%}
{% for attribute in line_item.custom_attributes %}
{% assign attr_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% if attr_key_norm == 'order_type' or attr_key_norm == '_order_type' %}
{{ attribute[1] | strip_newlines | strip }}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign item_order_type = item_order_type | strip %}
{% if item_order_type != blank %}
{% assign order_type_exists = false %}
{% if unique_order_types != '' %}
{% assign existing_types = unique_order_types | split: ',' %}
{% for existing_type in existing_types %}
{% if existing_type == item_order_type %}{% assign order_type_exists = true %}{% break %}{% endif %}
{% endfor %}
{% endif %}
{% if order_type_exists == false %}
{% if unique_order_types == '' %}
{% assign unique_order_types = item_order_type %}
{% else %}
{% assign unique_order_types = unique_order_types | append: ',' | append: item_order_type %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% assign unique_prep_areas_array = unique_prep_areas | split: ',' | sort %}
{% assign unique_order_types_array = unique_order_types | split: ',' %}
{% comment %} Add empty string for "No Order Type" grouping {% endcomment %}
{% assign unique_order_types_with_none = unique_order_types_array | join: ',' %}
{% if unique_order_types_with_none != '' %}
{% assign unique_order_types_with_none = unique_order_types_with_none | append: ',__none__' %}
{% else %}
{% assign unique_order_types_with_none = '__none__' %}
{% endif %}
{% assign unique_order_types_with_none_array = unique_order_types_with_none | split: ',' %}
{% comment %}
Customer address, built once and rendered directly under the name line in the box below.
POS receipt address fields use underscores: address_1 / address_2; province/country prefer
the code and fall back to the full name. Hides entirely when no address is present.
{% endcomment %}
{%- capture box_address_html -%}
{% if display_customer_address %}
{% assign addr = order.shipping_address %}
{% if addr == blank %}{% assign addr = order.billing_address %}{% endif %}
{% if addr == blank and order.customer %}{% assign addr = order.customer.default_address %}{% endif %}
{% if addr %}
{%- capture addr_name -%}
{% if addr.name != blank %}{{ addr.name }}{% else %}{{ addr.first_name }} {{ addr.last_name }}{% endif %}
{%- endcapture -%}
{% assign addr_name = addr_name | strip %}
{% assign addr_prov = addr.province_code %}
{% if addr_prov == blank %}{% assign addr_prov = addr.province %}{% endif %}
{% assign addr_country = addr.country %}
{% if addr_country == blank %}{% assign addr_country = addr.country_code %}{% endif %}
{%- capture city_line -%}
{% if addr.city != blank %}{{ addr.city }}{% endif %}{% if addr_prov != blank %}{% if addr.city != blank %}, {% endif %}{{ addr_prov }}{% endif %}{% if addr.zip != blank %} {{ addr.zip }}{% endif %}
{%- endcapture -%}
{% assign city_line = city_line | strip %}
{%- capture addr_html -%}
{% if addr_name != blank %}<p>{{ addr_name | escape }}</p>{% endif %}
{% if addr.company != blank %}<p>{{ addr.company | escape }}</p>{% endif %}
{% if addr.address_1 != blank %}<p>{{ addr.address_1 | escape }}</p>{% endif %}
{% if addr.address_2 != blank %}<p>{{ addr.address_2 | escape }}</p>{% endif %}
{% if city_line != blank %}<p>{{ city_line | escape }}</p>{% endif %}
{%- comment -%} {% if addr_country != blank %}<p>{{ addr_country | escape }}</p>{% endif %} {%- endcomment -%}
{% if addr.phone != blank %}<p>{{ addr.phone | escape }}</p>{% endif %}
{%- endcapture -%}
{% assign addr_html = addr_html | strip %}
{% if addr_html != blank %}
<div class='box-customer-address'>
{% if customer_address_title != blank %}<p class='box-customer-address-title'>{{ customer_address_title | escape }}</p>{% endif %}
{{ addr_html }}
</div>
{% endif %}
{% endif %}
{% endif %}
{%- endcapture -%}
{% assign box_address_html = box_address_html | strip %}
{% comment %}
Boxed header (customer/order name + address + timestamp). Identical for every group, so build it
once here and reuse it below instead of repeating the markup per section.
{% endcomment %}
{%- capture box_header -%}
{% if customer_name != blank %}
<p class='gift-receipt' style="font-size: 3rem;">{{ customer_name }}</p>
{{ box_address_html }}
<p>{{ order.name }}</p>
<p style="font-weight: normal;margin-top:10px;"><small>{{ order.created_at | date: "%b %d, %Y – %I:%M %p" }}</small></p>
{% if show_channel %}<p class="channel-label" style="font-weight: normal;margin-top:10px;">{{ channel }}</p>{% endif %}
{% elsif has_order_name %}
<p class='gift-receipt' style="font-size: 3rem;">{{ order_name }}</p>
{{ box_address_html }}
<p>{{ order.name }}</p>
<p style="font-weight: normal;margin-top:10px;"><small>{{ order.created_at | date: "%b %d, %Y – %I:%M %p" }}</small></p>
{% if show_channel %}<p class="channel-label" style="font-weight: normal;margin-top:10px;">{{ channel }}</p>{% endif %}
{% else %}
<p class='gift-receipt' style="font-size: 3rem;">{{ order.name }}</p>
{{ box_address_html }}
<p style="font-weight: normal;margin-top:10px;"><small>{{ order.created_at | date: "%b %d, %Y – %I:%M %p" }}</small></p>
{% if show_channel %}<p class="channel-label" style="font-weight: normal;margin-top:10px;">{{ channel }}</p>{% endif %}
{% endif %}
{%- endcapture -%}
{% comment %}
===== Items WITHOUT prep area (incl. non–POS Cafe when enabled), grouped by order type =====
Capture the rows first; only render the box + headers if rows were actually produced.
(This replaces the previous separate "count" passes.)
{% endcomment %}
{%- capture no_prep_body -%}
{% for current_order_type in unique_order_types_with_none_array %}
{%- capture order_type_rows -%}
{% for line_item in order.line_items %}
{% assign item_type_norm = '' %}
{% assign any_poscafe_value = false %}
{% assign is_modifier = false %}
{% assign item_order_type = '' %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if key_norm == 'item_type' %}{% assign item_type_norm = val_norm %}{% endif %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}{% assign is_modifier = true %}{% endif %}
{% assign attr_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}{% if attr_key_norm == 'order_type' or attr_key_norm == '_order_type' %}{% assign item_order_type = attribute[1] | strip %}{% endif %}
{% if val_norm == poscafe_norm %}{% assign any_poscafe_value = true %}{% endif %}
{% endfor %}
{% assign is_cafe_item = false %}
{% if item_type_norm == poscafe_norm or any_poscafe_value %}{% assign is_cafe_item = true %}{% endif %}
{%- capture current_item_prep_area -%}
{% if is_cafe_item %}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'prep_area' %}{{ attribute[1] | strip_newlines | strip }}{% endif %}
{% endfor %}
{% endif %}
{%- endcapture -%}
{% assign current_item_prep_area = current_item_prep_area | strip %}
{% assign include_non_cafe = false %}
{% if show_non_poscafe_items and is_cafe_item == false %}
{% assign include_non_cafe = true %}
{% endif %}
{% assign should_render = false %}
{% if is_modifier == false %}
{% if is_cafe_item and current_item_prep_area == blank %}
{% assign should_render = true %}
{% elsif include_non_cafe %}
{% assign should_render = true %}
{% endif %}
{% endif %}
{% comment %} Check if item matches current order type {% endcomment %}
{% assign matches_order_type = false %}
{% if current_order_type == '__none__' %}
{% if item_order_type == blank %}
{% assign matches_order_type = true %}
{% endif %}
{% else %}
{% if item_order_type == current_order_type %}
{% assign matches_order_type = true %}
{% endif %}
{% endif %}
{% if should_render and matches_order_type %}
<div class='line-item-row'>
<div class='line-item-title'>
<p class='product-name'>{{ line_item.quantity }} x {{ line_item.name | escape }}</p>
{% comment %} First variant description (if any) {% endcomment %}
{% assign variant_description = '' %}
{% for vd in line_item.variant_descriptions %}
{% if vd != blank %}
{% assign variant_description = vd %}
{% break %}
{% endif %}
{% endfor %}
{% assign service_type_value = '' %}
{% if line_item.product %}
{% assign prod_title_norm = line_item.product.title | strip | downcase %}
{% else %}
{% assign prod_title_norm = line_item.name | split: ' - ' | first | strip | downcase %}
{% endif %}
{% assign seen_values = '||' %}
{% assign deferred_notes = '' %}
{% assign deferred_name = '' %}
{% capture attributes_html %}
{% if variant_description != blank %}
{% assign v_clean = variant_description | strip | escape %}
{% assign v_norm = v_clean | downcase %}
{% assign v_key = '|' | append: v_norm | append: '|' %}
{% unless seen_values contains v_key %}
<li>{{ v_clean }}</li>
{% assign seen_values = seen_values | append: v_key %}
{% endunless %}
{% endif %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm_raw = attribute[0] | strip | downcase %}
{% assign key_norm = key_norm_raw | replace: ' ', '_' %}
{% assign key_words = key_norm_raw | replace: '_', ' ' %}
{% if service_type_value == '' and key_norm == 'service_type' %}
{% assign service_type_value = attribute[1] | strip | escape %}
{% endif %}
{% assign first_char = key_norm | slice: 0, 1 %}
{% if first_char == '_' %}{% continue %}{% endif %}
{% if hidden_keys contains key_norm or key_words == prod_title_norm %}{% continue %}{% endif %}
{% assign value_clean = attribute[1] | split: '@' | first | strip | escape %}
{% if value_clean != blank %}
{% if key_norm == 'notes' %}
{% assign deferred_notes = value_clean %}
{% elsif key_norm == 'name' %}
{% assign deferred_name = value_clean %}
{% else %}
{% assign val_norm = value_clean | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ value_clean }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endif %}
{% endfor %}
{% if deferred_notes != blank %}
{% assign val_norm = deferred_notes | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ deferred_notes }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% if deferred_name != blank %}
{% assign val_norm = deferred_name | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>For: {{ deferred_name }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endcapture %}
{% if service_type_value != blank %}<p class="dine-option">{{ service_type_value }}</p>{% endif %}
{% assign attributes_trimmed = attributes_html | strip %}
{% if attributes_trimmed != blank %}<ul>{{ attributes_trimmed }}</ul>{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign order_type_rows = order_type_rows | strip %}
{% if order_type_rows != blank %}
{% comment %} Only show the order-type header when there IS an order type;
items with no order type render without a "No Order Type" heading. {% endcomment %}
{% unless current_order_type == '__none__' %}
<div class='order-type-header'>{{ current_order_type }}</div>
{% endunless %}
{{ order_type_rows }}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign no_prep_body = no_prep_body | strip %}
{% if no_prep_body != blank %}
<div class='receipt-box'>{{ box_header }}</div>
{{ no_prep_body }}
{% endif %}
{% comment %}
===== POS Cafe items grouped BY prep area, then BY order type (modifiers never shown) =====
{% endcomment %}
{% for area in unique_prep_areas_array %}
{% unless forloop.first %}<div class='line-item-row-bottom-border'></div>{% endunless %}
<div class='receipt-box'>{{ box_header }}</div>
<div class='line-item-title'>
<p class='prep-area'>{{ area }}</p>
</div>
{% comment %} Loop through each order type (including __none__ for items without order type) {% endcomment %}
{% for current_order_type in unique_order_types_with_none_array %}
{%- capture order_type_rows -%}
{% for line_item in order.line_items %}
{% assign item_type_norm = '' %}
{% assign any_poscafe_value = false %}
{% assign is_modifier = false %}
{% assign item_order_type = '' %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if key_norm == 'item_type' %}{% assign item_type_norm = val_norm %}{% endif %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}{% assign is_modifier = true %}{% endif %}
{% assign attr_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}{% if attr_key_norm == 'order_type' or attr_key_norm == '_order_type' %}{% assign item_order_type = attribute[1] | strip %}{% endif %}
{% if val_norm == poscafe_norm %}{% assign any_poscafe_value = true %}{% endif %}
{% endfor %}
{% assign is_cafe_item = false %}
{% if item_type_norm == poscafe_norm or any_poscafe_value %}{% assign is_cafe_item = true %}{% endif %}
{% if is_cafe_item and is_modifier == false %}
{%- capture current_item_prep_area -%}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'prep_area' %}{{ attribute[1] | strip_newlines | strip }}{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign current_item_prep_area = current_item_prep_area | strip %}
{% comment %} Check if item matches current order type {% endcomment %}
{% assign matches_order_type = false %}
{% if current_order_type == '__none__' %}
{% if item_order_type == blank %}
{% assign matches_order_type = true %}
{% endif %}
{% else %}
{% if item_order_type == current_order_type %}
{% assign matches_order_type = true %}
{% endif %}
{% endif %}
{% if current_item_prep_area == area and matches_order_type %}
<div class='line-item-row'>
<div class='line-item-title'>
<p class='product-name'>{{ line_item.quantity }} x {{ line_item.name | escape }}</p>
{% comment %} First variant description (if any) {% endcomment %}
{% assign variant_description = '' %}
{% for vd in line_item.variant_descriptions %}
{% if vd != blank %}
{% assign variant_description = vd %}
{% break %}
{% endif %}
{% endfor %}
{% assign service_type_value = '' %}
{% if line_item.product %}
{% assign prod_title_norm = line_item.product.title | strip | downcase %}
{% else %}
{% assign prod_title_norm = line_item.name | split: ' - ' | first | strip | downcase %}
{% endif %}
{% assign seen_values = '||' %}
{% assign deferred_notes = '' %}
{% assign deferred_name = '' %}
{% capture attributes_html %}
{% if variant_description != blank %}
{% assign v_clean = variant_description | strip | escape %}
{% assign v_norm = v_clean | downcase %}
{% assign v_key = '|' | append: v_norm | append: '|' %}
{% unless seen_values contains v_key %}
<li>{{ v_clean }}</li>
{% assign seen_values = seen_values | append: v_key %}
{% endunless %}
{% endif %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm_raw = attribute[0] | strip | downcase %}
{% assign key_norm = key_norm_raw | replace: ' ', '_' %}
{% assign key_words = key_norm_raw | replace: '_', ' ' %}
{% if service_type_value == '' and key_norm == 'service_type' %}
{% assign service_type_value = attribute[1] | strip | escape %}
{% endif %}
{% assign first_char = key_norm | slice: 0, 1 %}
{% if first_char == '_' %}{% continue %}{% endif %}
{% if hidden_keys contains key_norm or key_words == prod_title_norm %}{% continue %}{% endif %}
{% assign value_clean = attribute[1] | split: '@' | first | strip | escape %}
{% if value_clean != blank %}
{% if key_norm == 'notes' %}
{% assign deferred_notes = value_clean %}
{% elsif key_norm == 'name' %}
{% assign deferred_name = value_clean %}
{% else %}
{% assign val_norm = value_clean | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ value_clean }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endif %}
{% endfor %}
{% if deferred_notes != blank %}
{% assign val_norm = deferred_notes | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ deferred_notes }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% if deferred_name != blank %}
{% assign val_norm = deferred_name | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>For: {{ deferred_name }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endcapture %}
{% if service_type_value != blank %}<p class="dine-option">{{ service_type_value }}</p>{% endif %}
{% assign attributes_trimmed = attributes_html | strip %}
{% if attributes_trimmed != blank %}<ul>{{ attributes_trimmed }}</ul>{% endif %}
</div>
</div>
{% endif %}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign order_type_rows = order_type_rows | strip %}
{% if order_type_rows != blank %}
{% comment %} Only show the order-type header when there IS an order type;
items with no order type render without a "No Order Type" heading. {% endcomment %}
{% unless current_order_type == '__none__' %}
<div class='order-type-header'>{{ current_order_type }}</div>
{% endunless %}
{{ order_type_rows }}
{% endif %}
{% endfor %}
{% endfor %}
</section>
</div>
{% comment %}
POS Cafe - Receipts 5.8
Receipts/gift-receipt.liquid
Customer receipt header section.
It is your responsibility to ensure that the printed receipts comply with all the applicable laws.
{% endcomment %}
{% # Show the big total box on the customer receipt? %}
{% assign display_total_header = true %}
{% # Show the customer address? Falls back shipping -> billing -> customer default. %}
{% assign display_customer_address = false %}
{% # Heading shown above the address (set to blank for no heading). %}
{% assign customer_address_title = '' %}
{% # End of the settings definition %}
<!doctype html>
<html>
<head>
<meta charset='UTF-8'>
<meta
name='viewport'
content='user-scalable=no'
>
<style>
:root {
--font-smallest: 0.6875rem;
--font-small: 0.875rem;
--font-medium: 1rem;
--font-large: 1.5rem;
--font-largest: 3rem;
--font-family: system-ui;
}
* {
box-sizing: border-box;
}
body {
margin: 2rem 1rem 2.5rem 1rem;
font-family: var(--font-family);
font-weight: 400;
font-size: var(--font-large);
background: white;
}
p {
margin: 0;
}
.price-box {
border-radius: 0.7rem;
border: 0.3rem solid black;
margin: 2.5rem 0.3125rem;
padding: 1.75rem 1.25rem;
text-align: center;
font-weight: bold;
font-size: var(--font-large);
}
.price-box .total {
font-size: var(--font-largest);
}
.customer-address {
margin: 1.5rem 0.3125rem;
padding: 1rem 1.25rem;
border: 0.15rem dashed black;
border-radius: 0.5rem;
text-align: center;
font-size: 1.25rem;
}
.customer-address-title {
font-weight: bold;
margin-bottom: 0.5rem;
}
.customer-address-name {
font-weight: bold;
}
.customer-address-order {
margin-top: 0.5rem;
font-weight: bold;
}
</style>
</head>
<body>
{% # This section is used by POS UI extensions to show information for the header %}
{% render 'extensions', extension_blocks: extension_blocks.header %}
{% render 'header' %}
{% if display_customer_address %}
{% assign addr = order.shipping_address %}
{% if addr == blank %}{% assign addr = order.billing_address %}{% endif %}
{% if addr == blank and order.customer %}{% assign addr = order.customer.default_address %}{% endif %}
{%- comment -%} POS receipt address fields use underscores: address_1 / address_2.
Province/country prefer the code, fall back to the full name. addr may be empty —
the customer name and order number still render. {%- endcomment -%}
{% comment %} Shopify customer name (bold, shown above the address) {% endcomment %}
{% assign cust_name = '' %}
{% if order.customer %}
{% if order.customer.display_name != blank %}
{% assign cust_name = order.customer.display_name | strip %}
{% elsif order.customer.name != blank %}
{% assign cust_name = order.customer.name | strip %}
{% endif %}
{% endif %}
{%- capture addr_name -%}
{% if addr.name != blank %}{{ addr.name }}{% else %}{{ addr.first_name }} {{ addr.last_name }}{% endif %}
{%- endcapture -%}
{% assign addr_name = addr_name | strip %}
{% comment %} Don't repeat the recipient name in the address when it matches the customer name {% endcomment %}
{% assign show_addr_name = false %}
{% if addr_name != blank %}
{% assign show_addr_name = true %}
{% if cust_name != blank %}
{% assign cn = cust_name | downcase %}
{% assign an = addr_name | downcase %}
{% if cn == an %}{% assign show_addr_name = false %}{% endif %}
{% endif %}
{% endif %}
{% assign addr_prov = addr.province_code %}
{% if addr_prov == blank %}{% assign addr_prov = addr.province %}{% endif %}
{% assign addr_country = addr.country %}
{% if addr_country == blank %}{% assign addr_country = addr.country_code %}{% endif %}
{%- capture city_line -%}
{% if addr.city != blank %}{{ addr.city }}{% endif %}{% if addr_prov != blank %}{% if addr.city != blank %}, {% endif %}{{ addr_prov }}{% endif %}{% if addr.zip != blank %} {{ addr.zip }}{% endif %}
{%- endcapture -%}
{% assign city_line = city_line | strip %}
{%- capture addr_html -%}
{% if show_addr_name %}<p>{{ addr_name | escape }}</p>{% endif %}
{% if addr.company != blank %}<p>{{ addr.company | escape }}</p>{% endif %}
{% if addr.address_1 != blank %}<p>{{ addr.address_1 | escape }}</p>{% endif %}
{% if addr.address_2 != blank %}<p>{{ addr.address_2 | escape }}</p>{% endif %}
{% if city_line != blank %}<p>{{ city_line | escape }}</p>{% endif %}
{%- comment -%} {% if addr_country != blank %}<p>{{ addr_country | escape }}</p>{% endif %} {%- endcomment -%}
{% if addr.phone != blank %}<p>{{ addr.phone | escape }}</p>{% endif %}
{%- endcapture -%}
{% assign addr_html = addr_html | strip %}
<section class='customer-address'>
{% if customer_address_title != blank %}<p class='customer-address-title'>{{ customer_address_title | escape }}</p>{% endif %}
{% if cust_name != blank %}<p class='customer-address-name'>{{ cust_name | escape }}</p>{% endif %}
{{ addr_html }}
{% if order.name != blank %}<p class='customer-address-order'>{{ order.name | escape }}</p>{% endif %}
</section>
{% endif %}
{% if display_total_header %}
<div class='price-box'>
<p class='receipt-total-header'>{{ 'receipt.total' | t }}</p>
<p class='total'>{{ order.total_price | money | escape }}</p>
</div>
{% endif %}
{% render 'gift-receipt-line-items' %}
{% render 'transactions' %}
{% render 'footer' %}
{% # This section is required in Germany %}
{% render 'kassenSichV-compliance' %}
{% # This section is used by POS UI extensions to show information for the footer %}
{% render 'extensions', extension_blocks: extension_blocks.footer %}
</body>
</html>
{% comment %}
POS Cafe - Receipts 5.8
Sections/gift-receipt-line-items.liquid
Customer receipt line items section.
It is your responsibility to ensure that the printed receipts comply with all the applicable laws.
{% endcomment %}
{% assign location_id = location.id | append: "" %}
{% comment %}
Feature toggles (adjust per merchant or per location if needed)
{% endcomment %}
{% assign display_product_sku = false %}
{% assign display_sales_attribution = false %}
{% assign display_compare_at_price = true %}
{% comment %} Hide modifiers? If true, $0.00 modifiers are hidden; priced modifiers still show {% endcomment %}
{% assign exclude_modifiers = true %}
{% comment %} If false, hide ALL modifiers even if they are priced {% endcomment %}
{% assign show_priced_modifiers = true %}
{% comment %} If true and product has tag "hidden-price", hide prices on receipt {% endcomment %}
{% assign hide_price_for_hidden_tag = false %}
{% comment %} If false, suppress the attributes list entirely {% endcomment %}
{% assign show_attributes = false %}
{% comment %} If false, do not show the product variant at all {% endcomment %}
{% assign show_variant = true %}
{% comment %} Show "Part of <bundle/group>" for non-modifier items. Default off. {% endcomment %}
{% assign show_line_item_group = false %}
{% comment %} Show "Part of <bundle/group)" on modifier items only (usually true). {% endcomment %}
{% assign show_group_on_modifiers = true %}
{% comment %} NEW: show Shopify customer name centered above the items list {% endcomment %}
{% assign display_customer_name = false %}
{% comment %} Show the order type (first one found on the line items) as a banner above the
items list. Reads the display "Order Type" attribute. Default off. {% endcomment %}
{% assign display_order_type = false %}
{% comment %}
Internal lists (normalized keys: downcase + spaces -> underscores)
{% endcomment %}
{% assign modifier_keys = 'item_type,itemtype' | split: ',' %}
{% assign hidden_attr_keys = 'item_type,modifier_variants,_modifiervariants,prep_area,channel' | split: ',' %}
<style>
.variant-name,
.line-item-quantity,
.product_sku,
.sale_attribution,
.selling-plan-name,
.line-item-group { font-size: var(--font-large); }
.line-items-header {
width: 100%;
border-bottom: 0.3rem solid black;
display: flex;
justify-content: space-between;
padding: 0 0.3125rem;
}
.line-item-row { padding: 1.35rem 0; display: flex; justify-content: space-between; }
.line-item-row-bottom-border { border-bottom: 0.25rem dotted black; }
.line-item-title { word-wrap: break-word; max-width: 70%; }
.price, .compare-at-price { padding: 0 0.3125rem; }
.line-item-title .product-name, .price, .line-items-header, .product-discount { font-weight: 700; }
.compare-at-price { text-decoration: line-through; font-size: var(--font-medium); justify-content: flex-end; }
.line-item-container { padding-left: 0.3125rem; padding-right: 0.3125rem; }
.discounts-container { padding: 0 0.3125rem; display: flex; justify-content: space-between; flex-direction: column; }
.discount-row { display: flex; flex-direction: row; }
.discount-code { width: 60%; }
.discount-price { width: 40%; text-align: right; }
.price--hidden { opacity: 0.75; }
.customer-receipt-name { text-align: center; font-size: 1.8rem; font-weight: 700; margin: 0.75rem 0 0.5rem; }
.order-type-banner { text-align: center; font-weight: 700; font-size: var(--font-large); text-transform: uppercase; margin: 0.5rem 0 1rem; }
</style>
<div class='line-items'>
{%- comment -%}
Customer name (centered). Only when:
- display_customer_name == true
- order has a linked customer with at least one of first/last name
{%- endcomment -%}
{% if display_customer_name and order.customer %}
{% assign cust_first = order.customer.first_name | default: '' | strip %}
{% assign cust_last = order.customer.last_name | default: '' | strip %}
{% assign customer_name = '' %}
{% if cust_first != '' and cust_last != '' %}
{% assign customer_name = cust_first | append: ' ' | append: cust_last %}
{% elsif cust_first != '' %}
{% assign customer_name = cust_first %}
{% elsif cust_last != '' %}
{% assign customer_name = cust_last %}
{% endif %}
{% if customer_name != '' %}
<div class="customer-receipt-name">{{ customer_name | escape }}</div>
{% endif %}
{% endif %}
{%- comment -%}
Order type banner: show the first order type found across the line items (display key only).
Simple single-value display — no grouping, unlike the order ticket.
{%- endcomment -%}
{% if display_order_type %}
{% assign receipt_order_type = '' %}
{% for line_item in order.line_items %}
{% for attribute in line_item.custom_attributes %}
{% assign ot_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% if ot_key_norm == 'order_type' %}
{% assign receipt_order_type = attribute[1] | strip_newlines | strip %}
{% break %}
{% endif %}
{% endfor %}
{% if receipt_order_type != blank %}{% break %}{% endif %}
{% endfor %}
{% if receipt_order_type != blank %}
<div class='order-type-banner'>{{ receipt_order_type | escape }}</div>
{% endif %}
{% endif %}
<header class='line-items-header'>
<p>{{ 'receipt.item' | t }}</p>
<p>{{ 'receipt.price' | t }}</p>
</header>
<section>
{% for line_item in order.line_items %}
{%- comment -%} Identify Modifiers via line-item properties {%- endcomment -%}
{% assign is_modifier = false %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}
{% assign is_modifier = true %}
{% break %}
{% endif %}
{% endfor %}
{%- comment -%} Priced? (treat > 0 as priced) {%- endcomment -%}
{% assign li_total_num = line_item.total_price | plus: 0 %}
{% assign modifier_is_priced = false %}
{% if li_total_num > 0 %}
{% assign modifier_is_priced = true %}
{% endif %}
{%- comment -%} Optionally hide prices for products tagged 'hidden-price' {%- endcomment -%}
{% assign hide_price = false %}
{% if hide_price_for_hidden_tag and line_item.product and line_item.product.tags contains 'hidden-price' %}
{% assign hide_price = true %}
{% endif %}
{%- comment -%}
Render rules (no parentheses; nested ifs only):
- If NOT a modifier → render as usual.
- If a modifier:
- If show_priced_modifiers == false → skip regardless of price.
- Else if exclude_modifiers == true → skip only when total == 0.
- Else (exclude_modifiers == false) → render always.
{%- endcomment -%}
{% assign skip_item = false %}
{% if is_modifier %}
{% if show_priced_modifiers == false %}
{% assign skip_item = true %}
{% else %}
{% if exclude_modifiers %}
{% if modifier_is_priced == false %}
{% assign skip_item = true %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% unless skip_item %}
<div class='line-item'>
<div class='line-item-row line-item-container'>
<div class='line-item-title'>
<p class='product-name'>{{ line_item.name | escape }}</p>
{%- comment -%}
Variant descriptions:
- If show_attributes == false AND show_variant == true: show standard variant <p> lines (Shopify default).
- If show_attributes == true: we may insert the first variant as the first <li> instead (also guarded by show_variant).
- If show_variant == false: variant is fully suppressed.
{%- endcomment -%}
{% if show_attributes == false %}
{% if show_variant %}
{% for variant_description in line_item.variant_descriptions %}
{% if variant_description != blank %}
<p class='variant-name'>{{ variant_description }}</p>
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if display_product_sku and line_item.sku %}
<p class='product_sku'>{{ line_item.sku }}</p>
{% endif %}
{% if line_item.quantity > 1 %}
{% if hide_price %}
<div class='line-item-quantity'>({{ line_item.quantity }} x —)</div>
{% else %}
<div class='line-item-quantity'>({{ line_item.quantity }} x {{ line_item.price | money | escape }})</div>
{% endif %}
{% endif %}
{%- comment -%}
"Part of ..." visibility:
- On modifiers: show if show_group_on_modifiers is true.
- On non-modifiers: show only if show_line_item_group is true AND show_group_on_modifiers is false
(to avoid duplicate “Part of …” on both parent and children).
{%- endcomment -%}
{% assign has_group_title = false %}
{% if line_item.line_item_group and line_item.line_item_group.title %}
{% assign has_group_title = true %}
{% endif %}
{% if is_modifier %}
{% if has_group_title and show_group_on_modifiers %}
<p class='line-item-group'>{{ 'receipt.part_of' | t }} {{ line_item.line_item_group.title }}</p>
{% endif %}
{% else %}
{% if has_group_title and show_line_item_group and show_group_on_modifiers == false %}
<p class='line-item-group'>{{ 'receipt.part_of' | t }} {{ line_item.line_item_group.title }}</p>
{% endif %}
{% endif %}
{% if display_sales_attribution and line_item.staff_member_description %}
<p class='sale_attribution'>
{{ 'receipt.sold_by_line_item' | t }} {{ line_item.staff_member_description }}
</p>
{% endif %}
{% if show_attributes %}
{% assign first_variant_description = '' %}
{% if show_variant %}
{% for vd in line_item.variant_descriptions %}
{% if vd != blank %}
{% assign first_variant_description = vd %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% if line_item.product %}
{% assign prod_title_norm = line_item.product.title | strip | downcase %}
{% else %}
{% assign prod_title_norm = line_item.name | split: ' - ' | first | strip | downcase %}
{% endif %}
{% assign seen_values = '||' %}
{% capture attributes_html %}
{% if first_variant_description != blank %}
{% assign v_clean = first_variant_description | strip | escape %}
{% assign v_norm = v_clean | downcase %}
{% assign v_key = '|' | append: v_norm | append: '|' %}
{% unless seen_values contains v_key %}
<li>{{ v_clean }}</li>
{% assign seen_values = seen_values | append: v_key %}
{% endunless %}
{% endif %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm_raw = attribute[0] | strip | downcase %}
{% assign key_norm = key_norm_raw | replace: ' ', '_' %}
{% assign key_words = key_norm_raw | replace: '_', ' ' %}
{% assign first_char = key_norm | slice: 0, 1 %}
{% if first_char == '_' %}{% continue %}{% endif %}
{% unless hidden_attr_keys contains key_norm or key_words == prod_title_norm %}
{% assign value_clean = attribute[1] | split: '@' | first | strip | escape %}
{% if value_clean != blank %}
{% assign val_norm = value_clean | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ value_clean }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endunless %}
{% endfor %}
{% endcapture %}
{% assign attributes_trimmed = attributes_html | strip %}
{% if attributes_trimmed != blank %}
<ul>{{ attributes_trimmed }}</ul>
{% endif %}
{% endif %}
{% for selling_plan in line_item.selling_plans %}
{% if selling_plan.name != blank %}
<p class='selling-plan-name'>{{ selling_plan.name | escape }}</p>
{% endif %}
{% endfor %}
</div>
<div>
{% if hide_price %}
<div class='price price--hidden'>—</div>
{% else %}
<div class='price'>{{ line_item.total_price | money | escape }}</div>
{% if display_compare_at_price and line_item.compare_at_price_description %}
<div class='line-item-row compare-at-price'>
{{ line_item.compare_at_price_description | money | escape }}
</div>
{% endif %}
{% endif %}
</div>
</div>
{% if line_item.discounts.size > 0 %}
<div class='discounts-container'>
{% for discount in line_item.discounts %}
{% assign discount_amount_num = discount.amount | plus: 0 %}
{% if discount.description or discount_amount_num > 0 %}
<div class='discount-row'>
<p class='discount-code'>{{ discount.description | escape }}</p>
{% if discount_amount_num > 0 %}
<p class='discount-price price'>-{{ discount.amount | money | escape }}</p>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% for refund in line_item.refunds %}
<p class='refunded-product'>
{% assign created_at = refund.created_at | date %}
{{ 'receipt.refunded_on' | t: quantity: refund.refund_line_item.quantity, date: created_at }}
</p>
{% endfor %}
</div>
<div class='line-item-row-bottom-border'></div>
{% endunless %}
{% endfor %}
</section>
</div>
Use this option if you would like your gift receipt to print as an order ticket, and your sales receipt to
print as your sales receipt. Note, in this configuration, you do not need to edit your
Receipts/sales-receipt.liquid code snippet.
{% comment %}
POS Cafe - Receipts 5.8
Sections/line-items.liquid
Customer receipt line items section.
It is your responsibility to ensure that the printed receipts comply with all the applicable laws.
{% endcomment %}
{% assign location_id = location.id | append: "" %}
{% comment %}
Feature toggles (adjust per merchant or per location if needed)
{% endcomment %}
{% assign display_product_sku = false %}
{% assign display_sales_attribution = false %}
{% assign display_compare_at_price = true %}
{% comment %} Hide modifiers? If true, $0.00 modifiers are hidden; priced modifiers still show {% endcomment %}
{% assign exclude_modifiers = true %}
{% comment %} If false, hide ALL modifiers even if they are priced {% endcomment %}
{% assign show_priced_modifiers = true %}
{% comment %} If true and product has tag "hidden-price", hide prices on receipt {% endcomment %}
{% assign hide_price_for_hidden_tag = false %}
{% comment %} If false, suppress the attributes list entirely {% endcomment %}
{% assign show_attributes = false %}
{% comment %} If false, do not show the product variant at all {% endcomment %}
{% assign show_variant = true %}
{% comment %} Show "Part of <bundle/group>" for non-modifier items. Default off. {% endcomment %}
{% assign show_line_item_group = false %}
{% comment %} Show "Part of <bundle/group)" on modifier items only (usually true). {% endcomment %}
{% assign show_group_on_modifiers = true %}
{% comment %} NEW: show Shopify customer name centered above the items list {% endcomment %}
{% assign display_customer_name = false %}
{% comment %} Show the order type (first one found on the line items) as a banner above the
items list. Reads the display "Order Type" attribute. Default off. {% endcomment %}
{% assign display_order_type = false %}
{% comment %}
Internal lists (normalized keys: downcase + spaces -> underscores)
{% endcomment %}
{% assign modifier_keys = 'item_type,itemtype' | split: ',' %}
{% assign hidden_attr_keys = 'item_type,modifier_variants,_modifiervariants,prep_area,channel' | split: ',' %}
<style>
.variant-name,
.line-item-quantity,
.product_sku,
.sale_attribution,
.selling-plan-name,
.line-item-group { font-size: var(--font-large); }
.line-items-header {
width: 100%;
border-bottom: 0.3rem solid black;
display: flex;
justify-content: space-between;
padding: 0 0.3125rem;
}
.line-item-row { padding: 1.35rem 0; display: flex; justify-content: space-between; }
.line-item-row-bottom-border { border-bottom: 0.25rem dotted black; }
.line-item-title { word-wrap: break-word; max-width: 70%; }
.price, .compare-at-price { padding: 0 0.3125rem; }
.line-item-title .product-name, .price, .line-items-header, .product-discount { font-weight: 700; }
.compare-at-price { text-decoration: line-through; font-size: var(--font-medium); justify-content: flex-end; }
.line-item-container { padding-left: 0.3125rem; padding-right: 0.3125rem; }
.discounts-container { padding: 0 0.3125rem; display: flex; justify-content: space-between; flex-direction: column; }
.discount-row { display: flex; flex-direction: row; }
.discount-code { width: 60%; }
.discount-price { width: 40%; text-align: right; }
.price--hidden { opacity: 0.75; }
.customer-receipt-name { text-align: center; font-size: 1.8rem; font-weight: 700; margin: 0.75rem 0 0.5rem; }
.order-type-banner { text-align: center; font-weight: 700; font-size: var(--font-large); text-transform: uppercase; margin: 0.5rem 0 1rem; }
</style>
<div class='line-items'>
{%- comment -%}
Customer name (centered). Only when:
- display_customer_name == true
- order has a linked customer with at least one of first/last name
{%- endcomment -%}
{% if display_customer_name and order.customer %}
{% assign cust_first = order.customer.first_name | default: '' | strip %}
{% assign cust_last = order.customer.last_name | default: '' | strip %}
{% assign customer_name = '' %}
{% if cust_first != '' and cust_last != '' %}
{% assign customer_name = cust_first | append: ' ' | append: cust_last %}
{% elsif cust_first != '' %}
{% assign customer_name = cust_first %}
{% elsif cust_last != '' %}
{% assign customer_name = cust_last %}
{% endif %}
{% if customer_name != '' %}
<div class="customer-receipt-name">{{ customer_name | escape }}</div>
{% endif %}
{% endif %}
{%- comment -%}
Order type banner: show the first order type found across the line items (display key only).
Simple single-value display — no grouping, unlike the order ticket.
{%- endcomment -%}
{% if display_order_type %}
{% assign receipt_order_type = '' %}
{% for line_item in order.line_items %}
{% for attribute in line_item.custom_attributes %}
{% assign ot_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% if ot_key_norm == 'order_type' %}
{% assign receipt_order_type = attribute[1] | strip_newlines | strip %}
{% break %}
{% endif %}
{% endfor %}
{% if receipt_order_type != blank %}{% break %}{% endif %}
{% endfor %}
{% if receipt_order_type != blank %}
<div class='order-type-banner'>{{ receipt_order_type | escape }}</div>
{% endif %}
{% endif %}
<header class='line-items-header'>
<p>{{ 'receipt.item' | t }}</p>
<p>{{ 'receipt.price' | t }}</p>
</header>
<section>
{% for line_item in order.line_items %}
{%- comment -%} Identify Modifiers via line-item properties {%- endcomment -%}
{% assign is_modifier = false %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}
{% assign is_modifier = true %}
{% break %}
{% endif %}
{% endfor %}
{%- comment -%} Priced? (treat > 0 as priced) {%- endcomment -%}
{% assign li_total_num = line_item.total_price | plus: 0 %}
{% assign modifier_is_priced = false %}
{% if li_total_num > 0 %}
{% assign modifier_is_priced = true %}
{% endif %}
{%- comment -%} Optionally hide prices for products tagged 'hidden-price' {%- endcomment -%}
{% assign hide_price = false %}
{% if hide_price_for_hidden_tag and line_item.product and line_item.product.tags contains 'hidden-price' %}
{% assign hide_price = true %}
{% endif %}
{%- comment -%}
Render rules (no parentheses; nested ifs only):
- If NOT a modifier → render as usual.
- If a modifier:
- If show_priced_modifiers == false → skip regardless of price.
- Else if exclude_modifiers == true → skip only when total == 0.
- Else (exclude_modifiers == false) → render always.
{%- endcomment -%}
{% assign skip_item = false %}
{% if is_modifier %}
{% if show_priced_modifiers == false %}
{% assign skip_item = true %}
{% else %}
{% if exclude_modifiers %}
{% if modifier_is_priced == false %}
{% assign skip_item = true %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% unless skip_item %}
<div class='line-item'>
<div class='line-item-row line-item-container'>
<div class='line-item-title'>
<p class='product-name'>{{ line_item.name | escape }}</p>
{%- comment -%}
Variant descriptions:
- If show_attributes == false AND show_variant == true: show standard variant <p> lines (Shopify default).
- If show_attributes == true: we may insert the first variant as the first <li> instead (also guarded by show_variant).
- If show_variant == false: variant is fully suppressed.
{%- endcomment -%}
{% if show_attributes == false %}
{% if show_variant %}
{% for variant_description in line_item.variant_descriptions %}
{% if variant_description != blank %}
<p class='variant-name'>{{ variant_description }}</p>
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if display_product_sku and line_item.sku %}
<p class='product_sku'>{{ line_item.sku }}</p>
{% endif %}
{% if line_item.quantity > 1 %}
{% if hide_price %}
<div class='line-item-quantity'>({{ line_item.quantity }} x —)</div>
{% else %}
<div class='line-item-quantity'>({{ line_item.quantity }} x {{ line_item.price | money | escape }})</div>
{% endif %}
{% endif %}
{%- comment -%}
"Part of ..." visibility:
- On modifiers: show if show_group_on_modifiers is true.
- On non-modifiers: show only if show_line_item_group is true AND show_group_on_modifiers is false
(to avoid duplicate “Part of …” on both parent and children).
{%- endcomment -%}
{% assign has_group_title = false %}
{% if line_item.line_item_group and line_item.line_item_group.title %}
{% assign has_group_title = true %}
{% endif %}
{% if is_modifier %}
{% if has_group_title and show_group_on_modifiers %}
<p class='line-item-group'>{{ 'receipt.part_of' | t }} {{ line_item.line_item_group.title }}</p>
{% endif %}
{% else %}
{% if has_group_title and show_line_item_group and show_group_on_modifiers == false %}
<p class='line-item-group'>{{ 'receipt.part_of' | t }} {{ line_item.line_item_group.title }}</p>
{% endif %}
{% endif %}
{% if display_sales_attribution and line_item.staff_member_description %}
<p class='sale_attribution'>
{{ 'receipt.sold_by_line_item' | t }} {{ line_item.staff_member_description }}
</p>
{% endif %}
{% if show_attributes %}
{% assign first_variant_description = '' %}
{% if show_variant %}
{% for vd in line_item.variant_descriptions %}
{% if vd != blank %}
{% assign first_variant_description = vd %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% if line_item.product %}
{% assign prod_title_norm = line_item.product.title | strip | downcase %}
{% else %}
{% assign prod_title_norm = line_item.name | split: ' - ' | first | strip | downcase %}
{% endif %}
{% assign seen_values = '||' %}
{% capture attributes_html %}
{% if first_variant_description != blank %}
{% assign v_clean = first_variant_description | strip | escape %}
{% assign v_norm = v_clean | downcase %}
{% assign v_key = '|' | append: v_norm | append: '|' %}
{% unless seen_values contains v_key %}
<li>{{ v_clean }}</li>
{% assign seen_values = seen_values | append: v_key %}
{% endunless %}
{% endif %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm_raw = attribute[0] | strip | downcase %}
{% assign key_norm = key_norm_raw | replace: ' ', '_' %}
{% assign key_words = key_norm_raw | replace: '_', ' ' %}
{% assign first_char = key_norm | slice: 0, 1 %}
{% if first_char == '_' %}{% continue %}{% endif %}
{% unless hidden_attr_keys contains key_norm or key_words == prod_title_norm %}
{% assign value_clean = attribute[1] | split: '@' | first | strip | escape %}
{% if value_clean != blank %}
{% assign val_norm = value_clean | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ value_clean }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endunless %}
{% endfor %}
{% endcapture %}
{% assign attributes_trimmed = attributes_html | strip %}
{% if attributes_trimmed != blank %}
<ul>{{ attributes_trimmed }}</ul>
{% endif %}
{% endif %}
{% for selling_plan in line_item.selling_plans %}
{% if selling_plan.name != blank %}
<p class='selling-plan-name'>{{ selling_plan.name | escape }}</p>
{% endif %}
{% endfor %}
</div>
<div>
{% if hide_price %}
<div class='price price--hidden'>—</div>
{% else %}
<div class='price'>{{ line_item.total_price | money | escape }}</div>
{% if display_compare_at_price and line_item.compare_at_price_description %}
<div class='line-item-row compare-at-price'>
{{ line_item.compare_at_price_description | money | escape }}
</div>
{% endif %}
{% endif %}
</div>
</div>
{% if line_item.discounts.size > 0 %}
<div class='discounts-container'>
{% for discount in line_item.discounts %}
{% assign discount_amount_num = discount.amount | plus: 0 %}
{% if discount.description or discount_amount_num > 0 %}
<div class='discount-row'>
<p class='discount-code'>{{ discount.description | escape }}</p>
{% if discount_amount_num > 0 %}
<p class='discount-price price'>-{{ discount.amount | money | escape }}</p>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% for refund in line_item.refunds %}
<p class='refunded-product'>
{% assign created_at = refund.created_at | date %}
{{ 'receipt.refunded_on' | t: quantity: refund.refund_line_item.quantity, date: created_at }}
</p>
{% endfor %}
</div>
<div class='line-item-row-bottom-border'></div>
{% endunless %}
{% endfor %}
</section>
</div>
{% comment %}
POS Cafe - Receipts 5.8
Receipts/gift-receipt.liquid
Order ticket header section.
It is your responsibility to ensure that the printed receipts comply with all the applicable laws.
{% endcomment %}
{% # Display order note %}
{% assign display_order_note = true %}
{% # End of the settings definition %}
<!doctype html>
<html>
<head>
<meta charset='UTF-8'>
<meta
name='viewport'
content='user-scalable=no'
>
<style>
:root {
--font-smallest: 0.6875rem;
--font-small: 0.875rem;
--font-medium: 1rem;
--font-large: 1.5rem;
--font-largest: 3rem;
--font-family: system-ui;
}
* {
box-sizing: border-box;
}
body {
margin: 2rem 1rem 2.5rem 1rem;
font-family: var(--font-family);
font-weight: 400;
font-size: var(--font-large);
background: white;
}
p {
margin: 0;
}
.receipt-box {
border-color: black;
border-radius: 0.7rem;
border: 0.3rem solid;
margin: 2.5rem 0.3125rem;
padding: 1.75rem 1.25rem;
text-align: center;
font-weight: bold;
font-size: var(--font-large);
}
.receipt-box .channel-label {
font-weight: bold;
font-size: var(--font-large);
}
.note {
padding-top: 1.35rem;
border-top: 0.25rem dotted black;
font-size: var(--font-large);
}
.note-title {
font-weight: bold;
padding-bottom: 0.3125rem;
font-size: var(--font-large);
}
</style>
</head>
<body>
{% render 'gift-receipt-line-items' %}
{% if display_order_note %}
{% if order.note != nil and order.note != blank %}
<section class='note'>
<p class='note-title'>{{ 'receipt.order_note' | t }}</p>
<p>{{ order.note | escape }}</p>
</section>
{% endif %}
{% endif %}
</body>
</html>
{% comment %}
POS Cafe - Receipts 5.8
Sections/gift-receipt-line-items.liquid
Order ticket line items section.
It is your responsibility to ensure that the printed receipts comply with all the applicable laws.
{% endcomment %}
<style>
.variant-name,
.line-item-quantity,
.line-item-group { font-size: var(--font-large); }
.line-items-header {
width: 100%;
border-bottom: 0.3rem solid black;
display: flex;
justify-content: space-between;
padding: 0 0.3125rem;
}
.line-item-row { padding: 1.35rem 0; display: flex; justify-content: space-between; }
.line-item-row-bottom-border { border-bottom: 0.25rem dotted black; }
.line-item-title, .line-item-quantity { padding: 0 0.3125rem; }
.line-item-title .product-name,
.line-item-quantity,
.line-items-header { font-size: 3rem; }
.line-item-title .prep-area,
.line-item-quantity,
.line-items-header {
font-size: 3rem;
font-weight:bold;
text-align:center;
margin-bottom:0.5rem;
}
.dine-option {
font-size: 2rem;
font-weight: 700;
text-transform: uppercase;
margin-top: 0.25rem;
}
li { font-size: 2rem; }
.order-type-header {
font-size: 2rem;
font-weight: bold;
padding: 0.75rem 0.3125rem;
margin-top: 1rem;
border-bottom: 0.15rem dashed black;
text-transform: uppercase;
}
hr.cut-here { border-top: 10px dashed black; }
.box-customer-address { font-weight: normal; font-size: var(--font-large); margin-top: 0.75rem; }
.box-customer-address-title { font-weight: bold; }
</style>
<div>
<section>
{% # Set variables %}
{% assign poscafe = "POS Cafe" %}
{% assign poscafe_norm = poscafe | downcase %}
{% assign unique_prep_areas = '' %}
{% assign has_order_name = false %}
{% assign order_name = '' %}
{% assign has_channel = false %}
{% assign channel = "Online" %}
{% assign show_channel = false %} {% # toggle: show/hide the channel line in header %}
{% comment %} Toggle: include non–POS Cafe items in the “no prep area” section {% endcomment %}
{% assign show_non_poscafe_items = false %}
{% comment %} Toggle: show the customer address inside the boxed header (below the name).
Falls back order.shipping_address -> billing_address -> customer default; hides when none. {% endcomment %}
{% assign display_customer_address = false %}
{% comment %} Heading shown above the address in the box (blank = no heading). {% endcomment %}
{% assign customer_address_title = '' %}
{% comment %} Keys to hide from attributes (normalized: downcase + spaces->underscores) {% endcomment %}
{% assign hidden_keys = 'item_type,modifier_variants,_modifiervariants,prep_area,channel,service_type,order_type,_order_type' | split: ',' %}
{% comment %} Valid order type values (display names used directly as values) {% endcomment %}
{% assign valid_order_types = 'For Here,To Go,Pickup,Drive Thru,Curbside,Delivery' | split: ',' %}
{% assign modifier_keys = 'item_type,itemtype' | split: ',' %}
{% comment %} Determine Shopify Customer name (priority #1) {% endcomment %}
{% assign customer_name = '' %}
{% if order.customer %}
{% if order.customer.name %}
{% assign customer_name = order.customer.name | strip %}
{% else %}
{% assign fn = order.customer.first_name | default: '' | strip %}
{% assign ln = order.customer.last_name | default: '' | strip %}
{% assign built_name = '' %}
{% if fn != blank %}
{% assign built_name = fn %}
{% endif %}
{% if ln != blank %}
{% if built_name != blank %}
{% assign built_name = built_name | append: ' ' | append: ln %}
{% else %}
{% assign built_name = ln %}
{% endif %}
{% endif %}
{% if built_name != blank %}
{% assign customer_name = built_name %}
{% endif %}
{% endif %}
{% endif %}
{% comment %} Initialize unique order types collector {% endcomment %}
{% assign unique_order_types = '' %}
{% comment %} First pass: capture order name/channel, and collect unique prep areas + order types for POS Cafe items (never include modifiers) {% endcomment %}
{% for line_item in order.line_items %}
{% assign item_type_norm = '' %}
{% assign any_poscafe_value = false %}
{% assign is_modifier = false %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if key_norm == 'item_type' %}{% assign item_type_norm = val_norm %}{% endif %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}{% assign is_modifier = true %}{% endif %}
{% if val_norm == poscafe_norm %}{% assign any_poscafe_value = true %}{% endif %}
{% endfor %}
{% assign is_cafe_item = false %}
{% if item_type_norm == poscafe_norm or any_poscafe_value %}{% assign is_cafe_item = true %}{% endif %}
{% if has_order_name == false %}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'name' %}
{% assign has_order_name = true %}
{% assign order_name = attribute[1] | strip_newlines | strip %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% if has_channel == false %}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'channel' %}
{% assign has_channel = true %}
{% assign channel = attribute[1] | strip_newlines | strip %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% if is_cafe_item and is_modifier == false %}
{%- capture item_prep_area -%}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'prep_area' %}
{{ attribute[1] | strip_newlines | strip }}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign item_prep_area = item_prep_area | strip %}
{% if item_prep_area != blank %}
{% assign prep_area_exists = false %}
{% if unique_prep_areas != '' %}
{% assign existing_areas = unique_prep_areas | split: ',' %}
{% for existing_area in existing_areas %}
{% if existing_area == item_prep_area %}{% assign prep_area_exists = true %}{% break %}{% endif %}
{% endfor %}
{% endif %}
{% if prep_area_exists == false %}
{% if unique_prep_areas == '' %}
{% assign unique_prep_areas = item_prep_area %}
{% else %}
{% assign unique_prep_areas = unique_prep_areas | append: ',' | append: item_prep_area %}
{% endif %}
{% endif %}
{% endif %}
{% comment %} Collect unique order types {% endcomment %}
{%- capture item_order_type -%}
{% for attribute in line_item.custom_attributes %}
{% assign attr_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% if attr_key_norm == 'order_type' or attr_key_norm == '_order_type' %}
{{ attribute[1] | strip_newlines | strip }}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign item_order_type = item_order_type | strip %}
{% if item_order_type != blank %}
{% assign order_type_exists = false %}
{% if unique_order_types != '' %}
{% assign existing_types = unique_order_types | split: ',' %}
{% for existing_type in existing_types %}
{% if existing_type == item_order_type %}{% assign order_type_exists = true %}{% break %}{% endif %}
{% endfor %}
{% endif %}
{% if order_type_exists == false %}
{% if unique_order_types == '' %}
{% assign unique_order_types = item_order_type %}
{% else %}
{% assign unique_order_types = unique_order_types | append: ',' | append: item_order_type %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% assign unique_prep_areas_array = unique_prep_areas | split: ',' | sort %}
{% assign unique_order_types_array = unique_order_types | split: ',' %}
{% comment %} Add empty string for "No Order Type" grouping {% endcomment %}
{% assign unique_order_types_with_none = unique_order_types_array | join: ',' %}
{% if unique_order_types_with_none != '' %}
{% assign unique_order_types_with_none = unique_order_types_with_none | append: ',__none__' %}
{% else %}
{% assign unique_order_types_with_none = '__none__' %}
{% endif %}
{% assign unique_order_types_with_none_array = unique_order_types_with_none | split: ',' %}
{% comment %}
Customer address, built once and rendered directly under the name line in the box below.
POS receipt address fields use underscores: address_1 / address_2; province/country prefer
the code and fall back to the full name. Hides entirely when no address is present.
{% endcomment %}
{%- capture box_address_html -%}
{% if display_customer_address %}
{% assign addr = order.shipping_address %}
{% if addr == blank %}{% assign addr = order.billing_address %}{% endif %}
{% if addr == blank and order.customer %}{% assign addr = order.customer.default_address %}{% endif %}
{% if addr %}
{%- capture addr_name -%}
{% if addr.name != blank %}{{ addr.name }}{% else %}{{ addr.first_name }} {{ addr.last_name }}{% endif %}
{%- endcapture -%}
{% assign addr_name = addr_name | strip %}
{% assign addr_prov = addr.province_code %}
{% if addr_prov == blank %}{% assign addr_prov = addr.province %}{% endif %}
{% assign addr_country = addr.country %}
{% if addr_country == blank %}{% assign addr_country = addr.country_code %}{% endif %}
{%- capture city_line -%}
{% if addr.city != blank %}{{ addr.city }}{% endif %}{% if addr_prov != blank %}{% if addr.city != blank %}, {% endif %}{{ addr_prov }}{% endif %}{% if addr.zip != blank %} {{ addr.zip }}{% endif %}
{%- endcapture -%}
{% assign city_line = city_line | strip %}
{%- capture addr_html -%}
{% if addr_name != blank %}<p>{{ addr_name | escape }}</p>{% endif %}
{% if addr.company != blank %}<p>{{ addr.company | escape }}</p>{% endif %}
{% if addr.address_1 != blank %}<p>{{ addr.address_1 | escape }}</p>{% endif %}
{% if addr.address_2 != blank %}<p>{{ addr.address_2 | escape }}</p>{% endif %}
{% if city_line != blank %}<p>{{ city_line | escape }}</p>{% endif %}
{%- comment -%} {% if addr_country != blank %}<p>{{ addr_country | escape }}</p>{% endif %} {%- endcomment -%}
{% if addr.phone != blank %}<p>{{ addr.phone | escape }}</p>{% endif %}
{%- endcapture -%}
{% assign addr_html = addr_html | strip %}
{% if addr_html != blank %}
<div class='box-customer-address'>
{% if customer_address_title != blank %}<p class='box-customer-address-title'>{{ customer_address_title | escape }}</p>{% endif %}
{{ addr_html }}
</div>
{% endif %}
{% endif %}
{% endif %}
{%- endcapture -%}
{% assign box_address_html = box_address_html | strip %}
{% comment %}
Boxed header (customer/order name + address + timestamp). Identical for every group, so build it
once here and reuse it below instead of repeating the markup per section.
{% endcomment %}
{%- capture box_header -%}
{% if customer_name != blank %}
<p class='gift-receipt' style="font-size: 3rem;">{{ customer_name }}</p>
{{ box_address_html }}
<p>{{ order.name }}</p>
<p style="font-weight: normal;margin-top:10px;"><small>{{ order.created_at | date: "%b %d, %Y – %I:%M %p" }}</small></p>
{% if show_channel %}<p class="channel-label" style="font-weight: normal;margin-top:10px;">{{ channel }}</p>{% endif %}
{% elsif has_order_name %}
<p class='gift-receipt' style="font-size: 3rem;">{{ order_name }}</p>
{{ box_address_html }}
<p>{{ order.name }}</p>
<p style="font-weight: normal;margin-top:10px;"><small>{{ order.created_at | date: "%b %d, %Y – %I:%M %p" }}</small></p>
{% if show_channel %}<p class="channel-label" style="font-weight: normal;margin-top:10px;">{{ channel }}</p>{% endif %}
{% else %}
<p class='gift-receipt' style="font-size: 3rem;">{{ order.name }}</p>
{{ box_address_html }}
<p style="font-weight: normal;margin-top:10px;"><small>{{ order.created_at | date: "%b %d, %Y – %I:%M %p" }}</small></p>
{% if show_channel %}<p class="channel-label" style="font-weight: normal;margin-top:10px;">{{ channel }}</p>{% endif %}
{% endif %}
{%- endcapture -%}
{% comment %}
===== Items WITHOUT prep area (incl. non–POS Cafe when enabled), grouped by order type =====
Capture the rows first; only render the box + headers if rows were actually produced.
(This replaces the previous separate "count" passes.)
{% endcomment %}
{%- capture no_prep_body -%}
{% for current_order_type in unique_order_types_with_none_array %}
{%- capture order_type_rows -%}
{% for line_item in order.line_items %}
{% assign item_type_norm = '' %}
{% assign any_poscafe_value = false %}
{% assign is_modifier = false %}
{% assign item_order_type = '' %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if key_norm == 'item_type' %}{% assign item_type_norm = val_norm %}{% endif %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}{% assign is_modifier = true %}{% endif %}
{% assign attr_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}{% if attr_key_norm == 'order_type' or attr_key_norm == '_order_type' %}{% assign item_order_type = attribute[1] | strip %}{% endif %}
{% if val_norm == poscafe_norm %}{% assign any_poscafe_value = true %}{% endif %}
{% endfor %}
{% assign is_cafe_item = false %}
{% if item_type_norm == poscafe_norm or any_poscafe_value %}{% assign is_cafe_item = true %}{% endif %}
{%- capture current_item_prep_area -%}
{% if is_cafe_item %}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'prep_area' %}{{ attribute[1] | strip_newlines | strip }}{% endif %}
{% endfor %}
{% endif %}
{%- endcapture -%}
{% assign current_item_prep_area = current_item_prep_area | strip %}
{% assign include_non_cafe = false %}
{% if show_non_poscafe_items and is_cafe_item == false %}
{% assign include_non_cafe = true %}
{% endif %}
{% assign should_render = false %}
{% if is_modifier == false %}
{% if is_cafe_item and current_item_prep_area == blank %}
{% assign should_render = true %}
{% elsif include_non_cafe %}
{% assign should_render = true %}
{% endif %}
{% endif %}
{% comment %} Check if item matches current order type {% endcomment %}
{% assign matches_order_type = false %}
{% if current_order_type == '__none__' %}
{% if item_order_type == blank %}
{% assign matches_order_type = true %}
{% endif %}
{% else %}
{% if item_order_type == current_order_type %}
{% assign matches_order_type = true %}
{% endif %}
{% endif %}
{% if should_render and matches_order_type %}
<div class='line-item-row'>
<div class='line-item-title'>
<p class='product-name'>{{ line_item.quantity }} x {{ line_item.name | escape }}</p>
{% comment %} First variant description (if any) {% endcomment %}
{% assign variant_description = '' %}
{% for vd in line_item.variant_descriptions %}
{% if vd != blank %}
{% assign variant_description = vd %}
{% break %}
{% endif %}
{% endfor %}
{% assign service_type_value = '' %}
{% if line_item.product %}
{% assign prod_title_norm = line_item.product.title | strip | downcase %}
{% else %}
{% assign prod_title_norm = line_item.name | split: ' - ' | first | strip | downcase %}
{% endif %}
{% assign seen_values = '||' %}
{% assign deferred_notes = '' %}
{% assign deferred_name = '' %}
{% capture attributes_html %}
{% if variant_description != blank %}
{% assign v_clean = variant_description | strip | escape %}
{% assign v_norm = v_clean | downcase %}
{% assign v_key = '|' | append: v_norm | append: '|' %}
{% unless seen_values contains v_key %}
<li>{{ v_clean }}</li>
{% assign seen_values = seen_values | append: v_key %}
{% endunless %}
{% endif %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm_raw = attribute[0] | strip | downcase %}
{% assign key_norm = key_norm_raw | replace: ' ', '_' %}
{% assign key_words = key_norm_raw | replace: '_', ' ' %}
{% if service_type_value == '' and key_norm == 'service_type' %}
{% assign service_type_value = attribute[1] | strip | escape %}
{% endif %}
{% assign first_char = key_norm | slice: 0, 1 %}
{% if first_char == '_' %}{% continue %}{% endif %}
{% if hidden_keys contains key_norm or key_words == prod_title_norm %}{% continue %}{% endif %}
{% assign value_clean = attribute[1] | split: '@' | first | strip | escape %}
{% if value_clean != blank %}
{% if key_norm == 'notes' %}
{% assign deferred_notes = value_clean %}
{% elsif key_norm == 'name' %}
{% assign deferred_name = value_clean %}
{% else %}
{% assign val_norm = value_clean | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ value_clean }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endif %}
{% endfor %}
{% if deferred_notes != blank %}
{% assign val_norm = deferred_notes | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ deferred_notes }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% if deferred_name != blank %}
{% assign val_norm = deferred_name | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>For: {{ deferred_name }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endcapture %}
{% if service_type_value != blank %}<p class="dine-option">{{ service_type_value }}</p>{% endif %}
{% assign attributes_trimmed = attributes_html | strip %}
{% if attributes_trimmed != blank %}<ul>{{ attributes_trimmed }}</ul>{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign order_type_rows = order_type_rows | strip %}
{% if order_type_rows != blank %}
{% comment %} Only show the order-type header when there IS an order type;
items with no order type render without a "No Order Type" heading. {% endcomment %}
{% unless current_order_type == '__none__' %}
<div class='order-type-header'>{{ current_order_type }}</div>
{% endunless %}
{{ order_type_rows }}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign no_prep_body = no_prep_body | strip %}
{% if no_prep_body != blank %}
<div class='receipt-box'>{{ box_header }}</div>
{{ no_prep_body }}
{% endif %}
{% comment %}
===== POS Cafe items grouped BY prep area, then BY order type (modifiers never shown) =====
{% endcomment %}
{% for area in unique_prep_areas_array %}
{% unless forloop.first %}<div class='line-item-row-bottom-border'></div>{% endunless %}
<div class='receipt-box'>{{ box_header }}</div>
<div class='line-item-title'>
<p class='prep-area'>{{ area }}</p>
</div>
{% comment %} Loop through each order type (including __none__ for items without order type) {% endcomment %}
{% for current_order_type in unique_order_types_with_none_array %}
{%- capture order_type_rows -%}
{% for line_item in order.line_items %}
{% assign item_type_norm = '' %}
{% assign any_poscafe_value = false %}
{% assign is_modifier = false %}
{% assign item_order_type = '' %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}
{% assign val_norm = attribute[1] | strip | downcase %}
{% if key_norm == 'item_type' %}{% assign item_type_norm = val_norm %}{% endif %}
{% if modifier_keys contains key_norm and val_norm == 'modifier' %}{% assign is_modifier = true %}{% endif %}
{% assign attr_key_norm = attribute[0] | strip | downcase | replace: ' ', '_' %}{% if attr_key_norm == 'order_type' or attr_key_norm == '_order_type' %}{% assign item_order_type = attribute[1] | strip %}{% endif %}
{% if val_norm == poscafe_norm %}{% assign any_poscafe_value = true %}{% endif %}
{% endfor %}
{% assign is_cafe_item = false %}
{% if item_type_norm == poscafe_norm or any_poscafe_value %}{% assign is_cafe_item = true %}{% endif %}
{% if is_cafe_item and is_modifier == false %}
{%- capture current_item_prep_area -%}
{% for attribute in line_item.custom_attributes %}
{% if attribute[0] == 'prep_area' %}{{ attribute[1] | strip_newlines | strip }}{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign current_item_prep_area = current_item_prep_area | strip %}
{% comment %} Check if item matches current order type {% endcomment %}
{% assign matches_order_type = false %}
{% if current_order_type == '__none__' %}
{% if item_order_type == blank %}
{% assign matches_order_type = true %}
{% endif %}
{% else %}
{% if item_order_type == current_order_type %}
{% assign matches_order_type = true %}
{% endif %}
{% endif %}
{% if current_item_prep_area == area and matches_order_type %}
<div class='line-item-row'>
<div class='line-item-title'>
<p class='product-name'>{{ line_item.quantity }} x {{ line_item.name | escape }}</p>
{% comment %} First variant description (if any) {% endcomment %}
{% assign variant_description = '' %}
{% for vd in line_item.variant_descriptions %}
{% if vd != blank %}
{% assign variant_description = vd %}
{% break %}
{% endif %}
{% endfor %}
{% assign service_type_value = '' %}
{% if line_item.product %}
{% assign prod_title_norm = line_item.product.title | strip | downcase %}
{% else %}
{% assign prod_title_norm = line_item.name | split: ' - ' | first | strip | downcase %}
{% endif %}
{% assign seen_values = '||' %}
{% assign deferred_notes = '' %}
{% assign deferred_name = '' %}
{% capture attributes_html %}
{% if variant_description != blank %}
{% assign v_clean = variant_description | strip | escape %}
{% assign v_norm = v_clean | downcase %}
{% assign v_key = '|' | append: v_norm | append: '|' %}
{% unless seen_values contains v_key %}
<li>{{ v_clean }}</li>
{% assign seen_values = seen_values | append: v_key %}
{% endunless %}
{% endif %}
{% for attribute in line_item.custom_attributes %}
{% assign key_norm_raw = attribute[0] | strip | downcase %}
{% assign key_norm = key_norm_raw | replace: ' ', '_' %}
{% assign key_words = key_norm_raw | replace: '_', ' ' %}
{% if service_type_value == '' and key_norm == 'service_type' %}
{% assign service_type_value = attribute[1] | strip | escape %}
{% endif %}
{% assign first_char = key_norm | slice: 0, 1 %}
{% if first_char == '_' %}{% continue %}{% endif %}
{% if hidden_keys contains key_norm or key_words == prod_title_norm %}{% continue %}{% endif %}
{% assign value_clean = attribute[1] | split: '@' | first | strip | escape %}
{% if value_clean != blank %}
{% if key_norm == 'notes' %}
{% assign deferred_notes = value_clean %}
{% elsif key_norm == 'name' %}
{% assign deferred_name = value_clean %}
{% else %}
{% assign val_norm = value_clean | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ value_clean }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endif %}
{% endfor %}
{% if deferred_notes != blank %}
{% assign val_norm = deferred_notes | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>{{ deferred_notes }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% if deferred_name != blank %}
{% assign val_norm = deferred_name | downcase %}
{% assign needle = '|' | append: val_norm | append: '|' %}
{% unless seen_values contains needle %}
<li>For: {{ deferred_name }}</li>
{% assign seen_values = seen_values | append: needle %}
{% endunless %}
{% endif %}
{% endcapture %}
{% if service_type_value != blank %}<p class="dine-option">{{ service_type_value }}</p>{% endif %}
{% assign attributes_trimmed = attributes_html | strip %}
{% if attributes_trimmed != blank %}<ul>{{ attributes_trimmed }}</ul>{% endif %}
</div>
</div>
{% endif %}
{% endif %}
{% endfor %}
{%- endcapture -%}
{% assign order_type_rows = order_type_rows | strip %}
{% if order_type_rows != blank %}
{% comment %} Only show the order-type header when there IS an order type;
items with no order type render without a "No Order Type" heading. {% endcomment %}
{% unless current_order_type == '__none__' %}
<div class='order-type-header'>{{ current_order_type }}</div>
{% endunless %}
{{ order_type_rows }}
{% endif %}
{% endfor %}
{% endfor %}
</section>
</div>
Shopify POS print options
In POS » Settings » Receipts, review your printing preferences for each location (e.g., whether to automatically print after checkout, and which printer to use). If you use the Gift receipt as Order Ticket option, staff will print the Order Ticket by tapping the gift receipt button on the completion screen.
Troubleshooting
| Issue | Solution |
|---|---|
| No items appear on the Order Ticket? | Only POS Cafe items are printed. Ensure items were created in POS Cafe and (optionally) that their prep_area is set. |
| Station headings look wrong? | Set/adjust Prep Areas in POS Cafe; items without a prep area print in the “no prep area” section. |
| Need to revert? | Click on the undo icon |