nopSolutions/nopCommerce · error · NopException

Capture ID not set

Error message

Capture ID not set

What it means

Thrown by RefundAsync when nopOrder.CaptureTransactionId is null or empty. A refund targets a specific captured payment, so PayPal requires the capture resource id; without it the CreateRefundRequest.CaptureId cannot be set. This guard fires before any API call and is surfaced as the Error string.

Solutions

  1. Confirm the order was actually captured and that order.CaptureTransactionId is populated.
  2. Guard with !string.IsNullOrEmpty(nopOrder.CaptureTransactionId) before calling RefundAsync.
  3. If the order is Authorize-only, void it (VoidAsync) instead of refunding.

Example fix

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

// after
if (string.IsNullOrEmpty(order.CaptureTransactionId))
{
    NotifyError("Order has no captured payment to refund");
    return;
}
var (refund, err) = await _serviceManager.RefundAsync(settings, order, amount);
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(order.CaptureTransactionId))
    return; // no capture to refund

Type guard

static string CaptureId(NopOrder o) =>
    string.IsNullOrEmpty(o?.CaptureTransactionId) ? null : o.CaptureTransactionId;

Try / catch

var (refund, error) = await mgr.RefundAsync(settings, order, amount);
if (!string.IsNullOrEmpty(error))
    NotifyError(error);

Prevention

When it happens

Trigger: Refunding an order that was authorized but never captured; the capture step ran but failed to persist CaptureTransactionId on the order; attempting to refund a manually-created or Authorize-only order.

Common situations: Payment mode set to Authorize (not Capture), so there is no capture to refund; capture failed mid-flow and the order was left without a capture id; order was placed through a different payment method and CaptureTransactionId was never set.

Related errors


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

Appendix: source

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

    /// <param name="nopOrder">Order</param>
    /// <param name="amount">Amount to refund; pass null to refund the full captured amount</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the refund details; error message if exists
    /// </returns>
    public async Task<(Refund Refund, string Error)> RefundAsync(PayPalCommerceSettings settings, NopOrder nopOrder, decimal? amount = null)
    {
        return await HandleFunctionAsync(async () =>
        {
            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

View on GitHub (pinned to 64bdf2ff08)