Running a campaign

Compliance & security

Canadian political fundraising has rules. TesterDonate gives your team tools that enforce the ones a platform can — caps, eligibility gates, contribution-time records — at the database, the API, and the UI. Compliance responsibility stays with your campaign and its official agent; our job is making that job easier.

Canadian compliance

Per-jurisdiction contribution caps

Configurable per page. The donation intent route aggregates the donor's year-to-date giving to your tenant and rejects any donation that would push them over the applicable cap. Monthly subscriptions are checked against the 12-month projection so a $250/month sign-up can't silently exceed a yearly limit.

Corporate-donation rules per jurisdiction

Some jurisdictions permit corporate donations, some prohibit them outright. Configure each page with its jurisdiction's rules and the platform enforces them — the donor form shows or hides the "Corporate" donor-type option accordingly, and the API rejects mismatched submissions server-side.

Citizenship attestation

Per-page configuration. When required, the donate form gates submission until the donor checks the attestation; the API double-checks server-side. Attestation flag persists on the donor row and the donation row for audit.

Contribution records + exports

Every donation snapshots the donor's details as they were at contribution time — name, address, employer, attestation — so your records reflect what the regulator audits. Donors get an automatic confirmation email (your official agent still issues official receipts through your usual process), and CSV exports carry the full contribution-time record for preparing your filings.

Money handling

Stripe Connect — your account, your money

Each tenant connects their own Stripe account via OAuth. Donations settle directly to the tenant's balance; the platform takes its fee via Stripe's native application-fee mechanism. No escrow, no 14-day holds, no platform-aggregator headaches.

Application-fee transparency

The platform fee is computed on the donor-charged amount (including the cover-fees uplift if any) and itemized on the Stripe balance. Treasurers reconcile cleanly.

Disputes + refunds

Webhook-driven status updates so the dispute, refund, and net- contribution math stay accurate in the admin UI without manual intervention. The legally-defensible "net contribution" is computed as gross − succeeded refunds − lost disputes.

Stripe Link disabled by design

Link auto-fills cards across merchants, which would undermine the donor-of-record provenance Elections Canada requires. We force Stripe's PaymentElement to omit Link from the options.

Security & data isolation

Postgres row-level security

Every tenant-scoped table has RLS policies that gate reads and writes by tenant_id = current_user_tenant_id(). Even a bug in our UI can't leak data across tenants — the database itself refuses to return the rows.

Per-tenant verified sending domains

Resend-verified DKIM + SPF per tenant. Account confirmations, receipts, and admin invites all send from the tenant's branded domain. Falls back to the platform domain only when a tenant's domain isn't yet verified.

Rate limiting

Upstash Redis-backed sliding-window limiters on auth callbacks, checkout intents, password resets, receipt re-sends. Donor-scoped for the donor portal so a shared-wifi roommate can't eat another donor's receipt-resend allowance.

IP hashing

Donor IP addresses are HMAC-hashed with a peppered key before storage. We can still detect abuse patterns; we can't hand out IPs to anyone (including ourselves) without the pepper.

Auth & sessions

Supabase Auth with custom email hook

Account signup, magic links, password resets, email change — all go through Supabase Auth but the email itself is rendered + sent by TesterDonate via Resend with the tenant's branding.

Anti-email-scanner confirmation

Confirmation URLs land on a click-to-continue page. The actual token verification happens on POST when a human clicks. Email scanners (Microsoft Defender, Gmail safe-browsing) GET but don't POST, so they can't burn single-use tokens before donors arrive.

Sentry error monitoring

Production errors stream to Sentry with tenant context, user context, and route metadata. Issues that affect money — Stripe failures, RLS rejections — escalate.