API

Stripe Payments

Stripe payment endpoints for booking payments, subscription intents, and webhook handling.

Base path: /api/v1/stripe


How Stripe Is Used in Booki

Stripe handles two types of payments:

FlowWhoEndpoint
Booking paymentCustomer paying for a bookingPOST /stripe/bookings/:id/payment-intent
Subscription paymentOwner registering and subscribingPOST /stripe/create-subscription-intent

The frontend uses the returned clientSecret with Stripe.js to complete payment on the client side, then calls the verify endpoint to confirm.


POST /api/v1/stripe/bookings/:id/payment-intent — Create Booking Payment Intent

Auth: Protected (Authorization: Bearer <accessToken>)
Used by: customer-booki-web-app · Role: customer
organizationId: From JWT token

Description: Create a Stripe PaymentIntent for a specific booking. Returns a clientSecret used by Stripe.js on the frontend to collect and confirm the payment.

Request

POST /api/v1/stripe/bookings/507f1f77bcf86cd799439041/payment-intent

No request body required. The booking ID is taken from the path parameter.

Response (201 Created)

{
  "clientSecret": "pi_3NxABC2eZvKYlo2C1ABC1234_secret_...",
  "paymentIntentId": "pi_3NxABC2eZvKYlo2C1ABC1234"
}

Error Responses

401 Unauthorized — No valid token:

{
  "statusCode": 401,
  "message": "Access token is required to proceed."
}

404 Not Found — Booking not found:

{
  "statusCode": 404,
  "message": "Booking not found."
}

cURL Example

curl -X POST "http://localhost:4001/api/v1/stripe/bookings/507f1f77bcf86cd799439041/payment-intent" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGc..."

POST /api/v1/stripe/bookings/:id/verify-payment — Verify Booking Payment

Auth: Protected (Authorization: Bearer <accessToken>)
Used by: customer-booki-web-app · Role: customer
organizationId: From JWT token

Description: After the customer completes payment via Stripe.js, call this endpoint to confirm the payment succeeded and update the booking status.

Request

{
  "paymentIntentId": "pi_3NxABC2eZvKYlo2C1ABC1234"
}

Fields:

  • paymentIntentId (string, required): The PaymentIntent ID returned from the payment intent step

Response (200 OK)

{
  "message": "Payment verified successfully.",
  "bookingId": "507f1f77bcf86cd799439041"
}

Error Responses

400 Bad Request — Payment not completed:

{
  "statusCode": 400,
  "message": "Payment has not been completed yet."
}

cURL Example

curl -X POST "http://localhost:4001/api/v1/stripe/bookings/507f1f77bcf86cd799439041/verify-payment" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGc..." \
  -d '{"paymentIntentId": "pi_3NxABC2eZvKYlo2C1ABC1234"}'

POST /api/v1/stripe/create-subscription-intent — Create Subscription Payment Intent

Auth: Public (no authentication required)
Used by: cms-booki-web-app · Role: guest/owner (during onboarding)

Description: Create a Stripe PaymentIntent for an organization subscription. Called during owner registration before the account is created. Returns a clientSecret for Stripe.js.

Request

{
  "email": "owner@example.com",
  "pricePerBranch": 999,
  "branchCount": 1,
  "interval": "monthly",
  "trialDays": 90,
  "registrationData": {}
}

Fields:

  • email (string, required): Owner email address
  • pricePerBranch (number, required): Price per branch in PHP (min 0)
  • branchCount (number, required): Number of branches (min 1)
  • interval (string, optional, default: monthly): monthly | annually
  • trialDays (number, optional, default: 90): Trial period in days
  • registrationData (object, optional): Additional data to carry through the registration flow

Response (201 Created)

{
  "clientSecret": "pi_3NxABC2eZvKYlo2C1ABC1234_secret_...",
  "paymentIntentId": "pi_3NxABC2eZvKYlo2C1ABC1234"
}

cURL Example

curl -X POST "http://localhost:4001/api/v1/stripe/create-subscription-intent" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "owner@example.com",
    "pricePerBranch": 999,
    "branchCount": 1,
    "interval": "monthly"
  }'

POST /api/v1/stripe/verify-subscription — Verify Subscription Payment

Auth: Public (no authentication required)
Used by: cms-booki-web-app · Role: guest/owner (during onboarding)

Description: After the owner completes subscription payment via Stripe.js, call this to confirm the payment and finalize the subscription setup.

Request

{
  "paymentIntentId": "pi_3NxABC2eZvKYlo2C1ABC1234"
}

Fields:

  • paymentIntentId (string, required): The PaymentIntent ID from the subscription intent step

Response (200 OK)

{
  "message": "Subscription payment verified successfully."
}

cURL Example

curl -X POST "http://localhost:4001/api/v1/stripe/verify-subscription" \
  -H "Content-Type: application/json" \
  -d '{"paymentIntentId": "pi_3NxABC2eZvKYlo2C1ABC1234"}'

POST /api/v1/stripe/webhook — Stripe Webhook Receiver

Auth: Public (Stripe signature verification)
Used by: Stripe payment gateway (server-to-server)

Description: Receives webhook events from Stripe (e.g., payment_intent.succeeded, payment_intent.payment_failed). This endpoint is called by Stripe's servers, not by the frontend.

Important: Stripe sends raw request bodies. This route is registered before express.json() in app.ts so the raw body is preserved for signature verification.

Request

POST /api/v1/stripe/webhook
Headers:
  stripe-signature: t=1700000000,v1=abc123...
{
  "id": "evt_3NxABC2eZvKYlo2C0001",
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_3NxABC2eZvKYlo2C1ABC1234",
      "amount": 45000,
      "status": "succeeded"
    }
  }
}

Response (200 OK)

{
  "received": true
}

Note: This endpoint is for Stripe's internal use. Do not call it from the frontend.