How-to guide
How to bulk-generate receipts from a CSV
Produce payment receipt PDFs from transaction exports for customer emails, archives, or support follow-up.
What you'll need
Supplies
- CSV of completed payments
- Receipt numbering rules
- Customer email or archive destination
Tools
- DocForge
- Payment export
- Spreadsheet app
Steps
- 1
Export completed transactions
Export only transactions that are settled enough for a receipt. One row should equal one receipt, with headers like `receipt_number,customer_name,transaction_date,payment_method,item_summary,amount_paid,tax,total,served_by`. Exclude failed, voided, or still-pending payments unless your process has a separate provisional receipt template. Keep refund and reversal rows separate; a negative `amount_paid` inside the same receipt export is easy to miss and can create a document that looks like a normal payment confirmation. Normalize `transaction_date` to ISO format such as `2026-06-15`, then display it with stock Liquid like `{{ row.transaction_date | date: '%B %-d, %Y' }}`. Payment methods should be customer-readable: use `Visa ending 4242` instead of an internal processor code like `card_pm_92A`. For tax-sensitive receipts, do not rely on a generic `amount` column. Split `amount_paid`, `tax`, and `total` so support can answer questions without reopening the transaction system.
- 2
Review receipt language
Review fixed template copy before looking at styling. A receipt should confirm payment, not imply a new invoice is due. Use labels such as `Amount paid`, `Payment date`, and `Receipt number`; avoid `Balance due` unless the receipt intentionally covers a partial payment. Map visible fields to concrete CSV headers like `receipt_number`, `customer_name`, `transaction_date`, `payment_method`, `item_summary`, and `total`. If refunds may be referenced later, add a deliberate optional field such as `refund_reference` and print it only when present: `{% if row.refund_reference != blank %}Refund reference: {{ row.refund_reference }}{% endif %}`. Use `{{ row.served_by | default: 'Support Team' }}` if older exports do not include an owner. Preview a receipt for `Jane Rivera` with `total` of `1250.00` and a long `item_summary`; the customer should understand what was paid without reading internal codes or transaction metadata.
- 3
Preview support scenarios
Choose preview rows that mirror the questions support actually receives. Include a card payment, bank transfer, high-value payment, refund-adjacent payment, and a row with a long item description such as `2026 annual subscription renewal plus onboarding workshop`. Check that `receipt_number`, `transaction_date`, `payment_method`, `amount_paid`, `tax`, and `total` are visible without hunting. If `payment_method` is blank on imported bank payments, decide whether to fix the export or use a Liquid fallback like `{{ row.payment_method | default: 'Payment method on file' }}`. Do not hide missing data that finance considers required. Watch for totals with commas, such as `1,250.00`, because some CSV exports split poorly when values are not quoted. Open the source CSV and confirm the amount stayed in `total`, not a shifted neighbor column. The PDF should help support answer "what did I pay for?" in one pass.
- 4
Generate and route the PDFs
Generate the batch after the sample receipts match the transaction export. Name the run for the payment window, for example `receipts_2026-06-01_to_2026-06-15`, so archive searches are practical later. Keep row-level failures visible and fix only the affected rows. Common failures include duplicate `receipt_number`, blank `customer_name`, invalid `transaction_date`, or an `item_summary` pasted with unexpected line breaks. Line breaks are acceptable when intentional, but preview one receipt with a multi-line summary before sending it to customers. When routing PDFs to email or storage, use stable identifiers such as `receipt_number` and `customer_name`; a file named `RCPT-2026-1842_Jane-Rivera.pdf` is easier to reconcile than `row-1842.pdf`. Before delivery, spot-check a zero-tax receipt and a taxable receipt. The receipt should show `tax` only when it is meaningful, using a conditional like `{% if row.tax != blank and row.tax != '0.00' %}...{% endif %}`.