Payments
Collect event fees with Stripe or PayPal, manage pricing tiers and add-ons, and handle refunds.
Quick outcome
The payment system supports per-attendee and flat-fee pricing with optional add-ons. Attendees pay through hosted Stripe or PayPal checkout. Organizers monitor payments, process refunds, and export records from the admin dashboard.
Setup
Configure payment methods
In /admin/settings, set the enabled payment methods:
| Method | Requirements |
|---|---|
| Stripe | STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in environment or per-event settings |
| PayPal | paypalClientId, paypalClientSecret, paypalWebhookId in event settings |
Per-event payment credentials can override the global environment variables, allowing different events to use different Stripe/PayPal accounts.
First-time setup?
See Stripe (Payments) and PayPal (Payments) for account creation, API key generation, and webhook configuration.
Set up pricing tiers
Navigate to /admin/pricing-tiers to define attendee types:
| Field | Example | Description |
|---|---|---|
| Name | "Adult (18+)" | Display name in RSVP and payment forms |
| Multiplier | 1.0 | Price multiplier (1.0 = full, 0.5 = half, 0 = free) |
Common setup:
- Adult: multiplier
1.0 - Child (6-17): multiplier
0.5 - Infant (0-5): multiplier
0
Create add-ons
Navigate to /admin/add-ons to define billable items:
| Field | Description |
|---|---|
| Name | Display name (e.g., "Accommodation", "Activity Fee") |
| Description | What the add-on covers |
| Amount (cents) | Base price in cents (e.g., 15000 for $150.00) |
| Currency | Currency code (default: usd) |
| Optional | If true, attendees can choose whether to include it |
| Per attendee | If true, charged per person; if false, flat fee per RSVP |
| Tier prices | Optional explicit price per tier (overrides multiplier calculation) |
Per-attendee pricing example: A $100 "Activity Fee" add-on with per-attendee enabled. An RSVP with 2 adults (1.0x) and 1 child (0.5x) pays: $100 + $100 + $50 = $250.
Flat-fee example: A $200 "Cabin Rental" add-on with per-attendee disabled. Each RSVP pays $200 regardless of party size.
Tier prices override: If the Activity Fee has explicit tier prices ({ "adult": 10000, "child": 3000 }), those amounts are used instead of the multiplier calculation.
Day-to-day management
Payment dashboard
Open /admin/payments to see all payment records:
| Column | Description |
|---|---|
| Payer | Email and name |
| Amount | Payment amount |
| Status | pending, completed, failed, refunded, partial_refund |
| Method | stripe, paypal, or manual |
| Date | Payment creation timestamp |
Recording manual payments
For cash, check, or other offline payments, create manual payment records with:
- Payer details
- Amount
- Optional note describing the payment method
Processing refunds
From a completed payment record:
- Click the refund action
- Enter the refund amount (full or partial)
- Provide a refund reason
- Confirm — the refund is processed through Stripe/PayPal
Refund status progresses: completed → refunding → refunded (or partial_refund).
Exporting payments
Export payment data for accounting or reconciliation. The export includes all payment fields, statuses, and amounts.
Attendee experience
Payment page
Attendees visit /pay to see available expense cards:
- Each add-on is shown as a card with name, description, and price
- Optional add-ons can be selected/deselected
- Required add-ons are always included
- The total updates based on selections and attendee count
Checkout flow
- Attendee selects items and clicks "Pay"
- Redirected to Stripe hosted checkout (or PayPal)
- Enters payment details on the secure checkout page
- On success → redirected to
/pay/success - On cancel → redirected to
/pay/cancel
Webhook reconciliation
After checkout, Stripe sends a webhook to /api/stripe/webhook:
- Webhook verifies the signature using
SubtleCryptoProvider - Payment status updated from
pendingtocompleted - Payment intent and session IDs recorded
TIP
If a payment shows as completed in Stripe but still pending in the admin, the webhook likely failed. Check the Stripe dashboard for webhook delivery history.
Troubleshooting
| Symptom | Fix |
|---|---|
| Checkout doesn't open | Verify Stripe credentials in settings or environment |
| Success page but admin shows pending | Webhook not delivered — check Stripe webhook dashboard |
| Wrong total | Verify tier multipliers, explicit tier prices, and add-on amounts |
| Refund blocked | Check payment intent state in Stripe dashboard |
| Manual payment not appearing | Confirm the record was saved with correct event ID |
| Export incomplete | Check date range and status filters |
Next steps
- RSVPs — manage attendee registrations linked to payments
- Custom Fields — add questions to the RSVP/payment flow
- Environment Variables — Stripe and PayPal configuration