10xMeet
All posts

Engineering

The Stripe + paid-bookings pattern for solo SaaS founders

How to charge invitees per meeting via Stripe Checkout without building a billing system. Includes the slot-holding logic and 30-minute cleanup pattern.

May 17, 2026·7 min read·Faz Karim

Paid bookings — where the invitee pays you for the meeting they're scheduling — is one of the most useful and least-implemented patterns in the scheduling-tool space. Calendly hides it behind their $20/mo plan and an add-on. Cal.com makes you build it yourself. SavvyCal doesn't do it.

You can ship it end-to-end in about 200 lines of code using Stripe Checkout. Here's the pattern.

The flow

  • Invitee fills the booking form and clicks Confirm
  • Server creates the booking with status=CONFIRMED, paymentStatus=“unpaid”
  • Server creates a Stripe Checkout session in mode=payment with the booking ID as client_reference_id
  • Server redirects the invitee to session.url
  • Stripe collects payment and redirects back to /booking/confirmed?id=...&paid=1
  • Stripe webhook (checkout.session.completed) flips paymentStatus to “paid”
  • Webhook fires the normal post-booking side effects: confirmation email, ICS attachment, host notification, your own webhooks

The slot-holding question

The interesting design question is: what happens to the slot while the invitee is on the Stripe Checkout page? Two paths:

  • Hold the slot — create the booking as CONFIRMED before redirecting, so nobody else can book it. Risk: if the invitee abandons checkout, the slot is locked forever.
  • Don't hold the slot — only create the booking after payment. Risk: someone else books the slot mid-checkout and Stripe charges the first invitee for a meeting that no longer exists.

Hold the slot. The abandonment risk is solvable; the double-booking risk is not. Solve abandonment with two mechanisms:

  • Listen for checkout.session.expired in your Stripe webhook (fires 24 hours after creation, or immediately on user-cancellation in some cases) and delete the booking when it fires.
  • Run a cron every 10 minutes that deletes any booking with paymentStatus=“unpaid” older than 30 minutes. Belt-and-braces.

The minimum viable Stripe Checkout call

const session = await stripe.checkout.sessions.create({
  mode: "payment",
  customer_email: invitee.email,
  client_reference_id: booking.id,
  line_items: [{
    price_data: {
      currency: eventType.currency,
      product_data: { name: eventType.title },
      unit_amount: eventType.priceCents
    },
    quantity: 1
  }],
  metadata: { bookingId: booking.id, type: "booking_payment" },
  success_url: `${appUrl}/booking/confirmed?id=${booking.id}&paid=1`,
  cancel_url: `${appUrl}/${user.username}`
});

await prisma.booking.update({
  where: { id: booking.id },
  data: {
    paymentStatus: "unpaid",
    stripeCheckoutId: session.id
  }
});

return redirect(session.url);

The webhook handler

case "checkout.session.completed": {
  const session = event.data.object as Stripe.Checkout.Session;
  if (session.mode === "payment" && session.metadata?.type === "booking_payment") {
    const bookingId = session.metadata.bookingId;
    const booking = await prisma.booking.update({
      where: { id: bookingId },
      data: { paymentStatus: "paid", stripeCheckoutId: session.id },
      include: { eventType: true, user: true }
    });
    await Promise.allSettled([
      sendBookingConfirmation(booking),
      sendHostBookingNotification(booking),
      dispatchBookingEvent("booking.created", booking)
    ]);
  }
  break;
}

Things to skip

  • Don't build a refund flow. Stripe's dashboard handles it. If you cancel a paid booking, decide your policy (refund or not) and refund manually in Stripe.
  • Don't build receipts. Stripe sends a receipt email by default.
  • Don't take a platform fee on top. Charging customers extra to use the “paid bookings” feature when Stripe already takes 2.9% is greedy and competitively bad.
The biggest unlock for solo consultants and coaches is the 2-minute setup to start charging for their time. Don't bury it behind an enterprise tier.
The Stripe + paid-bookings pattern for solo SaaS founders · 10xMeet