- src/payments: provider registry and PaymentProvider interface
(createCheckout, verifyWebhook/parseWebhook, getTransaction, refund,
listTransactions as capabilities), Yoco as the first provider. All four
direct Yoco checkout calls go through payments.createCheckout.
- Webhook split into the provider part (signature, parsing) and shared
processing; same URL (/api/webhooks/yoco), same behaviour.
- YocoTransaction renamed in place to ProviderTransaction (+ provider,
unique per provider + externalId); Payment.provider added and
backfilled to "yoco". Payment.method stays the wallet type so cashups
are unchanged.
- Site settings → Payments: Enabled toggle and order per provider; Yoco's
settings shown while it's enabled. R15 minimum for all online payments.
- New endpoint names (/api/payments/checkout, /api/provider-transactions);
the old /yoco-checkout and /yoco-transactions paths still work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sell a set of tickets together for one price (e.g. 5 Adult + 3 Child for R500).
Bundles apply automatically, as many times as a registration's tickets allow, and
only when cheaper than the prices actually charged. Coupons come off afterwards.
- EventBundle/EventBundleItem/RegistrationBundle models and Registration.bundleDiscount
- Supervisor "Bundle deals" page, quote endpoint, bundle lines on invoices,
receipts, emails, WhatsApp and Yoco checkout, Discounts report rows
- Register form shows started bundles with "Complete bundle" and "+" buttons
- Demo: Family Fun Day sells a Family deal bundle instead of the Family pass ticket
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events can allow sponsorships: a "Sponsor a spot" button lets anyone
(guests included) pay through Yoco for whole spots or a custom amount,
anonymously or with a name and message. Payments land as event
donations in a sponsorship pool that supervisors assign with the
existing donation flow. Adds optional public progress towards a goal,
a sponsor receipt and Automations thank-you, pool columns in the
Donations report, and sponsorship labels/filter in Payments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events can take a waitlist for sold-out lines. When places free up
(cancellation, smaller booking, higher stock limit) the next person is
offered them with a claim link and the places are held for a set time;
unclaimed offers expire and move on, with optional partial offers. Stock
is now counted in one place (utils/stock.js), including held places,
and manual registrations and booking edits are now stock-checked. Adds a
supervisor waitlist page, My Events waitlists, and the daily summary count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Youth events need a name and date of birth for every attendee, and a
parent/guardian's details for under-18s. Parents get an email and
WhatsApp link to a consent page that records the signed name, time, IP,
device and exact consent text; medical and emergency details are stored
encrypted. Tickets are issued but check-in is blocked until consent is
given (supervisor override audited). A daily job sends reminders and
purges medical details after the retention period. Adds a supervisor
consent tracker, consent status and medical list reports, and clears
consent data when an account is anonymised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Events can take a name (and optional email/phone) for each main-ticket
place. Each named person gets their own quantity-1 ticket and attendee
form; unnamed places share one ticket. Tickets go to the payer or to each
person, attendees matching one existing account are linked to it, and
the payer can edit names and send form links from My Events. Ticket
generation is now planned per line (utils/attendees.js), free
registrations use the same generator, and guest registration matches
accounts by phone too, refusing conflicting email/phone pairs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Percentage, fixed-amount and comp codes, limited by event, ticket option
or variant, dates, use counts, minimum quantity, and people, roles or an
email domain. The discount is locked in on the registration (mirrored on
Registration.discountTotal so every total sees it), shows on invoices,
receipts, emails and the Yoco checkout, and is released on cancellation.
Use limits are enforced under an advisory lock.
Ticket options and variants can also carry a staff price that Staff and
above get automatically. Adds a Supervisor > Coupon codes page, code
fields on registration, My Events, manual registration, at the door and
the kiosk, a Discounts report, and audit log entries. Also fixes Yoco
line items and the free-registration check for lines spanning several
price tranches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six site improvements picked from a "what could be better" review, plus a Jest
test suite covering the two areas with the trickiest money-handling history
in this project (early-bird pricing tranches, donation-leg accounting):
- "Add to calendar" .ics download on event pages and in confirmation emails
- sitemap.xml, robots.txt, and Open Graph/Twitter metadata for public pages
- Sentry error monitoring (backend + frontend), a no-op until SENTRY_DSN is set
- Nightly local pg_dump backups with a Site Settings tab to browse/trigger/download
- Admin audit trail for refunds, donations, manual registrations, event and
settings changes, and staff-initiated cancellations
- Jest tests reproducing and guarding against the 1.8.0 tranche-pricing bug
and the 1.4.2 donation-balance-inflation bug
Wallet passes (Google/Apple) were scoped out of this round — Apple Wallet
needs a paid Apple Developer account the project doesn't have yet, and the
user preferred shipping both together later rather than Google alone now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contact-only events have no registration flow, so their daily
summary (registration/payment stats) was always empty — exclude
them from the query instead of sending a pointless email.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Registration confirmations attach an invoice PDF (itemized breakdown,
early-bird discount, balance due, Yoco pay-now link/QR) whenever a
balance is outstanding; payment/donation confirmations attach a
payment receipt PDF. Sent as an email attachment and, over WhatsApp,
as the PDF itself with the existing message as its caption.
- Users can also (re)send either document on demand: an "Invoice"
button on the registration detail popup, and a "Receipt" button next
to each payment there and on the Payment history page, each opening
an Email/WhatsApp choice popup, via two new endpoints restricted to
the registration/payment's own owner.
- Fix: editing an event option's early-bird tiers deleted and
recreated every tier for that option with brand-new ids, silently
severing the appliedTierId link on all historical purchases (losing
early-bird attribution and undercounting stock-limit usage) even for
tiers the admin didn't touch. Tiers are now upserted by id.
- Update the "My Events" help content and the API docs index for the
new endpoints.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Registration/payment/reminder emails and WhatsApp messages loaded
registrations without their price tranches, so any line spanning more
than one tranche fell back to charging the full quantity at the most
recent tranche's price, silently dropping the early-bird discount from
the outstanding balance and itemized amounts shown to the user.
The user dashboard's registration detail popup also listed one raw
line per tranche; it now merges same item/price/tier lines and groups
early-bird lines separately from standard-price ones.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Site Settings -> Branding now supports a Primary/Secondary/Accent brand color
system applied site-wide (buttons, nav, hover states, links) and to outgoing
email header/CTA colors, plus a favicon upload alongside the existing logo
upload, a live preview panel (website/email x desktop/mobile), and
logo-based color suggestions. The setup wizard's Branding step got the same
treatment. Fixes two related bugs found along the way: the setup wizard's
logo/favicon upload was missing its auth token, and a static favicon.ico in
Next's special app/ convention path was silently overriding the dynamic one.
Also replaces every "Hope Events"/"Hope Family Church" default (org name,
email subjects, WhatsApp messages, report metadata, API docs) with a neutral
"Cross Code" placeholder, and the optional legal settings (operator name, IO
details, website URL, effective date) with obviously-generic placeholders
instead of defaulting to real personal/organisational details -- since this
platform is deployed for multiple organisations. Adds SETTINGS.md documenting
every setting's default behaviour.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The internal admin notice for applying a donation to a registration
reused buildPaymentAdminNotice as-is, so it read "Payment recorded" /
"Registration payment" — indistinguishable from an actual incoming
payment even though no new money changed hands. Adds a dedicated
buildDonationAssignmentAdminNotice wrapper (mirroring the one already
added for unassignment) so the subject, heading, and Type field all
say "Donation applied" instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assigning a donation to a registration was one-way: the refund flow
could reverse the money but left the donation's "leg" payment in
place, permanently locking that portion of the donation as used even
though it had been refunded back out. Adds POST
/api/payments/unassign-donation, which deletes the leg, reverts the
registration's status/tickets the same way a refund downgrade already
does, and notifies the registrant. New "Assigned donations" list on
the supervisor Payments page surfaces existing legs with an Unassign
action, since no such list existed before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets staff redeem a registration's Main Tickets by quantity at the door
(via the Payment/registration flow) instead of scanning each QR code,
and automatically emails/WhatsApps a check-in confirmation to the
attendee. Also routes the At The Door "Open" button and post-payment
flow dynamically: paid registrations jump straight to Check-In instead
of a forced ticket print, since tickets are already sent automatically.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>