For Website Developers

Branded Receipts

Brand the PDF receipts customers receive after a shop purchase

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.