# Rails Payments in 2026: Beyond Stripe

> Modern payment integration for Ruby on Rails apps. Move beyond Stripe to a Merchant of Record for simpler billing, automatic tax compliance, and global payment support.
- **Author**: Ayush Agarwal
- **Published**: 2026-03-29
- **Category**: Payments, Developer Tools, How-To
- **URL**: https://dodopayments.com/blogs/rails-payments-beyond-stripe

---

For over a decade, the default answer for any Ruby on Rails developer looking to accept payments was Stripe. The combination was legendary. Rails provided the productivity and convention, while Stripe provided the API-first payment infrastructure. It was a match made in developer heaven. You would pull in a gem like `pay` or `stripe-ruby`, set up a few webhooks, and you were in business.

But as we move into 2026, the landscape has shifted. The "simple" payment integration of 2015 has evolved into a complex web of global tax compliance, multi-region billing regulations, and sophisticated revenue recovery requirements. What used to be a weekend project now requires a dedicated team to manage the edge cases of VAT in the EU, sales tax in the US, and the ever-changing landscape of global payment methods.

For Rails developers who value the "convention over configuration" philosophy, the traditional payment gateway model is starting to feel like a lot of configuration and very little convention. This is where the Merchant of Record (MoR) model comes in. By moving beyond Stripe to an MoR like Dodo Payments, you can offload the entire burden of tax, compliance, and billing complexity, allowing you to focus on what Rails does best: building your application.

## The Hidden Complexity of the Gateway Model

When you use a traditional payment gateway like Stripe, you are the "Merchant of Record." This means you are legally responsible for every transaction. While Stripe handles the technical processing of the credit card, you are responsible for:

> The biggest mistake founders make when setting up payments is treating it as a solved problem. You wire up a gateway, add a checkout button, and assume you are done. Six months later, you are dealing with tax notices, failed renewals, and checkout abandonment from missing local payment methods.
>
> \- Ayush Agarwal, Co-founder & CPTO at Dodo Payments

- **Global Tax Compliance**: Calculating, collecting, and remitting sales tax, VAT, and GST in every jurisdiction where you have customers.
- **Billing Infrastructure**: Building and maintaining a billing portal where users can update their cards, view invoices, and manage subscriptions.
- **Dunning and Recovery**: Handling failed payments, expired cards, and the complex logic of when to retry a charge.
- **Compliance and Security**: Managing PCI compliance, KYC (Know Your Customer) requirements, and fraud prevention.

For a small team or a solo founder, this is a massive amount of overhead. You started your Rails app to solve a specific problem, not to become an expert in international tax law.

## Why Rails Developers Love the MoR Model

The Ruby on Rails philosophy is built on the idea that developers should focus on their unique business logic while the framework handles the "boring" stuff. We use ActiveRecord so we don't have to write raw SQL. We use ActionMailer so we don't have to manage SMTP servers.

A Merchant of Record is the logical extension of this philosophy for payments. Instead of being the merchant yourself, the MoR acts as the legal seller of your software. They handle the tax, the compliance, and the billing portal. You just provide the product and the price.

This alignment with Rails' core values is why more developers are looking for [Stripe alternatives](https://dodopayments.com/blogs/stripe-alternatives) that offer a more integrated, "batteries-included" experience.

## The Architecture of a Modern Rails Payment Integration

In a traditional setup, your Rails app sits in the middle of a complex web of services. With an MoR, the architecture is significantly simplified.

```mermaid
flowchart TD
    User[User] -->|1. Clicks Buy| Rails[Rails App]
    Rails -->|2. Creates Session| MoR[Dodo Payments]
    MoR -->|3. Handles Checkout| User
    MoR -->|4. Processes Tax & Payment| Gateway[Global Banks]
    MoR -->|5. Sends Webhook| Rails
    Rails -->|6. Provisions Access| User
```

The MoR handles the heavy lifting of the transaction, including the [embedded payments](https://dodopayments.com/blogs/embedded-payments-saas) experience, while your Rails app remains the source of truth for user access and application state.

## Step-by-Step: Integrating Dodo Payments in Rails

Let's walk through a modern integration. We'll move from a clean Rails 8 app to a fully functional, tax-compliant subscription system.

### 1. Setup and Configuration

First, we need to add the Dodo Payments gem to our `Gemfile`. While you can use a generic HTTP client, the official SDK provides better type safety and convenience.

```ruby
# Gemfile
gem "dodopayments"
```

Run `bundle install` to pull in the dependency. Next, we'll set up an initializer to configure our client. We'll use Rails credentials to keep our API keys secure.

```ruby
# config/initializers/dodo_payments.rb
require "dodopayments"

DODO_CLIENT = Dodopayments::Client.new(
  bearer_token: Rails.application.credentials.dig(:dodo, :api_key),
  environment: Rails.env.production? ? "live_mode" : "test_mode"
)
```

### 2. Creating Your Products

Before you can accept payments, you need to define your products in the Dodo Payments dashboard. You can choose between one-time payments or various [subscription pricing models](https://dodopayments.com/blogs/subscription-pricing-models). Once created, you'll get a `product_id` (e.g., `pdt_123`).

### 3. The Checkout Flow

In Rails, we want to keep our controllers thin. We'll create a simple `CheckoutsController` to handle the creation of payment sessions.

```ruby
# app/controllers/checkouts_controller.rb
class CheckoutsController < ApplicationController
  before_action :authenticate_user!

  def create
    # We create a checkout session for a specific product
    session = DODO_CLIENT.checkout_sessions.create(
      product_cart: [
        { product_id: params[:product_id], quantity: 1 }
      ],
      customer: {
        email: current_user.email,
        name: current_user.full_name
      },
      return_url: root_url,
      payment_link: true
    )

    # Redirect the user to the hosted checkout page
    redirect_to session.checkout_url, allow_other_host: true
  rescue Dodopayments::Errors::APIError => e
    flash[:error] = "Something went wrong: #{e.message}"
    redirect_back fallback_location: root_path
  end
end
```

In your view, you can now add a simple "Buy Now" button:

```erb
<%# app/views/plans/index.html.erb %>
<div class="plan-card">
  <h3>Pro Plan</h3>
  <p>$29/month</p>
  <%= button_to "Subscribe Now", checkouts_path(product_id: "pdt_pro_plan"), method: :post, class: "btn-primary" %>
</div>
```

### 4. Handling Webhooks

Webhooks are the most critical part of any payment integration. They ensure that your app stays in sync with the payment state, even if the user closes their browser during the checkout process.

We'll create a dedicated controller to handle incoming events from Dodo Payments.

```ruby
# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    payload = request.body.read
    sig_header = request.headers["X-Dodo-Signature"]

    begin
      # Verify the webhook signature to ensure it's actually from Dodo
      event = DODO_CLIENT.webhooks.construct_event(
        payload,
        sig_header,
        Rails.application.credentials.dig(:dodo, :webhook_secret)
      )
    rescue JSON::ParserError, Dodopayments::Errors::SignatureVerificationError
      return head :bad_request
    end

    # Process the event asynchronously to avoid blocking the webhook response
    ProcessWebhookJob.perform_later(event.to_h)

    head :ok
  end
end
```

### 5. Asynchronous Processing with ActiveJob

Processing payment events can involve database writes, sending emails, or calling external APIs. It's best practice in Rails to handle this in a background job.

```ruby
# app/jobs/process_webhook_job.rb
class ProcessWebhookJob < ApplicationJob
  queue_as :default

  def perform(event_data)
    case event_data[:type]
    when "subscription.created"
      handle_subscription_created(event_data[:data])
    when "subscription.cancelled"
      handle_subscription_cancelled(event_data[:data])
    when "payment.succeeded"
      handle_payment_succeeded(event_data[:data])
    end
  end

  private

  def handle_subscription_created(subscription)
    user = User.find_by(email: subscription[:customer][:email])
    return unless user

    user.update!(
      subscription_id: subscription[:id],
      plan_id: subscription[:product_id],
      status: :active
    )
  end

  def handle_subscription_cancelled(subscription)
    user = User.find_by(subscription_id: subscription[:id])
    user&.update!(status: :cancelled)
  end
end
```

### 6. Managing Access with Concerns

To keep your `User` model clean, you can use a Rails Concern to handle subscription-related logic. This makes it easy to check if a user has access to specific features.

```ruby
# app/models/concerns/subscribable.rb
module Subscribable
  extend ActiveSupport::Concern

  included do
    enum :status, { inactive: 0, active: 1, cancelled: 2, past_due: 3 }
  end

  def subscribed?
    active? || past_due?
  end

  def pro?
    subscribed? && plan_id == "pdt_pro_plan"
  end
end

# app/models/user.rb
class User < ApplicationRecord
  include Subscribable
end
```

Now, in your controllers or views, checking for access is as simple as:

```ruby
def show
  @project = Project.find(params[:id])
  unless current_user.pro?
    redirect_to plans_path, alert: "Please upgrade to Pro to access this feature."
  end
end
```

### 8. Global Payment Methods and Conversion

One of the biggest advantages of moving beyond Stripe is the native support for global payment methods. While Stripe supports many, an MoR like Dodo Payments often provides a more localized experience out of the box.

For a Rails app targeting a global audience, accepting only credit cards is a recipe for high churn. In many parts of the world, credit cards are not the primary way people pay for software:

- **Europe**: iDEAL (Netherlands), Bancontact (Belgium), and SEPA Direct Debit are essential.
- **Brazil**: Pix has become the dominant payment method.
- **India**: UPI is the standard for digital transactions.

When you use Dodo Payments, these methods are automatically enabled based on the customer's location. Your Rails app doesn't need any extra logic to handle a Pix transaction versus a Visa card. The webhook payload remains consistent, and the MoR handles the specific settlement logic for each method.

### 9. Revenue Recovery and Dunning

Failed payments are an inevitable part of running a SaaS. A card might expire, a bank might decline a transaction for fraud prevention, or a user might simply have insufficient funds.

In a traditional gateway setup, you have to build the "dunning" logic yourself:
1. Detect the failed payment via webhook.
2. Send an automated email to the user.
3. Schedule a retry in 3 days.
4. If it fails again, send another email.
5. Eventually, cancel the subscription and update your database.

Dodo Payments handles this entire lifecycle for you. They manage the retry logic and the customer communication. Your Rails app only needs to listen for the final `subscription.cancelled` or `subscription.past_due` events to restrict access. This "set it and forget it" approach to revenue recovery can save you dozens of hours of development time and thousands of dollars in recovered revenue.

## A Detailed Migration Strategy from Stripe

If you're already running on Stripe, the thought of migrating can be daunting. However, the process is more straightforward than it seems. Here is a checklist for a successful migration:

1. **Audit Your Current Plans**: Map your Stripe Price IDs to Dodo Product IDs. Ensure the billing intervals (monthly, yearly) match exactly.
2. **Export Customer Data**: Use Stripe's export tools to get a list of your current subscribers.
3. **Set Up Parallel Webhooks**: Keep your Stripe webhook active while you build the Dodo integration. Your Rails app can handle events from both providers simultaneously during the transition period.
4. **The "New Signups First" Approach**: Start by routing all new signups through Dodo Payments. This allows you to test the integration in production with real users before migrating existing ones.
5. **Migrate Existing Subscriptions**: For existing users, you can either wait for their next billing cycle to move them or use a migration script to create corresponding subscriptions in Dodo.
6. **Decommission Stripe**: Once all users are migrated and no more recurring charges are expected on Stripe, you can safely disable your Stripe integration.

## Selling Software with License Keys

Many Rails apps aren't just SaaS; they sell downloadable software or plugins that require license keys. Dodo Payments has native support for [license keys](https://docs.dodopayments.com/features/license-keys), which is a game-changer for Rails devs.

Instead of building a `LicenseKey` model and a generation algorithm, you can let Dodo handle it. When a user buys your software, Dodo generates a key and includes it in the webhook payload. Your Rails app just needs to store it:

```ruby
def handle_payment_succeeded(payment)
  user = User.find_by(email: payment[:customer][:email])
  if payment[:license_key]
    user.update!(license_key: payment[:license_key])
    LicenseMailer.send_key(user).deliver_later
  end
end
```

You can then use the Dodo API to [validate license keys](https://docs.dodopayments.com/api-reference/licenses/activate) from within your application or even from a CLI tool.

The direct integration with an MoR is often simpler than the "abstracted" integration with a gateway. You're trading a complex gem with many dependencies for a lightweight SDK and a few well-defined webhook handlers.

## Tips for a Smooth Transition

- **Use Test Mode**: Dodo Payments provides a robust test environment. Use it to simulate successful payments, failed charges, and subscription cancellations.
- **Idempotency**: Ensure your webhook handlers are idempotent. If the same event is sent twice, your app should handle it gracefully without creating duplicate records.
- **Logging**: Log all incoming webhook payloads. This is invaluable for debugging when a payment doesn't seem to have triggered the expected state change in your app.
- **Migration**: If you're moving from Stripe, you can often export your customer data and import it into your new MoR. Check the [how to sell software online](https://dodopayments.com/blogs/how-to-sell-software-online) guide for tips on managing the transition.

## Conclusion: Convention Over Configuration for Payments

The Rails community has always valued tools that make us more productive. We choose Postgres over managing raw files, and we choose Sidekiq over manual thread management. Choosing a Merchant of Record is the same type of decision.

By moving beyond the manual labor of tax compliance and billing infrastructure, you're embracing the true spirit of Rails. You're choosing a [payments architecture for SaaS](https://dodopayments.com/blogs/payments-architecture-saas) that scales with you, without adding to your technical or legal debt.

Whether you're building a small side project or a global enterprise, the simplicity of an MoR allows you to focus on the code that matters. In 2026, the best payment integration is the one you don't have to manage yourself.

Ready to simplify your Rails billing? Check out [Dodo Payments](https://dodopayments.com) and see how we can help you [accept online payments](https://dodopayments.com/blogs/how-to-accept-online-payments) with zero tax overhead.

## FAQ

### Does Dodo Payments handle sales tax for my Rails app?

Yes. As a Merchant of Record, Dodo Payments automatically calculates, collects, and remits sales tax, VAT, and GST for every transaction. You don't need to register for tax in different countries or file separate returns for your software sales.

### Can I use Dodo Payments with existing Rails authentication gems like Devise?

Absolutely. Dodo Payments is agnostic to your authentication system. You simply pass the user's email and name to the checkout session, and use webhooks to update the user's status in your database once the payment is confirmed.

### How do I handle subscription upgrades and downgrades?

You can manage subscription changes through the Dodo Payments API or by directing users to the hosted billing portal. When a plan changes, a webhook event is sent to your Rails app, allowing you to update the user's `plan_id` and access levels accordingly.

### Is there a Ruby SDK for Dodo Payments?

Yes, there is an official [Ruby SDK](https://docs.dodopayments.com/developer-resources/sdks/ruby) that makes it easy to interact with the API. It handles authentication, request retries, and provides a clean interface for creating checkout sessions and managing subscriptions.

### What payment methods are supported?

Dodo Payments supports a wide range of global payment methods, including credit cards, Apple Pay, Google Pay, and various local payment options. This ensures a high conversion rate for your Rails app, regardless of where your customers are located.
---
- [More Payments articles](https://dodopayments.com/blogs/category/payments)
- [All articles](https://dodopayments.com/blogs)