nopSolutions/nopCommerce · warning · NopException

The refund was cancelled

Error message

The refund was cancelled

What it means

Thrown by RefundAsync after a successful PayPal refund API call when the returned refund.Status equals CANCELLED. This is PayPal's own status indicating the refund was cancelled (e.g. due to a dispute reversal or PayPal action), not a local config problem. It surfaces through HandleFunctionAsync as the Error string, so the refund id is not persisted.

Solutions

  1. Treat the (null refund, error) result as 'not refunded' and inform the merchant the refund was cancelled.
  2. Check the PayPal dashboard / dispute state for the capture to understand why it was cancelled.
  3. Retry the refund only after confirming the underlying cancellation/dispute is resolved.

Example fix

// before
var (refund, err) = await _serviceManager.RefundAsync(settings, order, amount);

// after
var (refund, err) = await _serviceManager.RefundAsync(settings, order, amount);
if (!string.IsNullOrEmpty(err))
{
    _logger.Warning($"Refund for order {order.Id} did not complete: {err}");
    // surface to admin; do NOT mark the order as refunded
    return;
}
if (refund.Status?.ToUpper() == RefundStatusType.CANCELLED.ToString())
    NotifyAdmin("PayPal cancelled the refund; review the dispute/capture");
Defensive patterns

Strategy: fallback

Try / catch

var (refund, error) = await mgr.RefundAsync(settings, order, amount);
if (!string.IsNullOrEmpty(error))
{
    _logger.Warning($"Refund not completed for order {order.Id}: {error}");
    // leave order un-refunded; let staff review the dispute/capture
    return;
}

Prevention

When it happens

Trigger: PayPal returns a refund resource with status CANCELLED; the refund is initiated but then cancelled by PayPal or through a dispute process before completing.

Common situations: Buyer dispute causes PayPal to cancel the refund; PayPal manually reverses a refund; rare intermediary status on bank-funded refunds.

Related errors


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

Appendix: source

Thrown at src/Plugins/Nop.Plugin.Payments.PayPalCommerce/Services/PayPalCommerceServiceManager.cs:2576

            if (!IsConfigured(settings))
                throw new NopException("Plugin not configured");

            var currencyCode = (await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId))?.CurrencyCode;
            if (string.IsNullOrEmpty(currencyCode))
                throw new NopException("Primary store currency not set");

            if (string.IsNullOrEmpty(nopOrder.CaptureTransactionId))
                throw new NopException("Capture ID not set");

            var request = new CreateRefundRequest
            {
                CaptureId = nopOrder.CaptureTransactionId,
                Amount = amount.HasValue ? PrepareMoney(amount.Value, currencyCode) : null
            };
            var refund = await _httpClient.RequestAsync<CreateRefundRequest, CreateRefundResponse>(request, settings);

            if (refund.Status?.ToUpper() == RefundStatusType.CANCELLED.ToString())
                throw new NopException("The refund was cancelled");

            if (refund.Status?.ToUpper() == RefundStatusType.FAILED.ToString())
                throw new NopException("The refund could not be processed");

            if (refund.Status?.ToUpper() == RefundStatusType.PENDING.ToString())
                throw new NopException($"Capture is in {refund.Status} status due to {refund.StatusDetails?.Reason}");

            //save id to avoid double refund
            var refundIds = await _genericAttributeService
                .GetAttributeAsync<List<string>>(nopOrder, PayPalCommerceDefaults.RefundIdAttributeName)
                ?? new();
            if (!refundIds.Contains(refund.Id))
                refundIds.Add(refund.Id);
            await _genericAttributeService.SaveAttributeAsync(nopOrder, PayPalCommerceDefaults.RefundIdAttributeName, refundIds);

            return refund;
        });
    }

View on GitHub (pinned to 64bdf2ff08)