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
- Create or update the patient with the existing patient mutations.
- Create the booking with
status: pending. - Read
groupIdfrom the selectedPracticeLocation. - Query
paymentTerminalReaders(locationGroupId: ...). - Call
startTerminalPaymentForBooking(bookingId: ..., terminalId: ...). - 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.