{"record":{"id":"d65cf28a7684f1af","repo":"nopSolutions/nopCommerce","slug":"payment-token-not-found","errorCode":null,"errorMessage":"Payment token not found","messagePattern":"Payment token not found","errorType":"exception","errorClass":"NopException","httpStatus":null,"severity":"error","filePath":"src/Plugins/Nop.Plugin.Payments.PayPalCommerce/Services/PayPalCommerceServiceManager.cs","lineNumber":2826,"sourceCode":"    /// </summary>\n    /// <param name=\"settings\">Plugin settings</param>\n    /// <param name=\"paymentRequest\">Payment request</param>\n    /// <returns>\n    /// A task that represents the asynchronous operation\n    /// The task result contains the created order; error message if exists\n    /// </returns>\n    public async Task<(Order Order, string Error)> ProcessNextRecurringPaymentAsync(PayPalCommerceSettings settings,\n        ProcessPaymentRequest paymentRequest)\n    {\n        return await HandleFunctionAsync(async () =>\n        {\n            if (!IsConfigured(settings))\n                throw new NopException(\"Plugin not configured\");\n\n            var tokenId = await _genericAttributeService\n                .GetAttributeAsync<int>(paymentRequest.InitialOrder, PayPalCommerceDefaults.TokenIdAttributeName);\n            if (await _tokenService.GetByIdAsync(tokenId) is not PayPalToken token || token.CustomerId != paymentRequest.CustomerId)\n                throw new NopException(\"Payment token not found\");\n\n            var currencyCode = (await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId))?.CurrencyCode;\n            if (string.IsNullOrEmpty(currencyCode))\n                throw new NopException(\"Primary store currency not set\");\n\n            //prepare purchase unit\n            var store = await _storeService.GetStoreByIdAsync(paymentRequest.StoreId);\n            var orderGuid = paymentRequest.OrderGuid.ToString();\n            var money = PrepareMoney(paymentRequest.OrderTotal, currencyCode);\n            var purchaseUnit = new PurchaseUnit\n            {\n                CustomId = CommonHelper.EnsureMaximumLength(orderGuid, 127),\n                InvoiceId = CommonHelper.EnsureMaximumLength(orderGuid, 127),\n                Description = CommonHelper.EnsureMaximumLength($\"Purchase at '{store.Name}'\", 127),\n                SoftDescriptor = CommonHelper.EnsureMaximumLength(store.Name, 22),\n                Payee = new() { MerchantId = settings.MerchantId },\n                Amount = new() { Value = money.Value, CurrencyCode = money.CurrencyCode }\n            };","sourceCodeStart":2808,"sourceCodeEnd":2844,"githubUrl":"https://github.com/nopSolutions/nopCommerce/blob/64bdf2ff08c8b39e65717bcf974fb43dc2ef68f2/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/Services/PayPalCommerceServiceManager.cs#L2808-L2844","documentation":"Thrown inside ProcessNextRecurringPaymentAsync when the PayPal vault token referenced by the initial order's generic attribute either does not exist or does not belong to the customer initiating the recurring payment. The token ID is retrieved from the InitialOrder via generic attributes, then looked up through ITokenService. This protects against charging the wrong customer or using a stale/deleted tokenization reference.","triggerScenarios":"ProcessNextRecurringPaymentAsync is called; the TokenIdAttributeName generic attribute on paymentRequest.InitialOrder resolves to a tokenId that either yields no PayPalToken from GetByIdAsync, or the returned token's CustomerId does not match paymentRequest.CustomerId.","commonSituations":"The vaulted token was manually deleted from the PayPal Commerce token list after the original checkout; the initial order never had a token saved because tokenization failed silently; customer record was merged or the CustomerId changed; a scheduled recurring payment job fires for an order whose initial purchase used a guest checkout or a different PayPal account.","solutions":["Verify the token still exists: navigate to Admin > Configuration > PayPal Commerce > Tokens and check the token for that customer","Inspect the InitialOrder's TokenIdAttributeName generic attribute to confirm a valid token ID was stored during the original checkout","If the token is gone, cancel the recurring payment schedule and have the customer complete a new tokenized checkout to vault a fresh token","Ensure the recurring payment job passes the correct CustomerId matching the original tokenization"],"exampleFix":"// before\nvar (order, error) = await _serviceManager.ProcessNextRecurringPaymentAsync(settings, request);\nif (!string.IsNullOrEmpty(error))\n    _logger.Error(error);\n\n// after — pre-validate token before invoking recurring payment\nvar tokenId = await _genericAttributeService\n    .GetAttributeAsync<int>(request.InitialOrder, PayPalCommerceDefaults.TokenIdAttributeName);\nvar token = await _tokenService.GetByIdAsync(tokenId);\nif (token is null || token.CustomerId != request.CustomerId)\n{\n    await CancelRecurringPaymentAsync(request);\n    NotifyCustomerReTokenizationRequired(request.CustomerId);\n    return;\n}\nvar (order, error) = await _serviceManager.ProcessNextRecurringPaymentAsync(settings, request);","handlingStrategy":"validation","validationCode":"// Verify token exists and belongs to customer before calling ProcessNextRecurringPaymentAsync\nvar tokenId = await _genericAttributeService\n    .GetAttributeAsync<int>(initialOrder, PayPalCommerceDefaults.TokenIdAttributeName);\nvar token = await _tokenService.GetByIdAsync(tokenId);\nif (token is null || token.CustomerId != customer.Id)\n{\n    // Do NOT call ProcessNextRecurringPaymentAsync — cancel and re-tokenize\n    return;\n}","typeGuard":"// C# pattern-based guard\nvar token = await _tokenService.GetByIdAsync(tokenId);\nif (token is not PayPalToken { CustomerId: var cid } || cid != customerId)\n    return;\n// token is guaranteed non-null and owned by the customer","tryCatchPattern":"// HandleFunctionAsync catches internally; check the returned error tuple\nvar (order, error) = await _serviceManager\n    .ProcessNextRecurringPaymentAsync(settings, paymentRequest);\nif (!string.IsNullOrEmpty(error))\n{\n    _logger.Error($\"Recurring payment failed: {error}\");\n    await HandleRecurringPaymentFailureAsync(paymentRequest);\n}","preventionTips":["Always verify the TokenIdAttributeName generic attribute exists on the initial order before scheduling recurring payments","Run a periodic reconciliation job that checks all active recurring payment profiles have valid vaulted tokens","Log token ID and customer ID at checkout when tokenization succeeds, for later debugging","Handle recurring payment errors by notifying the customer to re-tokenize rather than silently retrying"],"tags":["paypal","payment-token","recurring-payment","vaulted-token","nopcommerce"],"backgroundTag":null,"analyzedSha":"64bdf2ff08c8b39e65717bcf974fb43dc2ef68f2","analyzedAt":"2026-08-13T21:19:38.062Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}