pay-rails/pay
59.4
Adequate · 19 September 2026
5k
lines of production code
Ruby
primary language
1
measurement over time
What this system is
This system is a Ruby gem that provides a unified payment processing layer for Rails applications, supporting Stripe, Braintree, Paddle, and Lemon Squeezy. It manages the full lifecycle of customers, subscriptions, and charges while handling secure webhook ingestion and asynchronous event processing. The library also includes configurable email notifications for billing events and a customizable UI for payment flows.
How it got here
2017 — Initial scaffolding and multi-provider integration
17 changes.
This period established the foundational structure of the Pay gem, introducing configurable model inheritance, table prefixes, and a comprehensive set of payment processor integrations including Stripe, Braintree, Paddle, and Lemon Squeezy. The work focused on building a feature-complete library with centralized webhook handling, dynamic email configuration, and modern UI components using Tailwind CSS and Stripe.js v3. Extensive test coverage was added via an updated dummy application to validate these new features and ensure compatibility with Rails 7.0 through 8.1.
2018–2020 — multi-processor expansion and UI modernization
18 changes.
The project expanded its payment gateway support by introducing STI-based implementations for Braintree, Lemon Squeezy, and Paddle, alongside dedicated webhook controllers for all providers. This period also focused on modernizing the user experience through a redesigned payment page using Stripe Elements and Tailwind CSS, while establishing comprehensive test coverage and customizable email templates to support the new integrations.
2021–2024 — Pay gem v3.0 migration and webhook overhaul
27 changes.
This period focused on upgrading the Pay gem to version 3.0, introducing Single Table Inheritance and new database fields to support multiple payment processors. It involved a significant architectural shift to asynchronous webhook processing and the addition of comprehensive support for Braintree, Paddle Classic, and Lemon Squeezy. Extensive test coverage was added across all core models, jobs, and processor integrations to ensure stability during this transition.
Features
Add configurable webhook routes for multiple payment providers
The application now exposes dedicated webhook endpoints for Stripe, Braintree, Paddle Billing, Paddle Classic, and Lemon Squeezy. These routes are conditionally registered based on whether each respective payment processor is enabled, allowing users to receive real-time payment events from all supported providers without manual configuration.
config · high confidence
Add customer sync job for Pay models
A new \CustomerSyncJob\ has been added to the \Pay\ module to handle synchronizing customer data. When executed, this job locates a \Pay::Customer\ by ID and calls \update\_api\_record\ to refresh the external payment processor's record, logging a message if the customer is not found.
app/jobs · high confidence
Add pay\_amount\_to\_currency view helper
A new view helper, \pay\_amount\_to\_currency\, is now available in the \Pay::CurrencyHelper\ module. This helper formats an object's amount into a currency string, automatically using the currency specified on the object itself, which simplifies displaying monetary values in views.
app/helpers · high confidence
Added generators for customizing Pay email and view templates
Developers can now generate local copies of Pay's default email and view templates to customize them. The new \email\_views\_generator\ copies the user mailer views, while the \views\_generator\ copies the general payment views, allowing for easy override of the default UI and email content.
lib/generators · high confidence
Added local multi-database test script and updated Rails bin configuration
Developers can now run tests against SQLite3, PostgreSQL, and MySQL locally using the new bin/test\_databases helper script. Additionally, the bin/rails script has been updated to default the Rails environment to 'test' and corrected path resolution for the engine and dummy application, ensuring consistent behavior during local development and testing.
bin · high confidence
Added sync\_default rake task for payment methods
A new rake task, \pay:payment\_methods:sync\_default\, has been introduced to synchronize default payment methods for Pay::Customers. This task iterates through all customers and updates their default payment method status based on the specific processor (Braintree, Stripe, or Paddle Classic), including retry logic for failed operations. The previous placeholder task file \pay\_tasks.rake\ has been removed.
lib/tasks · high confidence
Introduces STI-based processor implementations for Braintree, Lemon Squeezy, and Paddle Billing
The payment models now use Single Table Inheritance (STI) to provide distinct processor implementations. This location adds the specific \Pay::Braintree\, \Pay::LemonSqueezy\, and \Pay::PaddleBilling\ subclasses for \Charge\, \Customer\, \PaymentMethod\, and \Subscription\, alongside updated base models (\Pay::Charge\, \Pay::Customer\) that define shared scopes and helpers for all supported processors. Users can now utilize these new payment gateways with a consistent API structure.
pay · high confidence
New Paddle Classic webhook handlers and signature verification
This change introduces the core webhook processing logic for the Paddle Classic payment processor. It adds a \SignatureVerifier\ to validate incoming webhook requests using Paddle's public key and PHP-style serialization, ensuring request integrity. Additionally, it implements handlers for subscription lifecycle events: \SubscriptionCreated\ syncs new subscriptions, \SubscriptionUpdated\ maps Paddle statuses (active, trialing, deleted) to internal states and tracks trial/pause dates, \SubscriptionCancelled\ sets the subscription end date to the paid period's end (preserving access until then) and removes customer payment methods, \SubscriptionPaymentSucceeded\ records charges and sends receipts, and \SubscriptionPaymentRefunded\ updates refund amounts and sends refund notifications.
_lib/pay/paddle\classic · high confidence
New billing notification emails for payment actions and subscription lifecycle events
Users will now receive email notifications for key billing events, including payment action required, payment failures, subscription renewals, and trial status changes (ending soon and ended). The library introduces new HTML and plain-text templates for these scenarios, ensuring users are informed about payment confirmations, declined charges, upcoming renewals, and trial expirations, with links to manage their subscription or update billing information.
_app/views/pay/user\mailer · high confidence
New webhook controllers for Braintree, Lemon Squeezy, Paddle, and Stripe
This change introduces dedicated API controllers for handling webhooks from Braintree, Lemon Squeezy, Paddle (both Classic and Billing), and Stripe. Each controller validates incoming signatures, records the webhook event in the database, and queues a background job for processing. The Stripe controller includes logic to optionally receive test events based on configuration and supports multiple signing secrets. The Paddle controllers handle signature verification for both the new Paddle Billing API and the legacy Paddle Classic API. The Lemon Squeezy controller validates HMAC signatures. The Braintree controller parses and verifies webhook notifications using the Braintree gateway.
app/controllers/pay/webhooks · high confidence
Pay gem updated to version 11.7.2 with new processor integrations and webhook architecture
The Pay library has been upgraded to version 11.7.2, introducing native support for Lemon Squeezy and Paddle Billing alongside the existing Stripe and Paddle Classic processors. This release replaces the previous \stripe\_event\ dependency with a new centralized \Pay::Webhooks\ delegator that processes events via background jobs, ensuring consistent handling across all payment providers. The update also includes a new \Pay::Adapter\ module to handle database-specific JSON column types, a \Pay::NanoId\ generator for faster unique ID creation, and a refactored \Pay::Attributes\ module that adds \pay\_merchant\ support for merchant processors. Additionally, the internal \FakeProcessor\ has been moved from application helpers to the library core for testing purposes.
lib/pay · high confidence
Behavioural changes
Braintree webhook handlers now sync subscription state and send receipts
The Braintree payment processor now uses dedicated webhook handler classes to process subscription events. When a subscription becomes active, expires, ends its trial, or moves to past-due status, the system automatically syncs the local subscription state with Braintree. Additionally, when a subscription charge is successful, the system syncs the charge details and automatically sends a receipt email to the customer.
lib/pay/braintree · high confidence
Configurable customer sync trigger on email updates
The billable module now includes a new \SyncCustomer\ concern that automatically queues a job to update payment processor records when a customer's email address changes. This behavior is triggered by an \after\_update\_commit\ callback, but users can override the \pay\_should\_sync\_customer?\ method to customize or disable the sync logic entirely.
lib/pay/billable · high confidence
Configurable mailer defaults and dynamic email arguments
The Pay mailer system now supports independent configuration for email sending. The application mailer dynamically resolves the default 'from' address, preferring the configured support email over the previous hardcoded placeholder. Additionally, a new UserMailer class allows for polymorphic argument handling via instance\_exec, enabling flexible customization of mail arguments and supporting specific logic such as attaching receipt files when available.
app/mailers · high confidence
Database schema updated for Pay gem v3.0 with STI and new fields
The database schema has been updated to align with the Pay gem v3.0 release. This introduces Single Table Inheritance (STI) by adding \type\ columns to \pay\_customers\, \pay\_charges\, \pay\_subscriptions\, and \pay\_merchants\, while renaming the existing \type\ column in \pay\_payment\_methods\ to \payment\_method\_type\. New columns include \stripe\_account\ across customer, payment method, subscription, and charge tables, as well as \current\_period\_start\ and \current\_period\_end\ on subscriptions. Additionally, an \object\ JSON column has been added to charges, customers, and subscriptions to store raw processor data, and the migration respects the application's configured primary key type.
db · high confidence
Introduce background processing and explicit event subscription for webhooks
Webhooks are now processed asynchronously via a new \ProcessJob\ instead of synchronously, improving application responsiveness during payment events. The system replaces the previous \stripe\_event\ library with a custom \Delegator\ that uses \ActiveSupport::Notifications\ for event dispatching. This change allows users to explicitly subscribe to specific webhook events or unsubscribe from them, ensuring that only relevant events are queued and processed.
lib/pay/webhooks · high confidence
Major library overhaul introducing configurable payment processors and email settings
The core \lib/pay.rb\ file has been significantly expanded to replace the previous placeholder with a fully configured payment gateway library. This change introduces autoloaded support for multiple payment processors including Stripe, Braintree, Paddle Billing, Paddle Classic, and Lemon Squeezy, alongside a FakeProcessor for testing. It adds extensive configuration options via \mattr\_accessor\ for business details (name, address, logo, support email), route mounting, default product/plan names, and granular control over email notifications (enabling/disabling specific emails like payment failures, receipts, and trial endings). The library now includes a \Pay.sync\ method to handle webhook synchronization across different providers and uses \Mail::Address\ for robust email handling. This represents a foundational shift from a stub to a feature-complete, configurable payment integration layer.
lib · high confidence
Pay models now inherit from a configurable parent class with a dedicated table prefix
The Pay gem's models now inherit from a configurable parent class (defined by \Pay.model\_parent\class\) instead of directly inheriting from \ActiveRecord::Base\, allowing for greater flexibility in model hierarchy. Additionally, a \pay\\ table name prefix is now automatically applied to all Pay models, ensuring database table isolation. A new \Pay::Routing\ concern has also been added to provide URL helper methods for mailers within the Pay context.
app/models · high confidence
Payment layout migrated to Tailwind CSS and Stripe v3
The payment confirmation page layout has been updated to use Tailwind CSS (v2.2) for styling and includes the Stripe.js v3 library, replacing the previous static stylesheet and JavaScript includes. This change also adds responsive viewport meta tags and updates the page title to include the business name, improving the visual presentation and mobile compatibility of the payment confirmation interface.
app/views/layouts · high confidence
Redesigned payment page with Stripe Elements and Tailwind CSS
The payment page has been completely rewritten to use Stripe Elements for secure card input and Tailwind CSS for styling. Users now see a modern, responsive interface that handles payment status updates (success, failure, cancellation) via a Stimulus controller, replacing the previous implementation.
app/views/pay/payments · high confidence
Secure payment page with configurable back navigation
The new payments controller displays payment details and now safely handles the 'back' navigation parameter by stripping query strings and fragments, preventing XSS and open-redirect vulnerabilities. It also rescues Stripe errors during the show action, redirecting users to the home page with an alert message if a payment intent lookup fails.
app/controllers/pay · high confidence
Stripe webhook handling refactored into dedicated classes
The Stripe webhook processing logic has been restructured from a monolithic handler into individual, dedicated classes (e.g., \ChargeSucceeded\, \SubscriptionUpdated\, \PaymentFailed\) located in \lib/pay/stripe/webhooks/\. This change improves maintainability and allows for more precise handling of specific Stripe events, such as syncing payment intents during checkout session completion or sending targeted emails for refunds and trial endings, while ensuring consistent support for Stripe Connect accounts via the \stripe\_account\ parameter.
lib/pay/stripe · high confidence
Test coverage
Add dummy app views for Braintree and Stripe payment examples; Add test dummy app views for payment examples; Added Paddle Classic demo controllers and views; Added Paddle Classic demo views for charges and subscriptions; Added Paddle Classic webhook and verification test fixtures; Added Stripe payment integration examples to the dummy application; Added comprehensive test suite for Stripe payment processor; Added controller tests for payment webhooks; Added dummy app controllers and helpers for testing payment flows; Added dummy application models for testing Pay integration; Added dummy controllers for LemonSqueezy charges and subscriptions; Added dummy view templates for Lemon Squeezy charges and subscriptions; Added mailer previews for payment notifications; Added model tests for Pay core entities; Added test coverage for Braintree payment integration; Added test coverage for ChargeSucceeded webhook handler; Added test coverage for Lemon Squeezy payment processor; Added test coverage for Paddle Billing integration; Added test coverage for Pay core components; Added test coverage for Stripe webhook handlers; Added test coverage for the FakeProcessor payment integration; Added test dummy migration for users, teams, and accounts; Added test support infrastructure for payment processors; Added test view for payment method display; Added tests for Braintree subscription webhook handlers; Added tests for Paddle Billing webhooks controller; Added tests for Paddle Classic webhook handlers; Added tests for Pay user mailer functionality; Added tests for customer sync behavior; Added tests for customer sync job; Added tests for the Pay webhook delegator; Added tests for webhook routes and Pay configuration; Test suite updates: new Pay initializer, Rails 5.0 defaults removal, and asset/session config tweaks; Update test/dummy app to use Turbo & importmaps; Updated dummy app layout with modern navigation and payment provider scripts; Updated test dummy app to use Turbo, importmaps, and modern Rails defaults; Updated test dummy database schema for Pay 3.0.
Dependencies
Added Appraisal gemfiles for Rails 7.0 through 8.1 and main
The gemfiles directory now includes dedicated Appraisal configurations for testing against Rails 7.0, 7.1, 7.2, 8.0, 8.1, and the Rails main branch. These files define the specific dependency sets (including Stripe v19, Paddle \~\> 2.6, and Lemon Squeezy \~\> 1.0) required to run the test suite against each supported Rails version.
gemfiles · high confidence
Pay 11.7.2: Rails 8.1 support and Stripe 19 integration
This release updates the gem to version 11.7.2, raising the minimum Rails requirement to 7.0.0 and adding explicit support for Rails 8.1.3.1. The Stripe dependency has been upgraded to version 19.0, and the gem now includes support for Lemon Squeezy (v1.1.0) and Paddle (v2.10). Development dependencies have been refreshed to include appraisal, mocha, standard, vcr, and webmock, while the dummy app integration now utilizes importmap-rails, stimulus-rails, and turbo-rails.
(dependencies) · high confidence
Housekeeping
Initial project scaffolding and configuration
This change introduces the foundational configuration and documentation for the Pay gem, including \.rubocop.yml\ and \.standard.yml\ for code style enforcement, an \Appraisals\ file to test against Rails 7.0 through 8.1 and main, and updated \README.md\ and \CHANGELOG.md\ files. It also refactors the \Rakefile\ to use \bundler/gem\_tasks\ and standardizes string quoting, while expanding \.gitignore\ to exclude dummy app artifacts and release files.
(repo-wide) · high confidence
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
How this codebase got here
Baseline
- First survey — no prior run to compare against. CAI 59.
Lenses
- Code Health 70
- Architecture 98
- Maturity 52
- Readiness 68
- Security 79
- Domain Modelling 62
- Accessibility 65
Changes since last survey
- 300 commits — 269 feature/other, 31 fixes
By area
- (root) — 110 commits
- app/models — 51 commits
- lib/pay — 32 commits
- (repo) — 28 commits
- test/vcr_cassettes — 10 commits
- docs/stripe — 9 commits
- test/pay — 9 commits
- test/dummy — 7 commits
- app/controllers — 6 commits
- docs/1_installation.md — 6 commits
- .github/workflows — 4 commits
- test/fixtures — 3 commits
- app/views — 2 commits
- docs/2_configuration.md — 2 commits
- docs/paddle_billing — 2 commits
- test/models — 2 commits
- test/pay_test.rb — 2 commits
- .github/dependabot.yml — 1 commit
- app/mailers — 1 commit
- db/migrate — 1 commit
Notable commits
- fix: Add LemonSqueezy webhook metadata to objects Fixes #1107
- fix: Add retries to paddle billing model sync. Fixes #1095
- fix: Clean up URLs and fix change_quantity for LemonSqueezy subscriptions
- fix: Fix LS subscription invoice sync
- fix: Fix LemonSqueezy mappings
- fix: Fix LemonSqueezy sync subscription method (#1087)
- fix: Fix api_record call
- fix: Fix balance_transaction syncing
- fix: Fix deprecation for frozen string literal
- fix: Fix deprecation warning
- fix: Fix documentation. Closes #1191
- fix: Fix frozen string literals, standardize, and update gems
- fix: Fix invalid fixture reference
- fix: Fix line item unit amount for Stripe Invoices
- fix: Fix make_default! database sync in STI payment methods
- fix: Fix method redefined warning for support_email (#1214)
- fix: Fix owner association and version bump
- fix: Fix readme (#1163)
- fix: Fix receipts for non-Stripe charges and version bump
- fix: Fix retry_failed_payment for Stripe. Closes #1084
- …and 280 more
Architecture
- 0 containers · 1 bounded contexts · 0 dependency edges (baseline)
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
Survey your own repository
pay-rails/pay was measured the same way every project in this corpus was: the same rubric, at a pinned commit, with the result published in full. Point a surveyor at a repository you know and see whether you agree with it.
About this page
- The score is its most recent published measurement, taken on 19 September 2026 at a pinned commit. It is not a live figure and does not change until the project is measured again.
- Measured at commit 03c0d9d7a683212a3875c3f014c5cf49186ba839 — the exact code this score is about.
- Scored under rubric-2026.09.15 — the same rubric and the same method as every other entry in this index.
- Measured by watchdog.canine.dev using codehealth-analyzer preprod-13a154b7f5d1.