Skip to content

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:

MethodRequirements
StripeSTRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in environment or per-event settings
PayPalpaypalClientId, 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:

FieldExampleDescription
Name"Adult (18+)"Display name in RSVP and payment forms
Multiplier1.0Price 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:

FieldDescription
NameDisplay name (e.g., "Accommodation", "Activity Fee")
DescriptionWhat the add-on covers
Amount (cents)Base price in cents (e.g., 15000 for $150.00)
CurrencyCurrency code (default: usd)
OptionalIf true, attendees can choose whether to include it
Per attendeeIf true, charged per person; if false, flat fee per RSVP
Tier pricesOptional 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:

ColumnDescription
PayerEmail and name
AmountPayment amount
Statuspending, completed, failed, refunded, partial_refund
Methodstripe, paypal, or manual
DatePayment 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:

  1. Click the refund action
  2. Enter the refund amount (full or partial)
  3. Provide a refund reason
  4. Confirm — the refund is processed through Stripe/PayPal

Refund status progresses: completedrefundingrefunded (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

  1. Attendee selects items and clicks "Pay"
  2. Redirected to Stripe hosted checkout (or PayPal)
  3. Enters payment details on the secure checkout page
  4. On success → redirected to /pay/success
  5. On cancel → redirected to /pay/cancel

Webhook reconciliation

After checkout, Stripe sends a webhook to /api/stripe/webhook:

  1. Webhook verifies the signature using SubtleCryptoProvider
  2. Payment status updated from pending to completed
  3. 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

SymptomFix
Checkout doesn't openVerify Stripe credentials in settings or environment
Success page but admin shows pendingWebhook not delivered — check Stripe webhook dashboard
Wrong totalVerify tier multipliers, explicit tier prices, and add-on amounts
Refund blockedCheck payment intent state in Stripe dashboard
Manual payment not appearingConfirm the record was saved with correct event ID
Export incompleteCheck date range and status filters

Next steps

Released under the MIT License.