nopSolutions/nopCommerce · warning · NopException

Next payment date could not be calculated

Error message

Next payment date could not be calculated

What it means

Thrown in ProcessNextRecurringPaymentAsync when GetNextPaymentDateAsync(recurringPayment) returns null. nopCommerce computes the next billing date from CycleLength/CyclePeriod/StartDate/CyclesRemaining; if that math yields no valid future date (e.g. all cycles exhausted, or the start date is invalid), it refuses to charge the customer.

Source

Thrown at src/Libraries/Nop.Services/Orders/OrderProcessingService.cs:1905

    /// The task result contains the collection of errors
    /// </returns>
    public virtual async Task<IEnumerable<string>> ProcessNextRecurringPaymentAsync(RecurringPayment recurringPayment, ProcessPaymentResult paymentResult = null)
    {
        ArgumentNullException.ThrowIfNull(recurringPayment);

        try
        {
            if (!recurringPayment.IsActive)
                throw new NopException("Recurring payment is not active");

            var initialOrder = await _orderService.GetOrderByIdAsync(recurringPayment.InitialOrderId)
                               ?? throw new NopException("Initial order could not be loaded");

            var customer = await _customerService.GetCustomerByIdAsync(initialOrder.CustomerId)
                           ?? throw new NopException("Customer could not be loaded");

            if (await GetNextPaymentDateAsync(recurringPayment) is null)
                throw new NopException("Next payment date could not be calculated");

            //payment info
            var processPaymentRequest = new ProcessPaymentRequest
            {
                StoreId = initialOrder.StoreId,
                CustomerId = customer.Id,
                OrderGuid = Guid.NewGuid(),
                InitialOrder = initialOrder,
                RecurringCycleLength = recurringPayment.CycleLength,
                RecurringCyclePeriod = recurringPayment.CyclePeriod,
                RecurringTotalCycles = recurringPayment.TotalCycles
            };

            processPaymentRequest.CustomValues.FillByXml(initialOrder.CustomValuesXml);

            //prepare order details
            var details = await PrepareRecurringOrderDetailsAsync(processPaymentRequest);

View on GitHub (pinned to 64bdf2ff08)

Solutions

  1. Compare CyclesProcessed against TotalCycles - if equal, set IsActive=false (subscription complete).
  2. Correct the recurring payment's CycleLength/CyclePeriod/StartDateUtc if malformed.
  3. Guard the recurring task to skip payments whose next date is null.

Example fix

// before
var errors = await _orderProcessingService.ProcessNextRecurringPaymentAsync(recurringPayment);

// after
if (await _orderProcessingService.GetNextPaymentDateAsync(recurringPayment) is null)
{
    recurringPayment.IsActive = false; // cycles exhausted
    await _orderService.UpdateRecurringPaymentAsync(recurringPayment);
    continue;
}
var errors = await _orderProcessingService.ProcessNextRecurringPaymentAsync(recurringPayment);
Defensive patterns

Strategy: validation

Validate before calling

if (await _orderProcessingService.GetNextPaymentDateAsync(recurringPayment) is null)
{
    // cycles exhausted or misconfigured - mark complete
    recurringPayment.IsActive = false;
    await _orderService.UpdateRecurringPaymentAsync(recurringPayment);
    _logger.LogInformation("Recurring payment {Id} has no next payment date - deactivating.",
        recurringPayment.Id);
    return Array.Empty<string>();
}

return await _orderProcessingService.ProcessNextRecurringPaymentAsync(recurringPayment);

Type guard

Task<bool> HasNextCycleAsync(RecurringPayment rp)
    => _orderProcessingService.GetNextPaymentDateAsync(rp).ContinueWith(t => t.Result is not null);

Try / catch

try
{
    var errors = await _orderProcessingService.ProcessNextRecurringPaymentAsync(recurringPayment);
}
catch (NopException ex) when (ex.Message == "Next payment date could not be calculated")
{
    recurringPayment.IsActive = false;
    await _orderService.UpdateRecurringPaymentAsync(recurringPayment);
    _logger.LogWarning(ex, "Recurring payment {Id} has invalid cycle config", recurringPayment.Id);
}

Prevention

When it happens

Trigger: A recurring payment whose cycle configuration produces no next date: total cycles already completed, CycleLength <= 0, CyclePeriod unset, or StartDateUtc in an unparseable state. The recurring task picks it up but cannot determine when to bill.

Common situations: Subscription reached its total cycle count but IsActive was not flipped to false; misconfigured recurring product (zero cycle length); timezone/UTC date corruption in the order seed.

Related errors


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