Skip to main content

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 the createPayment right.
  • refundPayment — refunds a specific card payment on an invoice back to the original card. Requires the issueRefund right.

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 amount to 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 error and the invoice is left unpaid — fall back to sending the patient a payment link.
  • The optional comment is 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:

  • paymentId is the Invoice.payments[].id — the same value payInvoice returns.
  • amount can be a partial refund; it must be greater than 0 and no more than the payment's remaining unrefunded amount.
  • reason is 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 comment is 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 payInvoice with the invoiceId and optionally amount and comment. Omit amount to charge the full outstanding balance. On success, store the returned paymentId if you may later need to refund it. On error, do not retry blindly — treat it as "no card on file could be charged" and fall back to a payment link.
  • Refunding: Call refundPayment with invoiceId, the paymentId from payInvoice, the amount, and a reason. Use the payment's remaining unrefunded amount for a full refund, or a smaller value for a partial refund.
  • Reconciliation: Persist paymentChargeId / refundExternalId to 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).