Skip to content

Booking & Payments ​

Complete Booking Flow ​

The booking flow involves the frontend (myeasyguide.com), the API (api.myeasyguide.com), and Stripe working together:

┌─────────────┐     ┌──────────────┐     ┌────────┐
│  Frontend   │     │  API         │     │ Stripe │
└──────┬──────┘     └──────┬───────┘     └───┬────┘
       │                   │                  │
       │ 1. Browse         │                  │
       │  activity         │                  │
       │                   │                  │
       │ 2. View detail    │                  │
       │←─────────────────│                  │
       │                   │                  │
       │ 3. Click          │                  │
       │ "Book Now"        │                  │
       │                   │                  │
       │ 4. POST /bookings │                  │
       │──────────────────▶│                  │
       │                   │                  │
       │ 5. Booking        │                  │
       │ created           │                  │
       │ (PENDING_PAYMENT) │                  │
       │◀──────────────────│                  │
       │                   │                  │
       │ 6. POST /stripe/  │                  │
       │ create-checkout   │                  │
       │──────────────────▶│                  │
       │                   │ 7. Create        │
       │                   │ Checkout Session │
       │                   │─────────────────▶│
       │                   │                  │
       │                   │ 8. Session URL   │
       │                   │◀─────────────────│
       │ 9. Session URL    │                  │
       │◀──────────────────│                  │
       │                   │                  │
       │ 10. Redirect user │                  │
       │ to Stripe         │                  │
       │─────────────────────────────────────▶│
       │                   │                  │
       │                   │  11. User pays   │
       │                   │                  │
       │                   │ 12. Webhook:     │
       │                   │ checkout.session │
       │                   │ .completed       │
       │                   │◀─────────────────│
       │                   │                  │
       │                   │ 13. Transaction: │
       │                   │ • Booking →      │
       │                   │   CONFIRMED      │
       │                   │ • Payment →      │
       │                   │   SUCCESS        │
       │                   │ • bookingsCount+ │
       │                   │                  │
       │ 14. Redirect to   │                  │
       │ /success          │                  │

Key Implementation Details ​

Price Calculation ​

The booking's totalPrice is calculated when the booking is created:

totalPrice = activity.price × groupSize × days

Prices are stored as Float in the database. The Stripe Checkout session uses this calculated price.

Atomic Payment Processing ​

The Stripe webhook handler uses a Prisma transaction to ensure all database operations succeed or fail together:

typescript
await prisma.$transaction([
  prisma.booking.update({
    where: { id: bookingId },
    data: { status: "CONFIRMED" },
  }),
  prisma.payment.create({
    data: {
      bookingId,
      amount,
      currency: "USD",
      status: "SUCCESS",
      provider: "STRIPE",
      stripePaymentIntentId,
    },
  }),
  prisma.activity.update({
    where: { id: activityId },
    data: { bookingsCount: { increment: 1 } },
  }),
]);

Stripe Webhook Security ​

The webhook endpoint:

  1. Extracts the Stripe signature from the stripe-signature header
  2. Verifies it using STRIPE_WHSEC_KEY via stripe.webhooks.constructEvent()
  3. Only processes checkout.session.completed events
  4. Idempotency: checks if the booking already has a payment before creating one

Booking Statuses ​

StatusDescriptionNext Statuses
PENDING_PAYMENTCreated, awaiting paymentCONFIRMED, CANCELLED
CONFIRMEDPayment received, booking activeCANCELLED
CANCELLEDCanceled by user or admin—
FAILEDPayment failedPENDING_PAYMENT (retry)

Refunds ​

The API does not implement automated refunds. Refund requests must be processed manually through the Stripe Dashboard. The booking status should be updated to CANCELLED after manual refund processing.

Stripe Integration Details ​

Checkout Session ​

The Stripe Checkout Session is configured with:

typescript
const session = await stripe.checkout.sessions.create({
  mode: "payment",
  line_items: [{
    price_data: {
      currency: "usd",
      product_data: { name: activity.title },
      unit_amount: Math.round(totalPrice * 100), // cents
    },
    quantity: 1,
  }],
  metadata: { bookingId: booking.id.toString() },
  success_url: `${FRONTEND_BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${FRONTEND_BASE_URL}/cancel`,
});

Testing ​

Test the booking flow locally:

  1. Set Stripe keys to test mode (sk_test_...)
  2. Start Stripe webhook forwarding:
    bash
    stripe listen --forward-to localhost:3000/api/v1/stripe/webhook
  3. Use Stripe test card: 4242 4242 4242 4242 (any future expiry, any CVC)
  4. Check the Stripe Dashboard for test payments

Built with VitePress