Skip to main content

refundOrder

Refund the captured payment of an order, in full or in part. refundOrder is the service behind the Refund button on the order page and the POST /api/orders/:id/refunds endpoint. It works for every payment method that registered a refund handler.

Core validates the request, calls the handler (the one step that talks to the payment provider), then hands the result to recordRefund, which saves the refund, sets the payment status and emits order_refunded.

Import​

import { refundOrder } from "@evershop/evershop/oms/services";

Syntax​

refundOrder(uuid: string, amount: number): Promise<RefundOrderResult>

interface RefundOrderResult {
status: string;
isFullRefund: boolean;
}

Parameters​

uuid

Type: string

The order uuid (order.uuid), not the numeric order_id.

amount

Type: number

The amount to refund, in the order currency's major units (49.99, not 4999). It must be greater than zero and no more than what is still refundable: the captured amount minus the refunds already recorded. Amounts are compared in the currency's smallest unit, so rounding noise cannot slip a cent through.

Return Value​

Returns Promise<RefundOrderResult>:

  • status — the new payment status: <payment_method>_refunded when the refunds reach the captured amount, otherwise <payment_method>_partial_refunded.
  • isFullRefund — true when the refunds recorded so far, including this one, reach the captured amount.

Behavior​

  1. Loads the order by uuid.
  2. Looks up the payment method's registered refund handler.
  3. Checks that the order's payment status is flagged isRefundable.
  4. Finds the captured payment: the newest payment transaction that is not a refund.
  5. Checks the amount against what remains refundable.
  6. Calls the handler with { order, transaction, amount, currency }, where transaction is the capture. This call runs outside any database transaction.
  7. Calls recordRefund with what the handler returned. Core uses the handler's amount, not the requested one.

Errors​

refundOrder throws a plain Error. The REST endpoint returns the message in an HTTP 500 response.

MessageCause
Order <uuid> not foundNo order has this uuid.
Order <uuid> has no payment methodThe order has no payment method recorded.
Payment method "<code>" does not support refundsThe method has no refund handler, or it is not registered.
Order <uuid> is not refundable in its current status (<status>)The payment status is not flagged isRefundable.
Order <uuid> has no captured payment to refundThe order has no non-refund payment_transaction row.
Refund amount must be greater than 0 and at most the remaining captured amountThe amount is zero, negative, not a number, or more than what is left.
The handler's errorPassed through unchanged, for example PayPal refund failed (status ...).
Errors from recordRefundSee recordRefund, for example Invalid status when the method did not register its refund statuses.

Notes​

  • Not idempotent per call. Each valid call makes a new refund at the provider. Only the provider's own webhook echo is deduplicated, because it carries the same refund id.
  • Emits order_refunded through recordRefund. refundOrder itself is not wrapped in hookable.
  • If the provider's webhook reaches recordRefund first, the refund is already recorded when refundOrder gets there. recordRefund then reports alreadyRecorded, and refundOrder returns the order's current payment status with isFullRefund: false.
  • The supportsPartialRefund flag on a payment method is not checked.

Examples​

import { refundOrder } from "@evershop/evershop/oms/services";

const { status, isFullRefund } = await refundOrder(order.uuid, 25.5);
// status: 'stripe_partial_refunded', isFullRefund: false

An offline method has no provider to call. Cash On Delivery's handler only reports the amount:

refund: async ({ order, amount }) => ({
transactionId: `cod-refund-${order.uuid}-${Date.now()}`,
amount,
offline: true
})

See Also​