What a donor gets, when
| Donation type | Email receipt | PDF download |
|---|---|---|
| One-time, donor is a Haven member | ✅ Immediately after payment | ✅ Anytime from /member/donations |
| One-time, donor is not a member | ✅ Immediately after payment | ❌ (Stripe also sends a Stripe-branded receipt) |
| Recurring, each cycle | ✅ Immediately after each charge | ✅ Per-cycle PDFs from member portal |
A donation that completes a Stripe Checkout session generates exactly one Haven receipt — even if Stripe re-delivers the webhook. The send is keyed to
payment_records.receipt_sent_at, which is set to the timestamp of the first successful Resend delivery.Customize the receipt template
From the Donations → Form tab, scroll to Receipt email:- Subject line — defaults to “Thank you for your donation”. Personalize freely.
- Body — supports merge tags listed below. Leave blank to use Haven’s IRS Pub 1771-compliant default template, which covers the required elements (org name + address, date, amount, deductible portion, statement that no goods/services were provided beyond what’s described).
Merge tags
The body text can include any of these placeholders. Each renders as the donor’s actual data when the receipt is generated:| Tag | What it renders |
|---|---|
{{donor_name}} | Donor’s name (their member name if they’re a member; otherwise the name they entered on the form) |
{{amount}} | Total donation amount, formatted as $50.00 |
{{designation}} | Name of the fund the donor selected (or “the General Fund” when no designation) |
{{date}} | Donation date, formatted as April 25, 2026 |
{{deductible_amount}} | Tax-deductible portion (= amount unless you’ve recorded fair-market-value goods provided in exchange) |
{{premium_description}} | Auto-rendered explanation when FMV > 0 (otherwise empty) |
{{receipt_number}} | Sequential per-org receipt number |
{{org_name}} | Your organization’s name |
{{org_address}} | Your organization’s address (multi-line, joined with line breaks) |
{{tax_id}} | Currently empty — EIN field on organizations is on the roadmap |
{{unknown_tag}} in donor emails.
Receipt numbering
Every donation gets a sequential per-org receipt number, starting at 1 for your organization’s first donation and incrementing by 1 thereafter. Numbers are unique within your organization and never re-used, which gives you a clean audit trail. The numbering is enforced by a unique database index. Under heavy concurrent load (Giving Tuesday, year-end campaigns), if two donations race for the same number, one of them retries with the next number. The donor never sees the retry — the receipt that arrives in their inbox always has a unique number.On-demand PDF download
When a member donor visits/member/donations, the donation history table includes a PDF button per row. Clicking it opens the receipt as a print-ready PDF in a new tab.
The PDF is generated on demand each time, so it always reflects the current state of the donation record (including any admin edits to the org’s address or template). Receipts are never cached or pre-rendered.
The PDF layout includes:
- Your organization’s name + address + contact info as the header (your branding accent color is the underline)
- A meta grid showing receipt number / date / amount
- The same merge-tag-substituted body as the email
Annual consolidated receipts (planned)
The Send consolidated annual receipts toggle in the Form tab reserves the schema for this feature, but the year-end cron job that generates a single donor-by-donor summary delivered in early January isn’t shipped yet. For now, donors who want a year-end summary can download each donation’s PDF individually from their portal.What admins see
Under Donations → Donations, each row in the table shows the receipt number assigned to that donation. Clicking a row opens a drilldown sheet with the full payment record — useful when responding to “I never got my receipt” donor questions:- Check
receipt_sent_at(visible as the “Receipt sent” timestamp in the drilldown). If it’s set, Haven did send the email — the donor’s inbox or spam filter is probably the culprit. - Resending an individual receipt is on the roadmap (the server already supports it; the button isn’t wired into the drilldown yet).
Refunds processed in the Stripe dashboard automatically update the corresponding
payment_records row to status='refunded' via the webhook. The receipt email already sent isn’t recalled — for accounting purposes you’d issue a refund acknowledgment from your own records.