Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
164: Building a Subscription Billing System with Stripe
How do I actually link my local users to Stripe customers?
This is where most people overthink things. You don't want to mirror the entire Stripe database in your own PostgreSQL or MySQL instance—that's a synchronization nightmare. Instead, just treat Stripe as the source of truth for billing and keep a reference pointer in your users table.
I always add a stripe_customer_id string column to my User model. When a user signs up for a plan, you check if they already have a customer ID. If they don't, you hit the Stripe API, create the customer, and save that ID locally. It looks something like this:
def stripe_customer
if user.stripe_customer_id.present?
Stripe::Customer.retrieve(user.stripe_customer_id)
else
customer = Stripe::Customer.create(
email: user.email,
name: user.full_name
)
user.update(stripe_customer_id: customer.id)
customer
end
end
Simple, right? Now you have a bridge between your application's identity and Stripe's billing identity without duplicating sensitive data.
Should I build my own credit card forms or use Stripe Checkout?
Unless you have a massive team of security engineers and a lot of free time to deal with PCI compliance, use Stripe Checkout. Seriously. I've seen developers spend weeks building custom payment forms only to realize they're accidentally handling raw card data on their servers, which is a legal and security minefield.
With Checkout, you just redirect the user to a Stripe-hosted page. They handle the UI, the validation, and the security. Once the payment is done, they send the user back to your success_url. Here is how you'd trigger that redirect in a Ruby controller:
session = Stripe::Checkout::Session.create({
customer: current_user.stripe_customer_id,
payment_method_types: ['card'],
line_items: [{
price: 'price_H5ggY9IzS2', # The ID of your product in the Stripe Dashboard
quantity: 1,
}],
mode: 'subscription',
success_url: 'https://your-app.com/success',
cancel_url: 'https://your-app.com/billing',
})
redirect_to session.url, allow_other_host: true
It’s cleaner, it’s safer, and it converts better because users trust the Stripe brand more than a random form on a new site.
How do I handle "payment failed" or "subscription cancelled" without constantly polling the API?
You use webhooks. If you try to check the subscription status every time a user loads a page, you'll hit API rate limits and slow your app to a crawl. Instead, you tell Stripe: "Hey, whenever something happens to a subscription, send an HTTP POST request to this specific URL on my server."
You'll need a dedicated controller endpoint that doesn't require CSRF protection (since Stripe is the one calling it). I usually create a WebhooksController. The trick is to verify the signature so you know the request actually came from Stripe and not some random person trying to grant themselves a free subscription.
def create
payload = request.body.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
endpoint_secret = ENV['STRIPE_WEBHOOK_SECRET']
event = Stripe::Webhook.construct_event(payload, sig_header, endpoint_secret)
case event.type
when 'customer.subscription.deleted'
subscription = event.data.object
user = User.find_by(stripe_customer_id: subscription.customer)
user.update(active_subscription: false)
when 'invoice.payment_failed'
# This is where you'd trigger an email to the user saying "Your card was declined!"
NotificationService.payment_failed(event.data.object.customer)
end
head :ok
rescue Stripe::SignatureVerificationError
head :bad_request
end
This turns your app from a "pull" system into a "push" system. Your database stays updated in near real-time, and your users get immediate feedback if their payment fails.
📋 Practical Task
Implement the DevDocs Pro Subscription Webhook
You are building a subscription service called DevDocs Pro. You have already set up the User model with a stripe_customer_id and a boolean premium_member column.
Your task is to create a StripeWebhooksController with a create action that handles the invoice.paid event. When this event is received, the code should:
- Extract the
customerID from the event object. - Find the corresponding User in your database.
- Update that user's
premium_memberattribute totrue. - Return a
200 OKresponse to Stripe to acknowledge receipt.
Make sure to include a basic rescue block for Stripe::SignatureVerificationError to ensure the app doesn't crash when receiving malformed requests.
There are no comments for now.