Skip to main content

Stripe Terminal payments for bookings

Trusted applications can collect payment for an existing paid-product booking through a registered Stripe Terminal reader. The integration is server-driven; applications do not need Stripe Terminal connection tokens or the Terminal SDK.

Partner flow

  1. Create or update the patient with the existing patient mutations.
  2. Create the booking with status: pending.
  3. Read groupId from the selected PracticeLocation.
  4. Query paymentTerminalReaders(locationGroupId: ...).
  5. Call startTerminalPaymentForBooking(bookingId: ..., terminalId: ...).
  6. Poll bookingTerminalPaymentStatus(bookingId: ...) every two to three seconds until it returns a terminal state.

Do not call markBookingPaymentProcessing for Terminal payments. The booking remains pending while the reader interaction completes.

Reader discovery

query Readers($locationGroupId: ID!) {
paymentTerminalReaders(locationGroupId: $locationGroupId) {
data {
id
label
status
assignedToLocation
}
error
}
}

Readers assigned to the location group are returned first. When no readers are assigned, the response still includes the practice's other registered readers, matching the fallback used by the Semble application.

Start payment

mutation StartTerminalPayment($bookingId: ID!, $terminalId: ID!) {
startTerminalPaymentForBooking(
bookingId: $bookingId
terminalId: $terminalId
) {
bookingId
paymentIntentId
status
failureMessage
terminal {
id
label
status
assignedToLocation
}
error
}
}

The amount is always calculated by Semble from the product price and tax. The mutation rejects deleted or paid bookings, pathway products, products that do not require payment, unavailable readers, and practices without an active Stripe Terminal integration.

Repeating the mutation after losing the response returns the existing active attempt when it uses the same reader. Select another reader only after canceling or completing the current attempt.

Poll payment status

query TerminalPaymentStatus($bookingId: ID!) {
bookingTerminalPaymentStatus(bookingId: $bookingId) {
bookingId
paymentIntentId
status
failureMessage
error
}
}

WAITING_FOR_CARD and CAPTURING mean the payment is still active. FINALIZING means Stripe or the reader has succeeded but Semble is still processing the Stripe webhook. Only SUCCEEDED confirms that the webhook has marked the booking paid and created its invoice. FAILED, CANCELED, and EXPIRED are terminal outcomes.

This is the same completion rule used by online booking: reader success alone is not authoritative.

Cancel and retry

mutation CancelTerminalPayment($bookingId: ID!) {
cancelTerminalPaymentForBooking(bookingId: $bookingId) {
bookingId
paymentIntentId
status
error
}
}

Cancellation applies only to the reader action and PaymentIntent currently stored on the booking. It does not delete the booking. A canceled or failed attempt can be replaced by calling startTerminalPaymentForBooking again.

Permissions

All four operations require the createBookings permission and are scoped to the authenticated practice.