profitable v0.6.0 is an accuracy-focused production release for the Pay-powered metric layer. Every metric was re-audited against the data Pay 7.x-11.x actually stores, with regression coverage for the bugs found along the way.
This release intentionally changes user-visible numbers where previous calculations were wrong. Apps with meaningful trial volume, paused subscriptions, SQLite charge payloads, or Braintree/Lemon Squeezy subscription statuses should review the behavior notes below.
Highlights
- Canceled trials that never converted no longer count as subscribers, customers, new MRR, churned MRR, churn, or dashboard summary activity.
- Monthly churn rates no longer suffer from survivor bias: denominators now include subscribers who churn later inside the reporting window.
- Monthly and daily summaries no longer count future trial conversions before they actually happen.
- SQLite charge filtering now treats JSON booleans consistently with PostgreSQL/MySQL, so
paid: falsecharges do not count as revenue on SQLite. - Pay 7-9 data-only charge schemas are supported again; revenue queries no longer reference
pay_charges.objectunless that column exists. - Processor status handling is stricter:
expirednow churns atends_at, andpendingis never billable. - Paused subscriptions now preserve their historical subscriber/customer lifecycle while staying out of current MRR when paused.
Profitable.time_to_next_mrr_milestonenow returns aProfitable::NumericResult, so the documented.to_readableAPI works.- Added
Profitable.mrr_at(date)as the public historical MRR snapshot API.
Metric Correctness Fixes
- Fixed canceled trials polluting
total_subscribers,total_customers,new_customers,new_subscribers,new_mrr,churned_customers,churned_mrr,churn,monthly_summary, anddaily_summary. - Fixed churned subscriptions being removed from earlier monthly churn denominators when they churned mid-window.
- Fixed future trial conversions appearing early in current-month/current-day summaries.
- Fixed
paid: falsecharge payloads being counted on SQLite because SQLite returns JSON booleans as0/1instead of text. - Fixed Pay 7-9 compatibility for apps whose
pay_chargestable only hasdataand notobject. - Fixed paused lifecycle semantics so historical customer/subscriber counts and current MRR answer different questions correctly.
- Fixed sub-dollar positive MRR being treated as “No MRR yet” in milestone messaging.
- Fixed monthly churn denominator boundary behavior to match the inclusive period-start semantics used by
churn. - Fixed existing
Profitable::Errormessages being double-wrapped during MRR snapshot delegation.
API And Documentation
- Added
Profitable.mrr_at(date)for historical MRR snapshots. - Kept raw
calculate_mrr_atprivate so public metric methods consistently returnProfitable::NumericResult. - Documented that Stripe is the only processor with subscription amount payloads stored locally by Pay out of the box.
- Clarified that Braintree/Paddle subscriptions contribute
0to amount-derived MRR metrics unless apps backfill processor subscription payloads intopay_subscriptions.object. - Corrected the stale README reference to
Profitable.valuation_estimate.
Internal Cleanup
- Consolidated churn-event queries into one helper.
- Consolidated MRR “now” and MRR “at date” paths so current MRR and growth-rate snapshots cannot drift.
- Centralized subscription payload lookup in
Processors::Base.subscription_data. - Added schema-aware and memoized charge payload column detection.
- Changed raw SQL subscription joins to Active Record association joins where appropriate.
- Added direct
delegate/actionviewrequirements and declaredactionviewas a gem dependency. - Removed the full top-level
require "rails"; the gem now uses narrower direct dependency requires.
Behavior Notes
This release is expected to change metric outputs for some apps. These changes are corrections, not cosmetic formatting updates:
- Trial-only cancellations disappear from paid subscriber/customer and churn metrics.
expiredsubscriptions no longer inflate active/billable metrics forever.pendingsubscriptions no longer contribute to MRR-style or active subscriber metrics.- Paused subscriptions can still count historically after they became billable, but they are excluded from current MRR while paused.
- Charge-based metrics are portable across Pay processors because Pay stores real charge amounts locally.
- Amount-derived subscription metrics are fully supported out of the box for Stripe. Braintree/Paddle amount parsing requires local subscription payload backfills.
Verification
- Default suite on the merged release commit:
338 runs, 520 assertions, 0 failures. - Local appraisal matrix green: Pay 7.3, 8.3, 9.0, 10.0, 11.0; Rails 7.2, 8.1.
- GitHub Actions test matrix green across Ruby 3.3, 3.4, and 4.0 combinations.
- Line coverage: ~97.75%; branch coverage: ~89.9%.
bundle exec rake buildsucceeds and buildspkg/profitable-0.6.0.gemlocally.
GitHub Generated Notes
What's Changed
Full Changelog: v0.5.0...v0.6.0