Repository navigation
Conversation
…plan order stable - `price month: 24, quarter: 54, year: 108` declares real per-interval amounts (day/week/month/quarter/year); `price 24` stays monthly shorthand. price_components returns the declared amount plus a per-month equivalent label, derives from monthly only for undeclared intervals, and reads Stripe's month x3 as a quarter. Adds billing_intervals, prices, price_id_for, price_ids, billing_interval_for, monthly_equivalent_cents, and an interval: argument on cta_url / pricing_plan_cta. - PricingPlans.plan_for_price / billing_interval_for expose the price id lookup PlanResolver uses, as one implementation. - PricingPlans.plans breaks rank ties by declaration order (sort_by is not stable). - A Stripe-only paid plan that compares as $0 logs a one-time warning. - stripe_price keys are validated; a price Hash and stripe_price must cover the same intervals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHFoaVbFsz35hTz1TQLfbe
usage_credits 1.0.0 adds `pricing_plan` to its subscription_plan DSL (rameerez/usage_credits#35), which reads the ids from PricingPlans::Registry.plan(key).stripe_price. The doc now shows that as the way to keep one list of Stripe ids, with the two-initializer form kept for earlier usage_credits versions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHFoaVbFsz35hTz1TQLfbe
…cing_plan From usage_credits 1.0.0 a subscription plan links to its pricing plan and reads its stripe_price lazily, so the ids live in one initializer. The two-initializer setup stays documented for earlier usage_credits versions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHFoaVbFsz35hTz1TQLfbe
Two concurrent edits added the same usage_credits `pricing_plan` section; keep the first one. The CHANGELOG line stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHFoaVbFsz35hTz1TQLfbe
Runtime intervals (price_components, price_id_for, cta_url) usually come straight from params[:interval], so raising turned a hand-edited ?interval=foo into a 500. An unknown or blank interval now means :month, logged at debug level; padded strings like " quarter " are understood. Unknown intervals in the plan configuration still raise ConfigurationError at boot, since those are typos, not user input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHFoaVbFsz35hTz1TQLfbe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four gaps a production app (Pro at $24/mo, $54/quarter, $108/yr) had to work around by hand.
1. Price every billing interval for real
:day, :week, :month, :quarter, :year, in bothpriceandstripe_price(plus:id, which counts as monthly). String intervals (params[:interval]) are accepted everywhere; unknown ones raise (ConfigurationErrorin config,ArgumentErrorat call sites).price 24stays shorthand forprice month: 24, so existing configs don't change. An undeclared interval is derived from the monthly price only as a fallback; with no monthly price it is reported as not present.interval: "month", interval_count: 3reads as:quarter; week/day monthly equivalents are computed correctly (52/12, 365/12).to_view_modeladdsbilling_intervalsandinterval_prices;pricing_plan_cta(plan, interval:)passes the interval through.priceis a Hash andstripe_priceis declared too, both must cover the same intervals (raises at boot). Otherwise the page would show an amount Stripe doesn't charge, or offer an interval checkout can't sell.2. Public price → plan lookup
PlanResolver.plan_for_processor_plannow delegates toRegistry.plan_for_price, so there is one implementation.3. Stable plan order
PricingPlans.plansusedsort_by, which is not stable, so Stripe-only plans (all ranked 10_000) and equally priced plans came back in arbitrary order. Ties now break by declaration index.test_plans_with_the_same_rank_keep_declaration_orderfails onmainand passes here.4. Warn when a paid plan silently compares as $0
A Stripe-only paid plan whose price can't be resolved still compares as $0 in
upgrade_from?/downgrade_from?, but it now logs one warning per plan naming it and suggesting a localprice(toRails.logger, or stderr outside Rails). Nothing else changes.Things to note
stripe_priceHash keys are now validated. Any key other than:idor an interval raises at boot. Before, such keys were silently ignored bymonthly_price_id/yearly_price_id. This is flagged as a heads-up in the CHANGELOG.$29.99/mo, including Stripe-derived labels that used to round to$30/mo), and whole amounts drop.0.## [Unreleased].Tests
bundle exec rake test: 739 runs, 2379 assertions, 0 failures (main: 663 runs). Line coverage is 88.51% and branch coverage 70.01%, up from 87.42% and 68.74%.🤖 Generated with Claude Code
https://claude.ai/code/session_01GHFoaVbFsz35hTz1TQLfbe