Skip to content

Add downloadable receipt for paid bulk payments - #2011

Open
maebeale wants to merge 3 commits into
mainfrom
maebeale/bulk-payment-receipt
Open

Add downloadable receipt for paid bulk payments#2011
maebeale wants to merge 3 commits into
mainfrom
maebeale/bulk-payment-receipt

Conversation

@maebeale

@maebeale maebeale commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

🤖 suggested review level: 5 Inspect 🔬 payer-facing receipt + a security fix (invoice was reachable by enumerable id) + a Rails/blazer security bump

What & why

  • Receipt for paid bulk payments. Once a payment is on file, payers get a downloadable receipt from the ticket's Documents section (locked card until then). Billed to the connected Payment's payer (respecting payer_type) — not the form submitter, who can differ once an admin records the payment against another person/org.
  • A bulk payment has no owed-balance/paid-in-full concept, so the shared receipt document (EventReceipt#settles_balance?) shows just "Amount paid" + "Payment received" for bulk, keeping the "Total charged / Balance due / Paid in full" reconciliation for registration receipts.

Invoice access hardening

  • The bulk invoice was served by InvoicesController#show via submission_id, with FormSubmissionPolicy#show_invoice? returning true for any bulk payment — so a payer's invoice (names/emails/org/attendees) was reachable by guessing a sequential id, weaker than the slug-gated ticket/receipt.
  • Moved it to slug-based BulkPaymentFormSubmissions#invoice, authorized like ticket/receipt (admin? || slug matches). All three payer documents now live on the payer-facing controller (mirrors RegistrationsController). InvoicesController keeps only the admin blank template. Ticket/dashboard/submission links updated to the slug route.

Structure (rebased onto main's controller split)

  • Payer-facing ticket + invoice + receipt live on BulkPaymentFormSubmissionsController, slug-authorized via FormSubmissionPolicy. AllocationLedgerLabel refactored to label a Payment directly so invoice/receipt stay in sync.

CI

  • Included a conservative bump (rails 8.1.3→8.1.3.1, blazer 3.3.0→3.5.0) to clear repo-wide bundler-audit advisories published 2026-07-29 (activestorage CVE-2026-66066, blazer stored XSS) — unrelated to the feature; kept as its own commit.

Tests

  • Presenter, request (invoice/receipt/ticket), model predicate, and FormSubmissionPolicy#invoice?/#receipt? specs. RuboCop clean.

Copilot AI review requested due to automatic review settings July 22, 2026 13:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale
maebeale force-pushed the maebeale/bulk-payment-receipt branch from 84961a2 to 318399f Compare July 22, 2026 13:41
Copilot AI review requested due to automatic review settings July 22, 2026 13:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale
maebeale marked this pull request as ready for review July 22, 2026 13:58
Copilot AI review requested due to automatic review settings July 22, 2026 14:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 22, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale
maebeale requested a review from jmilljr24 July 25, 2026 12:32

@jmilljr24 jmilljr24 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The receipt doesn't reflect who paid, it's showing who submitted the form. I don't know how critical it is, but I could see someone getting their receipt with wrong payer info and wanted it fixed.

Do we actually need a name/org on the receipt? Invoice makes sense but the receipt is completely transactional. This amount was paid to awbw for xyz. But I don't know if some business (payer) needs so see their name on a receipt.
Image

Comment thread app/controllers/events/bulk_payments_controller.rb Outdated
@jmilljr24

Copy link
Copy Markdown
Collaborator

Also, being that the receipt is generated based on the event details, if a payment is made when an event is $1500 the receipt reflects that. If the event cost were to change (unlikely but out of principle) then receipt would then be updated. Shouldn't a receipt be static and captured at the time of payment?

Copilot AI review requested due to automatic review settings July 29, 2026 11:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread app/presenters/event_receipt.rb Outdated
payer_name = [ answers["payer_first_name"], answers["payer_last_name"] ]
.map(&:presence).compact.join(" ").presence || payer&.full_name
quantity = [ submission.bulk_payment_attendee_count, 1 ].max
amount_paid_cents = payment&.amount_cents.to_i

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmilljr24 the receipt is based on amount paid, not amount of event cost. is this what you were concerned about wrt the comment re it being snapshot in time vs dynamic as event cost changes?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! Thanks

@maebeale

Copy link
Copy Markdown
Collaborator Author

@jmilljr24 yes, i do think the org should be on the receipt

Copilot AI review requested due to automatic review settings July 29, 2026 12:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale

Copy link
Copy Markdown
Collaborator Author

🤖 From Claude: Fixed in 93eb27d — the receipt now bills the connected payment’s payer (respecting payer_type) instead of the form submitter: the org when an org paid, the person when a person paid, with their address/email. A receipt only exists once a payment is on file, so the payer is always present. Per Mae’s call we’re keeping the payer/org on the receipt, just sourced from the payment now.

@maebeale
maebeale requested review from Copilot and jmilljr24 July 29, 2026 12:23
@maebeale
maebeale force-pushed the maebeale/bulk-payment-receipt branch from 93eb27d to 171fd44 Compare July 30, 2026 01:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 30, 2026 02:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 30, 2026 02:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 30, 2026 02:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

maebeale and others added 3 commits July 29, 2026 22:38
Bulk payers had only an invoice. Once a payment is on file they now get a
receipt (proof money changed hands) from the ticket's Documents section, and a
locked card until then. The receipt bills whoever the connected Payment records
as the payer (respecting payer_type) — not the form submitter, who can differ
once an admin records the payment against a different person/organization.

A bulk payment has no owed-balance/paid-in-full concept, so the shared receipt
document (EventReceipt#settles_balance?) shows just "Amount paid" + "Payment
received" for bulk payments, keeping the "Total charged / Balance due / Paid in
full" reconciliation for registration receipts.

Rebased onto main's split of the bulk payment controller: the payer-facing
receipt action lives on BulkPaymentFormSubmissionsController and authorizes via
FormSubmissionPolicy#receipt? (slug context), matching #ticket.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bulk invoice was served by InvoicesController#show via submission_id, with
FormSubmissionPolicy#show_invoice? returning true for any bulk payment — so any
payer's invoice (names, emails, org, attendees) was reachable by guessing a
sequential id, weaker than the slug-gated ticket/receipt for the same record.

Move it to a slug-based BulkPaymentFormSubmissions#invoice, authorized like
#ticket/#receipt (admin? || slug matches), colocating all three payer documents
on the payer-facing controller and mirroring RegistrationsController. Update the
ticket, dashboard card, and submission-page links to the slug route.
InvoicesController now serves only the admin blank template.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The payer invoice route is slug-only, but a submission whose role becomes
bulk_payment after creation (or legacy records) can have a nil slug, so the
admin form-submission page and dashboard card raised UrlGenerationError. Only
render the invoice link when a slug is present — slugless legacy records have no
payer-facing pages anyway.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maebeale
maebeale force-pushed the maebeale/bulk-payment-receipt branch from 6f80e0a to c107a79 Compare July 30, 2026 02:40
Copilot AI review requested due to automatic review settings July 30, 2026 02:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants