Skip to main content

Record document-only refunds via refundPayment

Summary

refundPayment now records refunds for non-card payments as well as Stripe card payments. Requires the issueRefund right.

This release is additive: existing Stripe refund behaviour is unchanged. No new mutation is introduced.

Behaviour by payment source

  • Stripe card payments — same as before: refunds money back to the original card via Stripe, returns refundExternalId for reconciliation.
  • Non-card payments (cash, cheque, bank transfer, payment-on-account, and similar) — records a refund document on the invoice only. No money is moved and no credit note is created. The practice handles the offline refund. refundExternalId is not set.
  • Payments that still have a Stripe paymentChargeId (including legacy rows whose paymentSource is e.g. UNKNOWN) — refunded via Stripe so funds return to the card.
  • Terminal payments — still out of scope; the mutation returns an error.

Args are unchanged: invoiceId, paymentId, amount, reason (RefundReason), optional comment (max 500 characters). Partial refunds are supported.

Integration guidance

  • Stripe: Continue calling refundPayment as today; store refundExternalId if you reconcile with Stripe.
  • Non-card: Call the same refundPayment mutation with the payment's Invoice.payments[].id. Treat success as “refund recorded”; do not expect a PSP refund id.
  • Querying: Use existing Invoice.refunds fields to read refund history.
  • Deprecated nested input: NewInvoicePaymentDataInput.refunds remains deprecated; use refundPayment.

Example

mutation RefundCashPayment {
refundPayment(
invoiceId: "INVOICE_ID"
paymentId: "PAYMENT_ID"
amount: 50.0
reason: REQUESTED_BY_CUSTOMER
comment: "Cash refund at reception"
) {
data {
id
refunded
refunds {
id
refundAmount
refundReason
relatedPayment
}
}
refundId
refundExternalId
error
}
}

Migration

No breaking schema changes. Existing Stripe integrations keep working. Terminal (in-person reader) payments remain out of scope for public API refunds.