nopSolutions/nopCommerce · error · NopException

You can't partially refund (offline) this order

Error message

You can't partially refund (offline) this order

What it means

Thrown by OrderProcessingService.PartiallyRefundOfflineAsync when CanPartiallyRefundOffline(order, amountToRefund) returns false. An offline partial refund is allowed only when OrderTotal > 0, a refundable remainder exists (OrderTotal - RefundedAmount > 0), amountToRefund does not exceed the remainder, and PaymentStatus is Paid or PartiallyRefunded. Unlike the online version, no payment-method capability check is performed because the gateway is bypassed.

Source

Thrown at src/Libraries/Nop.Services/Orders/OrderProcessingService.cs:2943

        if (order.PaymentStatus == PaymentStatus.Paid ||
            order.PaymentStatus == PaymentStatus.PartiallyRefunded)
            return true;

        return false;
    }

    /// <summary>
    /// Partially refunds an order (offline)
    /// </summary>
    /// <param name="order">Order</param>
    /// <param name="amountToRefund">Amount to refund</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public virtual async Task PartiallyRefundOfflineAsync(Order order, decimal amountToRefund)
    {
        ArgumentNullException.ThrowIfNull(order);

        if (!CanPartiallyRefundOffline(order, amountToRefund))
            throw new NopException("You can't partially refund (offline) this order");

        //total amount refunded
        var totalAmountRefunded = order.RefundedAmount + amountToRefund;

        //update order info
        order.RefundedAmount = totalAmountRefunded;
        //mark payment status as 'Refunded' if the order total amount is fully refunded
        order.PaymentStatus = order.OrderTotal == totalAmountRefunded ? PaymentStatus.Refunded : PaymentStatus.PartiallyRefunded;
        await _orderService.UpdateOrderAsync(order);

        //add a note
        await AddOrderNoteAsync(order, $"Order has been marked as partially refunded. Amount = {amountToRefund}");

        //raise event       
        await _eventPublisher.PublishAsync(new OrderRefundedEvent(order, amountToRefund));

        //check order status
        await CheckOrderStatusAsync(order);

View on GitHub (pinned to 64bdf2ff08)

Solutions

  1. Guard with: if (!_orderProcessingService.CanPartiallyRefundOffline(order, amountToRefund)) return; and report the reason.
  2. Clamp amountToRefund = Math.Min(amountToRefund, order.OrderTotal - order.RefundedAmount).
  3. Confirm PaymentStatus is Paid or PartiallyRefunded before enabling the action.
  4. For zero-total or fully-refunded orders, no partial refund is possible.

Example fix

// before
await _orderProcessingService.PartiallyRefundOfflineAsync(order, amount);

// after
var remaining = order.OrderTotal - order.RefundedAmount;
amount = Math.Min(amount, remaining);
if (!_orderProcessingService.CanPartiallyRefundOffline(order, amount))
    return;

await _orderProcessingService.PartiallyRefundOfflineAsync(order, amount);
Defensive patterns

Strategy: validation

Validate before calling

var remaining = order.OrderTotal - order.RefundedAmount;
amountToRefund = Math.Min(amountToRefund, remaining);
if (!_orderProcessingService.CanPartiallyRefundOffline(order, amountToRefund))
    return;

await _orderProcessingService.PartiallyRefundOfflineAsync(order, amountToRefund);

Prevention

When it happens

Trigger: Calling PartiallyRefundOfflineAsync when: amountToRefund > OrderTotal - RefundedAmount; OrderTotal == 0; remainder <= 0; or PaymentStatus not Paid/PartiallyRefunded.

Common situations: Amount exceeds remaining refundable total; order already fully refunded; order not Paid (Pending/Authorized); staff using offline partial refund on an order with no refundable remainder.

Related errors


AI-assisted analysis of nopSolutions/nopCommerce@64bdf2ff08 (2026-08-13). Data as JSON: /api/errors/dfa83d592c476ad1. Report an issue: GitHub.