Skip to content

Settlement mails drop on-chain evidence — receipt shows only latest txid; issuer paid notice shows none #87

@n8bar

Description

@n8bar

What

Settlement mails are accounting documents — they should carry the on-chain evidence that supports the "paid" claim. Both currently fall short for multi-payment invoices.

Client receipt (InvoicePaidReceiptMail)

`resources/views/mail/invoice-paid.blade.php` renders a single `$invoice->txid` in the body. Per `Invoice::refreshPaymentLedger` (`app/Models/Invoice.php` lines 476–487), `$invoice->txid` stores only the latest confirmed payment's txid:

```php
$selectedPayment = $latestConfirmed ?? $latestDetected;

$this->txid = $selectedPayment->txid;
```

So an invoice paid via 3 partial transactions ships a receipt listing only the third txid. The other two confirmed payments that contributed to the settlement are silently dropped from the receipt body.

Issuer paid notice (`InvoiceIssuerPaidNoticeMail`)

`resources/views/mail/invoice-issuer-paid.blade.php` includes no txid at all. The issuer's accounting evidence is just `Client / Amount / Paid at` — they have to click through to the invoice page to find any txids.

Where

  • `resources/views/mail/invoice-paid.blade.php` (client receipt body)
  • `resources/views/mail/invoice-issuer-paid.blade.php` (issuer paid notice body)
  • Both Mailables already pass `$invoice` to the view; payment data is available via `$invoice->payments` / `$invoice->activePayments()` etc.

Fix direction

Iterate confirmed payments contributing to settlement and render a list, one row per payment, in both templates:

  • txid (full, monospace, with word-break: break-all for narrow viewports — same pattern the current receipt uses for its single txid)
  • sats received + fiat amount at detection (the locked-at-detection-time values, per spec §5.12.4 / PARTIAL_PAYMENTS.md:73-74)
  • Optional: confirmed at timestamp per payment

For single-payment invoices the list renders one row — same readability as today. For multi-payment invoices it surfaces the full settlement history.

Filter to confirmed, non-ignored payments only ($invoice->payments()->whereNotNull('confirmed_at')->whereNull('ignored_at') or via activeOnChainPayments()).

Test plan

  • Feature test: create an invoice with 2+ confirmed payments contributing to a paid settlement; render InvoicePaidReceiptMail and InvoiceIssuerPaidNoticeMail; assert every payment's txid appears in the rendered body.
  • Single-payment invoice still renders cleanly (one row, no awkward "1 of 1" framing).
  • Re-fire both mails against an existing §5.x invoice with the new templates; eyeball QA confirms the list reads as an accounting record, not noise.

Tracked in

docs/strategies/19.1_NOTIFICATION_COVERAGE_AUDIT.md §5.3 (settlement-mail verification).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:notificationsOutbound mail, alerts, delivery surfacebugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions