Card payments on the public API
Summary
You can now take and refund card payments programmatically, using cards a patient already has on file. Two new mutations are available:
payInvoice— charges a patient's stored card to pay an invoice. Requires thecreatePaymentright.refundPayment— refunds a specific card payment on an invoice back to the original card. Requires theissueRefundright.
This release is additive: existing invoice queries and mutations are unchanged.
payInvoice
Pays an invoice from the patient's stored card token(s). You provide the invoice and optionally an amount:
- Omit
amountto charge the full outstanding balance, or pass a specific value for a partial payment (must be greater than 0 and no more than the outstanding balance). - The patient is resolved from the invoice — you do not pass a card id.
- Semble attempts each of the patient's stored cards in turn until one succeeds, so you do not choose or pass a card.
- Charges that would require additional cardholder authentication (3D Secure) are declined and skipped. If no stored card can be charged, the mutation returns an
errorand the invoice is left unpaid — fall back to sending the patient a payment link. - The optional
commentis stored as the payment's comment on the invoice (max 500 characters).
PayInvoicePayload returns the updated invoice plus paymentId (the Invoice.payments[].id you pass to refundPayment) and paymentChargeId (the Stripe charge id, for reconciliation).
refundPayment
Refunds one specific payment recorded on an invoice, back to the card it was taken from:
paymentIdis theInvoice.payments[].id— the same valuepayInvoicereturns.amountcan be a partial refund; it must be greater than 0 and no more than the payment's remaining unrefunded amount.reasonis required (RefundReason:DUPLICATE,FRAUDULENT,REQUESTED_BY_CUSTOMER,OTHER).- Only payments taken through Stripe can be refunded via the API; refunding a non-card payment returns an error. No credit note is created by this mutation.
- The optional
commentis stored on the refund (max 500 characters).
RefundPaymentPayload returns the updated invoice plus refundId (the Invoice.refunds[].id) and refundExternalId (the Stripe refund id, for reconciliation).
Integration guidance
- Taking payment: Call
payInvoicewith theinvoiceIdand optionallyamountandcomment. Omitamountto charge the full outstanding balance. On success, store the returnedpaymentIdif you may later need to refund it. Onerror, do not retry blindly — treat it as "no card on file could be charged" and fall back to a payment link. - Refunding: Call
refundPaymentwithinvoiceId, thepaymentIdfrompayInvoice, theamount, and areason. Use the payment's remaining unrefunded amount for a full refund, or a smaller value for a partial refund. - Reconciliation: Persist
paymentChargeId/refundExternalIdto match Semble activity against your Stripe records.
Example
mutation PayPartialInvoice {
payInvoice(invoiceId: "INVOICE_ID", amount: 50.0, comment: "Deposit") {
data {
id
outstanding
}
paymentId
paymentChargeId
error
}
}
mutation PayNoShowInvoice {
payInvoice(invoiceId: "INVOICE_ID", comment: "No show fee") {
data {
id
outstanding
}
paymentId
paymentChargeId
error
}
}
mutation RefundCardPayment {
refundPayment(
invoiceId: "INVOICE_ID"
paymentId: "PAYMENT_ID"
amount: 50.0
reason: REQUESTED_BY_CUSTOMER
comment: "Goodwill refund"
) {
data {
id
refunded
}
refundId
refundExternalId
error
}
}
Migration
No breaking schema changes. These mutations are new; existing integrations are unaffected. Terminal (in-person reader) payments are out of scope. Omitting amount on payInvoice charges the full outstanding balance (existing behaviour).