The ticket-vouchers lookup previously returned the tier entitlement
every time, so a donor could keep claiming free tickets by re-
submitting the form. It now subtracts vouchers already consumed:
remaining = entitled - used
where `used` is the sum of the Vouchers column across that donor's
prior ticket orders (each checkout stores what it applied). Response
gains entitled/used/remaining; `vouchers` is now the remaining count
the form should grant. Consumption is implicit — no counter to keep in
sync — and resets by zeroing/deleting the Vouchers value on the order
row in NocoDB.
- nocodb: findByEmail + vouchersUsedByEmail (case-insensitive).
- 8 new tests (36 total). Doc updated with the new response + reset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the ticket-voucher API into its own standalone doc
(docs/fluentforms-ticket-vouchers.md) — endpoint, 0/1/2 rules, config, form
snippet, and curl test — so it's easy to find and hand off for the lookup. The
donor-discount doc now links to it instead of duplicating the section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/public/ticket-vouchers?key=&email= returns 0/1/2 free tickets based on
donations on/after VOUCHER_SINCE (default 2025-09-04): >= $400 -> 1, >= $1000 -> 2.
Same secret/CORS/rate-limit as donor-eligibility; returns only the count. Cutoff
and thresholds are env-configurable. Documented both lookup APIs (discount +
vouchers) in docs/fluentforms-donor-discount.md with form snippets.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Critical: NocoDB v2 PATCH /records with no primary key updates EVERY row, so a
table missing its Id column made each redeem rewrite all tickets' counts.
- nocodb update() now refuses to PATCH a record with no Id (fail-safe).
- Document that the tickets/audit tables must have an Id PK; note how to add it.
- Replace the real donor email/name used in the /test persona and the
FluentForms guide with fake placeholders (donor@example.test / <a-real-donor-email>).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hidden donor_tier field (regular/donor/member) driven by the email lookup;
payment options shown via FF conditional logic. Uses the native value setter +
input/change dispatch so FF's Vue model registers the programmatic change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/public/donor-eligibility?key=&email= returns only {eligible, tier}
(member/donor) — no names or dollar amounts — gated by PUBLIC_LOOKUP_SECRET,
rate-limited (30/min), and CORS-restricted to PUBLIC_LOOKUP_ORIGIN. Lets the
FluentForms checkout unlock a donor discount by email. Docs + ready-to-paste
form snippet in docs/fluentforms-donor-discount.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>