Build an HTML paywall that triggers native in-app purchases.
Fetch products in your controller:
class PaywallsController < ApplicationController
def show
@annual = PurchaseKit::Product.find("prod_XXXXXXXX")
@monthly = PurchaseKit::Product.find("prod_YYYYYYYY")
end
end
You'll find product IDs in the PurchaseKit dashboard under Apps → [Your App] → Products.
<%= turbo_stream_from current_user.payment_processor %>
<%= purchasekit_paywall customer_id: current_user.payment_processor.id, success_path: dashboard_path do |paywall| %>
<%= paywall.plan_option product: @annual, selected: true do %>
Annual - <%= paywall.price %>/year
<% end %>
<%= paywall.plan_option product: @monthly do %>
Monthly - <%= paywall.price %>/month
<% end %>
<%= paywall.submit "Subscribe" %>
<% end %>
<%= button_to "Restore purchases", restore_purchases_path %>
<%= turbo_stream_from current_user.payment_processor %>
<%= purchasekit_paywall customer_id: current_user.payment_processor.id, success_path: dashboard_path do |paywall| %>
<div class="d-flex flex-column gap-2 mb-3">
<%= paywall.plan_option product: @annual, selected: true, class: "btn btn-outline-primary text-start" do %>
<strong>Annual</strong>
<span class="text-muted"><%= paywall.price %>/year</span>
<% end %>
<%= paywall.plan_option product: @monthly, class: "btn btn-outline-primary text-start" do %>
<strong>Monthly</strong>
<span class="text-muted"><%= paywall.price %>/month</span>
<% end %>
</div>
<%= paywall.submit "Subscribe", class: "btn btn-primary w-100" %>
<div class="text-center mt-2">
<%= button_to "Restore purchases", restore_purchases_path, class: "btn btn-link link-secondary small p-0" %>
</div>
<% end %>
| Option | Description |
|---|---|
customer_id: |
Required. Your Pay::Customer ID (use current_user.payment_processor.id) |
success_path: |
Where to redirect after purchase (defaults to root_path) |
| Method | Description |
|---|---|
plan_option(product:, selected:) |
Radio button and label for a plan |
price |
Localized price (must be inside plan_option block) |
submit(text) |
Submit button (disabled until prices load) |
Add a restore link that checks your server for an active subscription:
# config/routes.rb
post "restore_purchases", to: "subscriptions#restore"
# app/controllers/subscriptions_controller.rb
def restore
if current_user.subscribed?
redirect_to dashboard_path, notice: "Your subscription is active."
else
redirect_to paywall_path, alert: "No active subscription found."
end
end
The gem handles these events automatically:
| Event | Action |
|---|---|
subscription.created |
Creates Pay::Subscription, redirects user |
subscription.updated |
Updates status and period dates |
subscription.canceled |
Marks subscription as canceled |
subscription.expired |
Marks subscription as expired |
Add the native package to your Hotwire Native app: