nopSolutions/nopCommerce · error · NopException

Card details not found

Error message

Card details not found

What it means

Thrown in CreateOrderAsync when a saved payment token (card) exists for the provided cardId but belongs to a different customer. This is a security check preventing one customer from using another's saved payment method.

Source

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

    /// A task that represents the asynchronous operation
    /// The task result contains the created order; error message if exists
    /// </returns>
    public async Task<(Order Order, string Error)> CreateOrderAsync(PayPalCommerceSettings settings,
        ButtonPlacement placement, string paymentSource, int? cardId, bool saveCard)
    {
        return await HandleFunctionAsync(async () =>
        {
            if (!IsConfigured(settings))
                throw new NopException("Plugin not configured");

            if (string.IsNullOrEmpty(settings.MerchantId))
                throw new NopException("Merchant PayPal ID not set");

            var details = await PrepareCartDetailsAsync(placement);

            var savedPaymentToken = await _tokenService.GetByIdAsync(cardId ?? 0);
            if (savedPaymentToken is not null && savedPaymentToken.CustomerId != details.Customer.Id)
                throw new NopException("Card details not found");

            var isGuest = await _customerService.IsGuestAsync(details.Customer);
            var isRecurring = await _shoppingCartService.ShoppingCartIsRecurringAsync(details.Cart);
            if (isRecurring)
            {
                if (!settings.UseVault)
                    throw new NopException("Vault disabled");

                if (isGuest)
                    throw new NopException("Anonymous checkout disabled for recurring items");

                var (error, cycleLength, cyclePeriod, totalCycles) = await _shoppingCartService.GetRecurringCycleInfoAsync(details.Cart);
                if (!string.IsNullOrEmpty(error))
                    throw new NopException(error);
            }

            var paymentRequest = await _orderProcessingService.GetProcessPaymentRequestAsync();
            var (order, _) = await GetCreatedOrderAsync(settings, paymentRequest, placement, details.ShippingIsRequired, paymentSource);

View on GitHub (pinned to 64bdf2ff08)

Solutions

  1. Ensure the cardId passed to CreateOrderAsync comes from the current customer's list of saved cards only
  2. Validate that the selected card belongs to the authenticated customer in the UI before submission
  3. Check for authentication state changes between card selection and order creation
  4. Audit card selection logic for any path that could pass another customer's cardId

Example fix

// before
var savedPaymentToken = await _tokenService.GetByIdAsync(cardId ?? 0);
if (savedPaymentToken is not null && savedPaymentToken.CustomerId != details.Customer.Id)
    throw new NopException("Card details not found");

// after
var savedPaymentToken = cardId.HasValue
    ? (await _tokenService.GetByCustomerIdAsync(details.Customer.Id))
        .FirstOrDefault(t => t.Id == cardId.Value)
    : null;
// Token is already scoped to the customer — no cross-customer access possible
Defensive patterns

Strategy: validation

Validate before calling

// Scope card lookup to the current customer to prevent cross-customer access
if (cardId.HasValue)
{
    var customerTokens = await _tokenService.GetByCustomerIdAsync(details.Customer.Id);
    var validToken = customerTokens.FirstOrDefault(t => t.Id == cardId.Value);
    if (validToken is null)
        return Error("Selected card not found");
}

Type guard

static bool TokenBelongsToCustomer(PaymentToken token, int customerId)
    => token is not null && token.CustomerId == customerId;

Try / catch

var (order, error) = await manager.CreateOrderAsync(settings, placement, paymentSource, cardId, saveCard);
if (!string.IsNullOrEmpty(error) && error == "Card details not found")
    return BadRequest("Invalid card selection");

Prevention

When it happens

Trigger: cardId is provided, _tokenService.GetByIdAsync returns a non-null token, but savedPaymentToken.CustomerId does not match details.Customer.Id.

Common situations: cardId was tampered with in the client request to reference another customer's card; session or authentication changed between card selection and order creation (e.g., logout/login); a bug in card selection UI passes a stale or wrong cardId; concurrent sessions under different accounts sharing a browser.

Related errors


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