feat(stripe): price-scoped subscription count, price unarchive, interval_count on price response #41
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/DUNITE-9-price-subscription-count"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
DUNITE-9. Three additions to the price surface of
dunite-stripe, all driven by the consumers' admin billing screens.count_price_subscriptionsCounts subscriptions attached to one price using Stripe's server-side
pricelist filter, so a monthly and a yearly price on the same product report separately rather than collapsing into a product-level number. Status filtering is Stripe's default (every subscription it does not consider canceled:active,trialing,past_due,unpaid,paused,incomplete), which is the set an admin cares about before archiving a price.Pages at 100 per request up to
MAX_PRICE_SUBSCRIPTION_PAGES(20 pages = 2000 subscriptions) instead of walking unbounded and hanging an admin request on sequential Stripe round trips. Hitting the cap setscomplete: falseon the newStripePriceSubscriptionCountand logs a warning, so the caller can render "N+" rather than a silently wrong total.unarchive_priceInverse of
archive_price:UpdatePrice { active: Some(true) }. Stripe refuses to activate a price whose parent product is still archived, so the doc comment tells callers to unarchive the product first and the error log carries that as ahintfield.recurring_interval_countonStripePriceResponseRead off
recurring.interval_countin bothlist_pricesandcreate_price. Without it the DTO cannot represent a quarterly price:interval = monthalone is indistinguishable from monthly billing.Nonefor one-off prices.create_pricereads the value back off Stripe's response rather than assuming the default of 1.Compatibility
Additive on both the DTO and the service. Consumers that deserialize or read
StripePriceResponseare unaffected; a consumer that constructs the struct literally must add the new field.Testing
just fmt,just check,just lintandjust testare clean (cargo test --workspacerun four times end to end). One intermediate sweep hit a load-timing flake in an unrelated crate (dunite-download'sburst_of_concurrent_stores_coalesces_into_few_eviction_passes_and_converges); it passes standalone and in every subsequent sweep on this branch, and nothing here touches that crate.No new tests: every added path is a live Stripe API round trip and the crate has no HTTP-level harness (the
async-stripeclient's base URL is not injectable here), consistent with the rest of the service.Three additions to dunite-stripe's price surface (DUNITE-9), all needed by the admin billing screens in the consumers and none of them expressible without a service-layer call. `StripeService::count_price_subscriptions` counts the subscriptions attached to one price via Stripe's server-side `price` list filter, so two prices on the same product (monthly vs yearly) report separately instead of collapsing into a product-level number. Status filtering is Stripe's default: every subscription it does not consider canceled, which is the set that matters before archiving a price. It pages at 100 per request up to `MAX_PRICE_SUBSCRIPTION_PAGES` (20, so 2000 subscriptions) rather than making an admin request wait on an unbounded walk; hitting that cap returns `complete: false` on the new `StripePriceSubscriptionCount` so the caller can render "N+" instead of silently reporting a wrong total. `StripeService::unarchive_price` is the inverse of `archive_price` (`UpdatePrice { active: Some(true) }`). Stripe rejects activating a price whose parent product is still archived, so the failure log carries that as a hint and the doc comment says to unarchive the product first. `StripePriceResponse` gains `recurring_interval_count`, read off `recurring.interval_count` in both `list_prices` and `create_price`. Without it the DTO cannot describe a quarterly price: `interval = month` alone is indistinguishable from monthly billing. `None` for one-off prices. This is an additive field on a response DTO, so consumers that only deserialize or read it are unaffected; any consumer constructing the struct literally needs the extra field. No tests: every new path is a Stripe API round trip and the crate has no HTTP-level test harness (async-stripe's client base URL is not injectable here), matching the rest of the service.