Skip to content

Update Stripe to use usage-based meters - #689

Merged
epompeii merged 1 commit into
develfrom
u/ep/stripe-meter
Mar 10, 2026
Merged

Update Stripe to use usage-based meters#689
epompeii merged 1 commit into
develfrom
u/ep/stripe-meter

Conversation

@epompeii

@epompeii epompeii commented Mar 7, 2026

Copy link
Copy Markdown
Member

Last year, Stripe deprecated the old usage-based billing. As we look to add bare metal runners, we will need to add a new usage-based billing product. Instead of managing both the old and new usage-based systems, move the current metrics based billing over to the new metered system.

@github-actions

github-actions Bot commented Mar 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

PR: #689
Base: devel
Head: u/ep/stripe-meter
Commit: dad61365d32f711a852fff47843b58ffe0c18f4e


Here's my review:


PR Review: Billing migration — multi-price support + modular stripe crates

Summary

This PR migrates from async-stripe 0.41 (monolithic) to the modular async-stripe 1.0.0-rc.2 crate family, and switches metered usage recording from per-subscription-item UsageRecord to Stripe's newer BillingMeterEvent API. It also introduces a preferred_price_ids mechanism with fallback for multi-price migration.


Issues

1. RC dependency in production (Cargo.toml)

All async-stripe-* crates are pinned to 1.0.0-rc.2. This is a release candidate — it may have breaking changes before 1.0.0 stable. For a billing system, this is worth flagging. Ensure this is an intentional, temporary choice with a plan to update once stable ships.

2. Redundant wildcard arm in map_status (biller.rs:650)

SubscriptionStatus::Unpaid | SubscriptionStatus::Unknown(_) | _ => PlanStatus::Unpaid,

The | _ wildcard makes SubscriptionStatus::Unpaid and SubscriptionStatus::Unknown(_) dead arms. This also silently suppresses the exhaustiveness check — if Stripe adds a new status variant (not Unknown), it will silently map to Unpaid rather than triggering a compile error. Consider:

SubscriptionStatus::Unpaid | SubscriptionStatus::Unknown(_) => PlanStatus::Unpaid,

This preserves the Unknown handling while keeping the exhaustiveness check. If the intent is truly to catch-all, add an #[expect(unreachable_patterns)] for documentation.

3. preferred_price_ids fallback logic may surprise during migration (products.rs:67-82)

The new preferred_price_ids falls back from preferred to DEFAULT_PRICE_NAME independently for metered and licensed. If only one of the two has the preferred key, you'll get a mix of preferred and default prices. Verify this is the intended behavior during the migration window.

4. Period dates moved from Subscription to SubscriptionItem (biller.rs:430-444)

current_period_start and current_period_end are now read from the filtered subscription_item instead of the subscription. This is a semantic change — subscription-level period and item-level period can differ (e.g., multi-phase subscriptions). Confirm this is correct for your billing model.

5. into_payment_card widens types unnecessarily (biller.rs:704-708)

let exp_month: i64 = i32::from(exp_month).into();
let exp_year: i64 = i32::from(exp_year).into();

The double conversion exp_month → i32 → i64 works but is a bit roundabout. If CreatePaymentMethodCardDetailsParams::new takes i64, consider i64::from(i32::from(exp_month)) or a direct .into() chain for clarity.

6. rustls dev-dependency added without comment (Cargo.toml)

rustls.workspace = true was added to [dev-dependencies] and default_provider().install_default() is called in tests. This is needed because the new async-stripe crate uses rustls with ring instead of the old hyper-rustls integration. This is fine, but ensure no other test crate also calls install_default() which would panic on the second call.

7. Removed error variants (error.rs)

Several error variants were removed (MeteredPlanId, LicensedPlanId, CheckoutSessionId, StripeId, NoPrice, NoProduct, NoProductName). This is correct since the new API uses infallible From/Into conversions instead of fallible .parse(). Good cleanup.

Positive Notes

  • The migration to modular crates with feature-gated dependencies is well done — only the needed Stripe modules are pulled in.
  • Replacing CreateUsageRecord with CreateBillingMeterEvent is the right move for Stripe's current metered billing model.
  • Removing ParseIdError paths throughout simplifies error handling since the new SDK uses infallible conversions.
  • Test coverage was maintained and updated to match the new struct shapes (no more Default::default() on types that lost Default).
  • The METRICS_METER_EVENT_NAME constant is properly shared via bencher_json rather than duplicated.

Compliance with CLAUDE.md

  • thiserror is used for errors (compliant).
  • Workspace dependency versions are in root Cargo.toml (compliant).
  • Strong types used throughout (compliant).
  • Tests updated to use new types correctly.

Recommendation

The main actionable item is issue #2 (the redundant wildcard arm) — it suppresses a useful compiler warning and should be tightened. The rest are points to verify intentionality rather than bugs.


Model: claude-opus-4-6

@github-actions

github-actions Bot commented Mar 7, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchu/ep/stripe-meter
Testbedubuntu-22.04
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
Adapter::Json📈 view plot
🚷 view threshold
3.87 µs
(+14.54%)Baseline: 3.38 µs
4.59 µs
(84.28%)
Adapter::Magic (JSON)📈 view plot
🚷 view threshold
3.84 µs
(+14.00%)Baseline: 3.37 µs
4.51 µs
(85.23%)
Adapter::Magic (Rust)📈 view plot
🚷 view threshold
26.22 µs
(+2.45%)Baseline: 25.59 µs
30.87 µs
(84.95%)
Adapter::Rust📈 view plot
🚷 view threshold
2.80 µs
(-1.59%)Baseline: 2.84 µs
3.34 µs
(83.72%)
Adapter::RustBench📈 view plot
🚷 view threshold
2.80 µs
(-1.63%)Baseline: 2.84 µs
3.33 µs
(84.06%)
head_version_insert/batch/10📈 view plot
🚷 view threshold
99.28 µs
(+0.13%)Baseline: 99.15 µs
123.19 µs
(80.59%)
head_version_insert/batch/100📈 view plot
🚷 view threshold
234.22 µs
(-0.75%)Baseline: 235.98 µs
269.55 µs
(86.89%)
head_version_insert/batch/255📈 view plot
🚷 view threshold
453.91 µs
(-1.22%)Baseline: 459.52 µs
490.25 µs
(92.59%)
head_version_insert/batch/50📈 view plot
🚷 view threshold
158.55 µs
(-0.48%)Baseline: 159.31 µs
184.91 µs
(85.75%)
threshold_query/join/10📈 view plot
🚷 view threshold
140.64 µs
(-1.53%)Baseline: 142.83 µs
171.24 µs
(82.13%)
threshold_query/join/20📈 view plot
🚷 view threshold
156.09 µs
(-0.52%)Baseline: 156.91 µs
186.19 µs
(83.83%)
threshold_query/join/5📈 view plot
🚷 view threshold
134.68 µs
(-0.25%)Baseline: 135.02 µs
161.16 µs
(83.57%)
threshold_query/join/50📈 view plot
🚷 view threshold
196.15 µs
(-0.82%)Baseline: 197.77 µs
229.08 µs
(85.62%)
🐰 View full continuous benchmarking report in Bencher

@epompeii
epompeii force-pushed the u/ep/stripe-meter branch from 2cc0003 to 41c666d Compare March 8, 2026 05:04
@epompeii
epompeii force-pushed the u/ep/stripe-meter branch from 41c666d to 678b9c1 Compare March 8, 2026 05:22
epompeii added a commit that referenced this pull request Mar 9, 2026
Last year, [Stripe deprecated the old usage-based
billing](https://docs.stripe.com/changelog/basil/2025-03-31/deprecate-legacy-usage-based-billing).
As we look to add bare metal runners, we will need to add a new
usage-based billing product. Instead of managing both the old and new
usage-based systems, move the current metrics based billing over to the
new `meter`ed system.

The actual transition will take place in
#689
However, in order to have a smooth cutover, we will need to have
multiple Stripe subscription items active at once. The current `default`
keys will be used for now and then cutover to the new `metrics` keys.
@epompeii
epompeii force-pushed the u/ep/stripe-meter branch from 678b9c1 to d0463ff Compare March 9, 2026 06:00
@epompeii
epompeii force-pushed the u/ep/stripe-meter branch from d0463ff to dad6136 Compare March 10, 2026 03:27
@epompeii
epompeii merged commit 1fbccb5 into devel Mar 10, 2026
58 of 60 checks passed
@epompeii
epompeii deleted the u/ep/stripe-meter branch March 10, 2026 04:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant