Payments
Collect payments for bookings using Stripe Connect
Payments
AstroCal supports paid bookings through Stripe Connect. Charge invitees for consultations, coaching sessions, or any bookable event type.
Overview
The payment flow uses Stripe Connect Standard, where your organization connects their own Stripe account. AstroCal creates Payment Intents on behalf of your connected account with a 1% platform fee.
Payment lifecycle:
- Connect your Stripe account via OAuth
- Set a price on an event type
- Invitee creates a booking — receives a
client_secretto complete payment - Invitee completes payment on your frontend using Stripe.js
- Stripe webhook confirms payment — booking moves to
confirmed - If payment times out (30 minutes), booking is automatically cancelled
Connect Stripe
Before accepting payments, connect your Stripe account:
# Initiate OAuth flow — returns a URL to redirect the user to
curl -X GET https://api.astrocal.dev/v1/stripe/connect \
-H "Authorization: Bearer YOUR_API_KEY"
# Response
{
"url": "https://connect.stripe.com/oauth/authorize?..."
}After the user authorizes on Stripe, they're redirected back to your application. Check connection status:
curl -X GET https://api.astrocal.dev/v1/stripe/status \
-H "Authorization: Bearer YOUR_API_KEY"
# Response (connected)
{
"connected": true,
"stripe_account_id": "acct_...",
"connected_at": "2026-02-14T12:00:00.000Z",
"charges_enabled": true,
"payouts_enabled": true
}Set a Price on an Event Type
Add price_amount (in cents) when creating or updating an event type:
# Create a paid event type ($50 consultation)
curl -X POST https://api.astrocal.dev/v1/event-types \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Strategy Consultation",
"slug": "strategy-consultation",
"duration_minutes": 60,
"price_amount": 5000,
"price_currency": "usd"
}'
# Make an event type free again
curl -X PATCH https://api.astrocal.dev/v1/event-types/:id \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "price_amount": null }'price_amountis in the smallest currency unit (cents for USD). Minimum is 100 (i.e., $1.00).price_currencydefaults to"usd". Must be a 3-character ISO currency code.- Event types without
price_amount(or withnull) are free — the booking flow is unchanged.
Create a Paid Booking
When an invitee books a paid event type, the response includes a payment object:
curl -X POST https://api.astrocal.dev/v1/bookings \
-H "Content-Type: application/json" \
-d '{
"event_type_id": "...",
"start_time": "2026-03-05T14:00:00Z",
"invitee_name": "Jane Doe",
"invitee_email": "jane@example.com",
"invitee_timezone": "America/New_York"
}'
# Response for paid booking
{
"id": "...",
"status": "pending_payment",
"start_time": "2026-03-05T14:00:00.000Z",
"end_time": "2026-03-05T15:00:00.000Z",
"payment": {
"amount": 5000,
"currency": "usd",
"client_secret": "pi_xxx_secret_yyy",
"stripe_payment_intent_id": "pi_xxx"
}
}Key differences from free bookings:
- Status is
pending_paymentinstead ofconfirmed paymentfield contains the Stripeclient_secretneeded to complete payment- No calendar event or emails are sent until payment succeeds
- The time slot is held — other invitees cannot book the same slot
Complete Payment on the Frontend
Use the client_secret with Stripe.js to collect payment:
const stripe = Stripe("pk_live_...");
const { error } = await stripe.confirmPayment({
clientSecret: booking.payment.client_secret,
confirmParams: {
return_url: "https://yourapp.com/booking-confirmed",
},
});Handle Webhooks
Set up a Stripe webhook endpoint pointing to your AstroCal instance:
POST https://api.astrocal.dev/v1/stripe/webhooksSubscribe to these events in your Stripe dashboard:
payment_intent.succeeded— confirms the booking, triggers calendar event and emailspayment_intent.payment_failed— cancels the booking
AstroCal verifies the webhook signature using your webhook signing secret (STRIPE_WEBHOOK_SECRET env var).
Payment Timeout
If payment is not completed within 30 minutes of booking creation:
- The Stripe Payment Intent is cancelled
- The booking status changes to
cancelledwith reason "Payment timeout" - The held time slot is released
A background worker checks for timed-out bookings every 5 minutes.
Refund Policy
When a confirmed paid booking is cancelled:
- If the cancellation is 24+ hours before the start time: a full refund is issued automatically
- If the cancellation is less than 24 hours before: no automatic refund (the payment stands)
When a pending_payment booking is cancelled, the Payment Intent is cancelled at Stripe (no charge was made).
Platform Fee
AstroCal charges a 1% platform fee on each payment, with a minimum of $0.50. This is collected via Stripe's application_fee_amount on the Payment Intent.
| Booking Price | Platform Fee |
|---|---|
| $10.00 | $0.50 (minimum) |
| $50.00 | $0.50 |
| $100.00 | $1.00 |
| $500.00 | $5.00 |
Disconnect Stripe
To remove the Stripe connection:
curl -X DELETE https://api.astrocal.dev/v1/stripe/disconnect \
-H "Authorization: Bearer YOUR_API_KEY"This deauthorizes the OAuth connection and removes the stored account ID. Existing paid bookings are not affected, but new paid bookings cannot be created until Stripe is reconnected.
Environment Variables
| Variable | Required | Description |
|---|---|---|
STRIPE_SECRET_KEY | Yes (for payments) | Your Stripe platform secret key |
STRIPE_CONNECT_CLIENT_ID | Yes (for payments) | Stripe Connect OAuth client ID (ca_...) |
STRIPE_WEBHOOK_SECRET | Yes (for payments) | Webhook signing secret (whsec_...) |