Branded Receipts
When a customer completes a purchase on your website, r2ware can email them a
branded PDF receipt. This is opt-in: you turn it on by adding a
_layouts/receipt.html layout to your repo. Until you do, the platform issues
no receipt of its own and Stripe emailed receipt still reaches every buyer, so
no customer is ever left without proof of purchase.
The receipt's wording, numbering scheme, and layout all live in your source repo alongside your other site files — edit them, commit, and publish, and new receipts pick up the changes. No dashboard config, no database row.
The receipt is built by the same builder that builds the rest of your site
(jigyll, our Jekyll-compatible engine), so {% include %} of your partials,
site.data.*, _config.yml, and layout chaining all behave exactly as they do
on your live pages. The built HTML is then rendered to a PDF.
What gets sent
- A one-page PDF receipt, attached to an email sent to the customer's checkout email.
- A per-site, continuous sequential receipt number (e.g.
EX-260001), not the raw platform order id. - The fields you'd expect: transaction date, tax year, one line per item, subtotal/discount/shipping/total, ship-to address, your thank-you copy and footer.
Receipt numbering
Your layout controls how the receipt looks — logo, copy, notices, and footer
are just markup you write in the body of _layouts/receipt.html, the same as
any other page. The one thing the platform computes for you is the
receipt number (a per-site sequence), so the number format is the only
setting that has to live in the layout's front matter:
---
number_template: "EX-{{ YY }}{{ NNNN }}"
---
{% include "includes/header.html" %}
<h1>Receipt {{ page.receipt_number }}</h1>
<p>Thank you for supporting Example Co.</p>
...
| Field | Default | Purpose |
|---|---|---|
number_template |
{{ YY }}{{ NNNN }} |
Liquid string built from the context variables in the table below (see Number template variables). Write your brand straight into it — EX-{{ YY }}{{ NNNN }} produces EX-260001. |
number_template is optional: omit it and receipts number as 260001
(bare sequence); set one to brand the number.
The sequence is per-site and continuous — it never resets. Your site's
Nth receipt is number N whatever calendar year it lands in (the year shown
in YY/YYYY comes from each order's own transaction date, so a receipt still
carries the year it was issued in). The numeric collision guard is on the
(site_id, sequence_n) sequence, so a broken number_template can never
produce a duplicate or blank number — it falls back to the default format.
Number template variables
number_template is rendered against a set of precomputed, ready-to-print
variables — each is already a formatted string, so you interpolate them
directly with {{ … }} and never need a filter. The
date/time parts come from the order's transaction time (completed_at), and
the sequence parts from the allocated per-site number.
| Variable | Example | Meaning |
|---|---|---|
YY |
26 |
Two-digit year of the transaction |
YYYY |
2026 |
Four-digit year of the transaction |
MM |
01 |
Two-digit month |
DD |
02 |
Two-digit day |
HH |
03 |
Two-digit hour (24h) |
N |
1 |
Sequence number, no padding |
NN / NNN / NNNN |
0001 |
Sequence number, zero-padded to a minimum width (a larger number is never truncated) |
R / RR / RRR / RRRR |
4821 |
Random decimal digits, drawn once and frozen on the receipt |
X / XX / XXX / XXXX |
9F3A |
Random uppercase hex digits, drawn once and frozen on the receipt |
Example: {{ YY }}{{ MM }}-{{ NNNN }} → 2601-0001.
The layout is the template — and it's the opt-in. It always lives at
_layouts/receipt.html. If your repo has no such file, the platform issues no receipt of its own — there is no built-in fallback template. Your customer still receives Stripe's own emailed receipt for the payment, so adding the layout is purely how you opt into a branded receipt on top of it.
The receipt layout
_layouts/receipt.html is a normal jigyll layout. Because the receipt is a full
site build, it can {% include %} your partials, read site.data.*, and
chain to a parent layout — the receipt stays visually consistent with your site:
{% include "includes/header.html" %}
<h1>Receipt {{ page.receipt_number }}</h1>
{% for item in page.line_items %}
{{ item.product }} — {{ item.line_cents | money }}
{% endfor %}
<strong>Total: {{ page.total_cents | money }}</strong>
...
Anything you put in the layout's own front matter is available in the body as
{{ layout.* }} (standard Jekyll) — handy for a logo path or a line
of copy you'd rather keep out of the markup.
Context variables
Per-order data is exposed under page.*. Money amounts are raw cent
integers (*_cents) and dates are raw datetimes — format both with the
filters described in the note below.
| Variable | Type | Notes |
|---|---|---|
page.receipt_number |
string | e.g. EX-260001 |
page.transaction_date |
datetime | Date of the sale (completed_at); format with date |
page.issued_date |
datetime | Receipt issue date; format with date |
page.tax_year |
int | Calendar year of the transaction |
page.line_items |
list | Each: {product, description, quantity, unit_cents, line_cents} |
page.subtotal_cents |
int | Subtotal |
page.discount_amount_cents |
int | Discount amount |
page.discount_code_text |
string | Discount code label, if any |
page.shipping_cents |
int | Shipping |
page.total_cents |
int | Total charged |
page.amount_cents |
int | Charge before shipping |
page.currency |
string | e.g. USD |
page.customer_email |
string | |
page.shipping_address |
object | {name, line1, line2, city, state, zip} |
page.logo_url |
string | Absolute URL of your published logo, if any |
A note on filters
The receipt body is built by jigyll, which ships Shopify-style money filters (jigyll v1.1.0+). They take a cent amount and return a formatted string:
| Filter | Example | Output |
|---|---|---|
money |
{{ page.total_cents | money }} |
$43.00 |
money_with_currency |
{{ page.total_cents | money_with_currency }} |
$43.00 USD |
money_without_currency |
{{ page.total_cents | money_without_currency }} |
43.00 |
Dates are raw datetimes — format them with jigyll's standard date filter
(strftime syntax):
| Filter | Example | Output |
|---|---|---|
date |
{{ page.transaction_date | date: "%b %d, %Y" }} |
Jun 15, 2026 |
The number_template needs no filters: its
context variables (YY, NNNN, …) are already
formatted strings you interpolate directly.
A complete example
A complete, working setup is two files. Download both with the button below, or read the full source inline — the on-page source is loaded from the very files the download bundles, so what you see is exactly what you get.
Preview it locally. Drop both files into any jigyll site (the layout at
_layouts/receipt.html, the render page at the repo root) and render the one
page to stdout — the same command the platform runs:
jigyll render -q -s . dash-receipt-render.html
That prints the exact HTML the receipt PDF is built from, so you can iterate on wording and layout before publishing.
When receipts are issued
- Automatically when a checkout payment completes (the same webhook that
marks the order
completed). No operator action needed. - Manually via the Reissue receipt button on the order detail page in the dashboard. Reissue renders a fresh PDF from the current live-repo config and layout, assigns a new receipt number, supersedes the prior one (the old row is retained in the history), and re-sends the email. Use this to pick up template/wording edits after you publish, or to regenerate a receipt for an order that predates the feature.
Both paths require a published _layouts/receipt.html. Without one, automatic
issuance generates nothing, and the Reissue receipt button tells you to add
the layout first — Stripe's own receipt is what the buyer has in the meantime.
The order detail page also has a Download receipt button for the current PDF.
Source of truth
Receipts are always rendered from the published site — the config (from the
receipt layout's front matter) and the layout itself are read from the live
commit's tree, not from uncommitted edits in your working directory. So to
change receipt wording, edit _layouts/receipt.html, commit, and publish;
new receipts (and reissues) pick up the change.
Pre-feature orders
Orders that completed before this feature was enabled have no receipt. They get their first receipt only when you click Generate receipt on the order detail page. There is no automated backfill — we don't email old customers unsolicited.