{"record":{"id":"da0b196bce172b9e","repo":"kgrzybek/modular-monolith-with-ddd","slug":"subscription-for-renewal-must-exist","errorCode":null,"errorMessage":"Subscription for renewal must exist.","messagePattern":"Subscription for renewal must exist\\.","errorType":"validation","errorClass":"InvalidCommandException","httpStatus":400,"severity":"error","filePath":"src/Modules/Payments/Application/Subscriptions/BuySubscriptionRenewal/BuySubscriptionRenewalCommandHandler.cs","lineNumber":40,"sourceCode":"            IPayerContext payerContext,\n            ISqlConnectionFactory sqlConnectionFactory)\n        {\n            _aggregateStore = aggregateStore;\n            _payerContext = payerContext;\n            _sqlConnectionFactory = sqlConnectionFactory;\n        }\n\n        public async Task<Guid> Handle(BuySubscriptionRenewalCommand command, CancellationToken cancellationToken)\n        {\n            var priceList = await PriceListFactory.CreatePriceList(_sqlConnectionFactory.GetOpenConnection());\n\n            var subscriptionId = new SubscriptionId(command.SubscriptionId);\n\n            var subscription = await _aggregateStore.Load(new SubscriptionId(command.SubscriptionId));\n\n            if (subscription == null)\n            {\n                throw new InvalidCommandException([\"Subscription for renewal must exist.\"]);\n            }\n\n            var subscriptionRenewalPayment = SubscriptionRenewalPayment.Buy(\n                _payerContext.PayerId,\n                subscriptionId,\n                SubscriptionPeriod.Of(command.SubscriptionTypeCode),\n                command.CountryCode,\n                MoneyValue.Of(command.Value, command.Currency),\n                priceList);\n\n            _aggregateStore.AppendChanges(subscriptionRenewalPayment);\n\n            return subscriptionRenewalPayment.Id;\n        }\n    }\n}","sourceCodeStart":22,"sourceCodeEnd":56,"githubUrl":"https://github.com/kgrzybek/modular-monolith-with-ddd/blob/91c8ef24b4cb6ef558c95d8267fa07d68c7059f8/src/Modules/Payments/Application/Subscriptions/BuySubscriptionRenewal/BuySubscriptionRenewalCommandHandler.cs#L22-L56","documentation":"Thrown by BuySubscriptionRenewalCommandHandler when _aggregateStore.Load(new SubscriptionId(...)) returns null. Renewal needs the existing Subscription aggregate to attach the renewal payment; a missing subscription aborts with InvalidCommandException (HTTP 400). Note the price list is loaded first, then the subscription, so a bad id fails at the subscription step.","triggerScenarios":"Dispatching BuySubscriptionRenewalCommand with a SubscriptionId that has no aggregate: wrong Guid, subscription expired/cancelled and removed, wrong tenant, or stream not found.","commonSituations":"User renews from a stale account page after cancellation; cross-environment id; checkout retries with an id from a deleted test subscription.","solutions":["Confirm the SubscriptionId belongs to the payer's active subscription before checkout.","Verify the SubscriptionTypeCode/CountryCode/Value/Currency are valid (these feed SubscriptionRenewalPayment.Buy and MoneyValue.Of, which can also throw or fail rule checks against the price list).","Pre-check existence in the controller and return 404.","Map InvalidCommandException to 400/404 at the API boundary."],"exampleFix":"// before\nvar paymentId = await _commandDispatcher.SendAsync(new BuySubscriptionRenewalCommand(subscriptionId, typeCode, countryCode, value, currency));\n\n// after\nvar subscription = await _subscriptionQueries.GetSubscriptionAsync(subscriptionId, payerId);\nif (subscription is null) return NotFound(\"Subscription not found.\");\nvar paymentId = await _commandDispatcher.SendAsync(new BuySubscriptionRenewalCommand(subscriptionId, typeCode, countryCode, value, currency));","handlingStrategy":"validation","validationCode":"var subscription = await _subscriptionQueries.GetSubscriptionAsync(subscriptionId, payerId);\nif (subscription is null) return NotFound(\"Subscription not found.\");\nvar paymentId = await _commandDispatcher.SendAsync(new BuySubscriptionRenewalCommand(subscriptionId, typeCode, countryCode, value, currency));","typeGuard":"public static bool IsValidRenewalCommand(BuySubscriptionRenewalCommand c) =>\n    c.SubscriptionId != Guid.Empty\n    && !string.IsNullOrWhiteSpace(c.SubscriptionTypeCode)\n    && c.Value >= 0\n    && !string.IsNullOrWhiteSpace(c.Currency);","tryCatchPattern":"try { await _commandDispatcher.SendAsync(cmd); }\ncatch (InvalidCommandException ex) when (ex.Errors.Any(m => m.Contains(\"must exist\")))\n{ return NotFound(new { errors = ex.Errors }); }","preventionTips":["Confirm the subscription belongs to the payer and is renewable before checkout.","Pre-validate subscription type/country/money against the price list.","Reject empty Guid and negative money client-side."],"tags":["payments","subscriptions","renewal","not-found","invalid-command-exception","command-handler"],"backgroundTag":null,"analyzedSha":"91c8ef24b4cb6ef558c95d8267fa07d68c7059f8","analyzedAt":"2026-08-13T17:18:16.571Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}