feat(me): my-payments self-view in the Membership tab (P3d)

New getMyPayments: payments SELECT is treasurer-scoped (RLS), so the
self-view uses the validated service client strictly scoped to the
caller's space_id + member_id (same convention as getMyBilling /
getMyNotifications). Read-only list (amount, date, platform, status)
in the Membership tab. Docs updated. 521 tests; build clean.
This commit is contained in:
Moheeb Zara
2026-05-18 15:50:34 -07:00
parent 6189e2023d
commit 2c847befeb
4 changed files with 72 additions and 1 deletions
+1
View File
@@ -967,3 +967,4 @@ Outbox + dispatcher; the webhook never sends inline. Pure render + dedupe in `li
- The Stripe webhook enqueues into `notifications` (`ignoreDuplicates` on the `(space_id, dedupe_key)` unique index, so an event replay is a no-op): `invoice.paid` → `dues_renewed`, `invoice.payment_failed` → `dues_payment_failed`, transition to `late` (in `applySubscription`) → `dues_lapsed`. Recipient + space name resolved server-side; never sends inline.
- Dispatcher `POST /api/cron/notifications` (nodejs, force-dynamic; constant-time `CRON_SECRET` bearer; 503 if unset, 401 on mismatch; `proxy.ts` whitelists `/api/cron`). Drains ≤20 oldest `pending` email rows with `attempts < MAX`, sends with the row id as Resend `Idempotency-Key` (overlap-safe), ~4.5/s spacing. Success → `sent`+`sent_at`; failure → `attempts++`, `pending` if retryable and budget remains else `failed`. Returns `{ scanned, sent, failed, retried }`. Hit once a minute by the droplet crontab.
- `getMyNotifications()` — the caller's own last 15 notifications (service client; `notifications` is admin-RLS, mirrors `getMyBilling`). Surfaced read-only on `/me`.
- `getMyPayments()` — the caller's own last 50 payments (service client; `payments` SELECT is treasurer-scoped, so the self-view is strictly scoped to the caller's `space_id` + `member_id`, same convention as `getMyBilling`). Surfaced read-only in the `/me` Membership tab.