Selling products as one-time purchases is also easy with Jumpstart Pro. This is a multi-step process that starts with presenting the customer with checkout and then fulfilling the purchase once completed.
The first step is to create a checkout form that handles one-time payments. We recommend adding metadata to the checkout for accessing later in fulfillment.
For Stripe, you'll create a Checkout Session and redirect or embed the form on the page (see subscriptions/forms/stripe
). We'll use hosted Stripe Checkout for our example.
def new
@checkout_session = payment_processor.checkout(
mode: "payment",
line_items: "price_1ILVZaKXBGcbgpbZQ26kgXWG",
allow_promotion_codes: true,
return_url: checkout_completed_url
)
redirect_to @checkout_session.url, allow_other_host: true
end
For Paddle Billing, checkout happens in JavaScript.
<%= tag.div data: {
controller: "paddle--billing",
paddle__billing_environment_value: Pay::PaddleBilling.environment,
paddle__billing_client_token_value: Pay::PaddleBilling.client_token,
paddle__billing_target: "form",
paddle__billing_email_value: current_account.email,
paddle__billing_items_value: [price_id: @price_id].to_json,
paddle__billing_success_url_value: checkout_completed_url,
paddle__billing_custom_data: {order: @order.id}.to_json
} %>
Webhooks are the most reliable way of fulfilling a purchase but redirects after completed purchase also help ensure fulfillment happens as the user finishes in case webhooks are delayed.
In the previous step, we used a checkout_completed_url
to redirect the user after checkout. You'll need to define a route for this and matching controller action to sync purchases.
class Checkout::CompletedController < ApplicationController
def show
# For Stripe
@charge = Pay::Stripe::Charge.sync_from_checkout_session(params[:session_id])
# For Paddle Billing
current_account.set_payment_processor :paddle_billing, processor_id: params[:user_id]
@transaction = Pay::PaddleBilling::Charge.sync_from_transaction(params[:transaction_id])
end
end
The controller is only responsible for triggering the sync to the database. This is the exact same process as webhooks and ensures that any delays with webhooks don't affect the customer's checkout flow.
Now that checkout is setup and redirects are handled, we can implement the fulfillment step.
# config/initializers/pay.rb
module PayChargeExtension
extend ActiveSupport::Concern
# Trigger fulfillment on create
after_commit :fulfill_purchase, on: :create
def fulfill_purchase
# Look up record from metadata
order = Order.find(metadata["course_id"])
# Skip if already processed
return if order.completed?
# Handle fulfillment and mark it as complete
order.update(status: :completed)
end
end
Rails.configuration.to_prepare do
Pay::Charge.include PayChargeExtension
end