One-time Payments
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.
Checkout Form
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_12345",
allow_promotion_codes: true,
return_url: checkout_return_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_return_url,
paddle__billing_custom_data: {order: @order.id}.to_json
} %>
Checkout Completed Controller
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_return_url to redirect the user after checkout. Jumpstart already provides this controller and uses it with subscription billing.
The Checkout::ReturnsController is responsible for syncing the checkout 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. See lib/jumpstart/app/controllers/checkout/returns_controller.rb for more details.
Fulfillment
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