docs: dedicated ticket-voucher lookup page under fluent
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>
This commit is contained in:
parent
0b4ad1c99f
commit
718d1515b0
2 changed files with 119 additions and 91 deletions
|
|
@ -147,94 +147,6 @@ member/donor distinction.
|
||||||
|
|
||||||
## Ticket-voucher entitlement
|
## Ticket-voucher entitlement
|
||||||
|
|
||||||
Returns how many **free tickets** a donor has earned from their giving, based on
|
The donor **ticket-voucher lookup** (how many free tickets a donor earned) is a
|
||||||
donations **on/after a cutoff date** (default `2025-09-04` — "9/4 last year").
|
separate endpoint documented on its own page:
|
||||||
|
[`fluentforms-ticket-vouchers.md`](./fluentforms-ticket-vouchers.md).
|
||||||
```
|
|
||||||
GET https://scan.beartariacampgrounds.com/api/public/ticket-vouchers?key=<SECRET>&email=<email>
|
|
||||||
-> {"vouchers": 0} | {"vouchers": 1} | {"vouchers": 2}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules** (donations summed on/after the cutoff):
|
|
||||||
|
|
||||||
| Total since cutoff | Vouchers |
|
|
||||||
|---|---|
|
|
||||||
| ≥ $1000 | 2 |
|
|
||||||
| ≥ $400 | 1 |
|
|
||||||
| otherwise | 0 |
|
|
||||||
|
|
||||||
**Config** (backend `.env`):
|
|
||||||
|
|
||||||
| Var | Default | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `VOUCHER_SINCE` | `2025-09-04` | Only donations on/after this date count. Bump each year. |
|
|
||||||
| `VOUCHER_TIER1_MIN` | `400` | Dollar total for 1 voucher |
|
|
||||||
| `VOUCHER_TIER2_MIN` | `1000` | Dollar total for 2 vouchers |
|
|
||||||
|
|
||||||
> The count is computed from the **dated transaction tables** (online + offline)
|
|
||||||
> — the master-list rollups have no dates. Only `Paid` transactions count.
|
|
||||||
|
|
||||||
### Form snippet
|
|
||||||
|
|
||||||
Displays the voucher count and (optionally) sets a hidden field / caps a
|
|
||||||
quantity. Same pattern as the discount lookup — paste into a Custom HTML element
|
|
||||||
and set `KEY`.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="voucher-status" style="margin:6px 0;font-size:14px;font-weight:600;"></div>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
var API = "https://scan.beartariacampgrounds.com/api/public/ticket-vouchers";
|
|
||||||
var KEY = "REPLACE_WITH_PUBLIC_LOOKUP_SECRET";
|
|
||||||
var EMAIL_SELECTOR = 'input[name="email"]';
|
|
||||||
var statusEl = document.getElementById("voucher-status");
|
|
||||||
var last = "";
|
|
||||||
|
|
||||||
function show(n) {
|
|
||||||
if (n > 0) {
|
|
||||||
statusEl.textContent = "🎟️ You've earned " + n + " free ticket" + (n > 1 ? "s" : "") + "!";
|
|
||||||
statusEl.style.color = "#1b7f3b";
|
|
||||||
} else {
|
|
||||||
statusEl.textContent = "";
|
|
||||||
}
|
|
||||||
// Optional: write n into a hidden field named "free_tickets" for conditional
|
|
||||||
// logic / to cap a quantity. (Uses the native setter so FF's Vue model sees it.)
|
|
||||||
var el = document.querySelector('input[name="free_tickets"]');
|
|
||||||
if (el) {
|
|
||||||
var proto = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(el), "value");
|
|
||||||
(proto && proto.set ? proto.set : function (v) { el.value = v; }).call(el, String(n));
|
|
||||||
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
||||||
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function check(email) {
|
|
||||||
if (!email || email === last) return;
|
|
||||||
last = email;
|
|
||||||
fetch(API + "?key=" + encodeURIComponent(KEY) + "&email=" + encodeURIComponent(email))
|
|
||||||
.then(function (r) { return r.json(); })
|
|
||||||
.then(function (d) { show(d && d.vouchers ? d.vouchers : 0); })
|
|
||||||
.catch(function () { show(0); });
|
|
||||||
}
|
|
||||||
|
|
||||||
function bind() {
|
|
||||||
var el = document.querySelector(EMAIL_SELECTOR);
|
|
||||||
if (!el) { return setTimeout(bind, 500); }
|
|
||||||
el.addEventListener("blur", function () { check(el.value.trim().toLowerCase()); });
|
|
||||||
}
|
|
||||||
bind();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test
|
|
||||||
|
|
||||||
```
|
|
||||||
curl "https://scan.beartariacampgrounds.com/api/public/ticket-vouchers?key=<SECRET>&email=<a-real-donor-email>"
|
|
||||||
# -> {"vouchers":2} (>= $1000 since the cutoff)
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Heads-up on the cutoff:** with `VOUCHER_SINCE=2025-09-04`, everyone currently
|
|
||||||
> returns `0` because the donation data in NocoDB ends **2025-05-22** — there are
|
|
||||||
> no transactions after the cutoff yet. Adjust `VOUCHER_SINCE` (or wait for new
|
|
||||||
> donations to sync) so the window matches real giving.
|
|
||||||
|
|
|
||||||
116
docs/fluentforms-ticket-vouchers.md
Normal file
116
docs/fluentforms-ticket-vouchers.md
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
# FluentForms → ticket-voucher lookup
|
||||||
|
|
||||||
|
Look up, by email, how many **free tickets** a donor has earned from their
|
||||||
|
giving. Intended for the ticket-rewards / checkout form: enter an email, call
|
||||||
|
this endpoint, and show / apply the earned vouchers.
|
||||||
|
|
||||||
|
FluentForms can't query an external database from a field natively, so this is
|
||||||
|
done with a small Custom JS block that calls a secret-gated endpoint on the
|
||||||
|
ticketing backend.
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
GET https://scan.beartariacampgrounds.com/api/public/ticket-vouchers?key=<SECRET>&email=<email>
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns only the count — never names or dollar amounts:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "vouchers": 0 } // or 1, or 2
|
||||||
|
```
|
||||||
|
|
||||||
|
- `key` = the value of `PUBLIC_LOOKUP_SECRET` (set in the backend `.env`).
|
||||||
|
- `email` = the donor's email (URL-encoded).
|
||||||
|
- Rate-limited (30 requests / minute / IP) and CORS-restricted to
|
||||||
|
`PUBLIC_LOOKUP_ORIGIN` (default `https://tickets.beartariacampgrounds.com`).
|
||||||
|
|
||||||
|
> The secret is visible in page source, so treat it as **deterrence, not
|
||||||
|
> security** — it only gates a 0/1/2 count. Rotate it by changing
|
||||||
|
> `PUBLIC_LOOKUP_SECRET` and redeploying.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
Donations are summed for the email across the online + offline transaction
|
||||||
|
tables, counting only **Paid** rows dated **on/after `VOUCHER_SINCE`**:
|
||||||
|
|
||||||
|
| Total since the cutoff | Vouchers |
|
||||||
|
|---|---|
|
||||||
|
| ≥ $1000 | 2 |
|
||||||
|
| ≥ $400 | 1 |
|
||||||
|
| otherwise | 0 |
|
||||||
|
|
||||||
|
Configurable in the backend `.env`:
|
||||||
|
|
||||||
|
| Var | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `VOUCHER_SINCE` | `2025-09-04` | Only donations on/after this date count. Bump each year. |
|
||||||
|
| `VOUCHER_TIER1_MIN` | `400` | Dollar total for 1 voucher |
|
||||||
|
| `VOUCHER_TIER2_MIN` | `1000` | Dollar total for 2 vouchers |
|
||||||
|
|
||||||
|
The count comes from the **dated transaction tables** (the donor master-list
|
||||||
|
rollups have no dates), so donations must exist in those tables for the window.
|
||||||
|
|
||||||
|
## Form snippet
|
||||||
|
|
||||||
|
Add a **Custom HTML** element to the form and paste this, setting `KEY` to your
|
||||||
|
`PUBLIC_LOOKUP_SECRET` (and `EMAIL_SELECTOR` if the email field isn't named
|
||||||
|
`email`). It shows the earned count on email blur and writes it into a hidden
|
||||||
|
field `free_tickets` you can use for conditional logic or to cap a quantity.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div id="voucher-status" style="margin:6px 0;font-size:14px;font-weight:600;"></div>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var API = "https://scan.beartariacampgrounds.com/api/public/ticket-vouchers";
|
||||||
|
var KEY = "REPLACE_WITH_PUBLIC_LOOKUP_SECRET";
|
||||||
|
var EMAIL_SELECTOR = 'input[name="email"]';
|
||||||
|
var statusEl = document.getElementById("voucher-status");
|
||||||
|
var last = "";
|
||||||
|
|
||||||
|
function show(n) {
|
||||||
|
statusEl.textContent = n > 0
|
||||||
|
? "🎟️ You've earned " + n + " free ticket" + (n > 1 ? "s" : "") + "!"
|
||||||
|
: "";
|
||||||
|
statusEl.style.color = "#1b7f3b";
|
||||||
|
// Optional: write the count into a hidden field named "free_tickets".
|
||||||
|
// Uses the native setter so FluentForms' Vue model registers the change.
|
||||||
|
var el = document.querySelector('input[name="free_tickets"]');
|
||||||
|
if (el) {
|
||||||
|
var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(el), "value");
|
||||||
|
(d && d.set ? d.set : function (v) { el.value = v; }).call(el, String(n));
|
||||||
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(email) {
|
||||||
|
if (!email || email === last) return;
|
||||||
|
last = email;
|
||||||
|
fetch(API + "?key=" + encodeURIComponent(KEY) + "&email=" + encodeURIComponent(email))
|
||||||
|
.then(function (r) { return r.json(); })
|
||||||
|
.then(function (d) { show(d && d.vouchers ? d.vouchers : 0); })
|
||||||
|
.catch(function () { show(0); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind() {
|
||||||
|
var el = document.querySelector(EMAIL_SELECTOR);
|
||||||
|
if (!el) { return setTimeout(bind, 500); } // form may render late
|
||||||
|
el.addEventListener("blur", function () { check(el.value.trim().toLowerCase()); });
|
||||||
|
}
|
||||||
|
bind();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```
|
||||||
|
curl "https://scan.beartariacampgrounds.com/api/public/ticket-vouchers?key=<SECRET>&email=<a-real-donor-email>"
|
||||||
|
# >= $1000 since cutoff -> {"vouchers":2}
|
||||||
|
# >= $400 since cutoff -> {"vouchers":1}
|
||||||
|
# otherwise -> {"vouchers":0}
|
||||||
|
```
|
||||||
|
|
||||||
|
Related: [`fluentforms-donor-discount.md`](./fluentforms-donor-discount.md) — the
|
||||||
|
companion donor-discount eligibility lookup (same key / CORS / rate limit).
|
||||||
Loading…
Add table
Add a link
Reference in a new issue