Found while live-testing #1849/#1850/#1851. Both problems are in the API creation path (CreatePlan / CreateProduct) and exist on main — the plans-file bootstrap path is not affected.
1. Plans get an empty state and become invisible
CreatePlan saves the plan without setting state. Every plan lookup that goes through the repository List filter defaults to state = 'active' (internal/store/postgres/billing_plan_repository.go), so an API-created plan:
- does not show up in
ListPlans (returns empty even though GetPlan by id works)
- breaks
ChangeSubscription: resolving the plan from the Stripe schedule uses planService.List, so it fails with no plan found for phase products: [...], interval: month and the RPC returns internal
- makes the background
subscription.SyncWithProvider job log the same error every minute, forever, for any subscription on such a plan
Reproduce:
CreatePlan via the API with one product and one price
ListPlans → empty; select state from billing_plans → empty string
- Subscribe to the plan, then
ChangeSubscription to another plan → internal
Workaround: update billing_plans set state='active' where name=...
Fix idea: set state = 'active' as a default in the plan service Create/upsert path (the same way prices already get state = 'active').
2. Empty product description fails at Stripe
productService.Create always sends Description: &product.Description to Stripe. When the caller leaves the description empty, Stripe rejects it with parameter_invalid_empty ("You passed an empty string for 'description'"), and CreatePlan/CreateProduct return internal.
Reproduce: CreateProduct via the API without a description → internal.
Fix idea: only set the description param when it is non-empty.
Found while live-testing #1849/#1850/#1851. Both problems are in the API creation path (
CreatePlan/CreateProduct) and exist onmain— the plans-file bootstrap path is not affected.1. Plans get an empty
stateand become invisibleCreatePlansaves the plan without settingstate. Every plan lookup that goes through the repositoryListfilter defaults tostate = 'active'(internal/store/postgres/billing_plan_repository.go), so an API-created plan:ListPlans(returns empty even thoughGetPlanby id works)ChangeSubscription: resolving the plan from the Stripe schedule usesplanService.List, so it fails withno plan found for phase products: [...], interval: monthand the RPC returnsinternalsubscription.SyncWithProviderjob log the same error every minute, forever, for any subscription on such a planReproduce:
CreatePlanvia the API with one product and one priceListPlans→ empty;select state from billing_plans→ empty stringChangeSubscriptionto another plan →internalWorkaround:
update billing_plans set state='active' where name=...Fix idea: set
state = 'active'as a default in the plan serviceCreate/upsert path (the same way prices already getstate = 'active').2. Empty product description fails at Stripe
productService.Createalways sendsDescription: &product.Descriptionto Stripe. When the caller leaves the description empty, Stripe rejects it withparameter_invalid_empty("You passed an empty string for 'description'"), andCreatePlan/CreateProductreturninternal.Reproduce:
CreateProductvia the API without adescription→internal.Fix idea: only set the description param when it is non-empty.