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.

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)

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.