Skip to content

Bill against the new Stripe Prices - #792

Closed
mitchelljkotler wants to merge 1 commit into
pricing-models-item-splitfrom
pricing-models-bill-on-price
Closed

Bill against the new Stripe Prices#792
mitchelljkotler wants to merge 1 commit into
pricing-models-item-splitfrom
pricing-models-bill-on-price

Conversation

@mitchelljkotler

Copy link
Copy Markdown
Member

stripe_items() sent item.plan.stripe_id -- the legacy identifier built from the slug -- so every PlanPrice and every Stripe Price made by consolidate_stripe_products was inert. Stripe held both the old Plans and the new Prices, and squarelet billed against the old ones.

Lines now bill against their PlanPrice's Stripe Price, falling back to the plan's legacy id. The fallback is not a transitional convenience that disappears once the backfill finishes: it covers the three populations that keep a null plan_price by design -- per-user subscribers awaiting decomposition, deferred slugs, and every signup until the purchase flow records a price. It goes away in 3e, once nothing can be null.

Subscription.free moves with it, and has to. It read Plan.free, which is computed from base_price and price_per_user -- not from the price the line is actually on. A comped PlanPrice sits on a paid plan, so a subscription with one paid line and one comped line was not free, went to Stripe, and would have sent a blank plan id for the comped line, since a comped price has no Stripe Price at all. Both now ask the line, through SubscriptionItem.is_free, and stripe_items drops free lines rather than naming an object that does not exist.

This has to precede the decomposition: the pack plans are created through a historical model, so the make_stripe_plan signal never fires for them and they have no legacy Stripe Plan to fall back to. A pack line cannot be created until this exists.

stripe_items() sent item.plan.stripe_id -- the legacy identifier built
from the slug -- so every PlanPrice and every Stripe Price made by
consolidate_stripe_products was inert.  Stripe held both the old Plans and
the new Prices, and squarelet billed against the old ones.

Lines now bill against their PlanPrice's Stripe Price, falling back to the
plan's legacy id.  The fallback is not a transitional convenience that
disappears once the backfill finishes: it covers the three populations
that keep a null plan_price by design -- per-user subscribers awaiting
decomposition, deferred slugs, and every signup until the purchase flow
records a price.  It goes away in 3e, once nothing can be null.

Subscription.free moves with it, and has to.  It read Plan.free, which is
computed from base_price and price_per_user -- not from the price the line
is actually on.  A comped PlanPrice sits on a *paid* plan, so a
subscription with one paid line and one comped line was not free, went to
Stripe, and would have sent a blank plan id for the comped line, since a
comped price has no Stripe Price at all.  Both now ask the line, through
SubscriptionItem.is_free, and stripe_items drops free lines rather than
naming an object that does not exist.

This has to precede the decomposition: the pack plans are created through
a historical model, so the make_stripe_plan signal never fires for them
and they have no legacy Stripe Plan to fall back to.  A pack line cannot
be created until this exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@allanlasser
allanlasser temporarily deployed to squarelet-pi-pricing-mo-e8nplo September 2, 2026 15:41 Inactive
@mitchelljkotler mitchelljkotler changed the title Bill against the new Stripe Prices (step 2c-bis) Bill against the new Stripe Prices Sep 2, 2026
@mitchelljkotler
mitchelljkotler deleted the pricing-models-bill-on-price branch September 2, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants