E-commerce Flow
E-commerce Flow pattern for Ruby.
End-to-end recipe for a typical e-commerce checkout: capture a tokenised card from the browser, charge it, persist the order, and react to the post-charge webhook.
Goal
Take a customer through a one-shot card payment with a server-side charge:
- Browser tokenises the card with BasisTheory and posts the token to our server.
- Server creates (or retrieves) a customer.
- Server attaches the tokenised card as a payment instrument.
- Server creates a transfer for the order amount.
- Server records the order locally.
- Webhook handler confirms the payment when
payment.createdarrives.
Implementation
require "easy_sdk"
client = EasyLabs::Client.new(api_key: ENV.fetch("EASY_API_KEY"))
# 1. Browser POST: { token: "tok_…", email:, first_name:, last_name:, amount_cents: }
def checkout(client, params)
customer = client.customers.create(
first_name: params[:first_name],
last_name: params[:last_name],
email: params[:email]
)
card = client.payment_instruments.create(
tokenId: params[:token],
identityId: customer[:id],
type: "PAYMENT_CARD",
name: "Card on file"
)
transfer = client.transfers.create(
amount: params[:amount_cents],
currency: "USD",
source: card[:id],
tags: { order_id: SecureRandom.uuid }
)
Order.create!(
customer_id: customer[:id],
transfer_id: transfer[:id],
amount: params[:amount_cents],
status: "pending"
)
{ transfer_id: transfer[:id], status: "pending" }
rescue EasyLabs::InvalidRequestError => e
# Bad input from the browser (e.g. expired token).
{ error: e.message, code: e.code }
rescue EasyLabs::Error => e
Rails.logger.error("[easy] #{e.status} #{e.code}: #{e.message}")
raise
end
# Webhook receiver
post "/webhooks/easy" do
event = EasyLabs::Webhooks.construct_event(
payload: request.body.read,
signature: request.headers["X-Easy-Webhook-Signature"],
secret: ENV.fetch("EASY_WEBHOOK_SECRET")
)
case event[:type]
when "payment.created"
transfer_id = event.dig(:data, :id)
Order.where(transfer_id: transfer_id).update_all(status: "paid")
end
status 204
endTradeoffs
- Server-side
transfers.createvs. checkout sessions. This recipe charges the customer synchronously. For a hosted page, swap steps 2-4 for Payment Links. For an embedded widget, use Embedded Checkout — both return a URL/secret you hand to the browser instead of charging inline. - Customer dedup. This recipe always creates a new customer.
Real-world apps should look up by email first and reuse the existing
identity to avoid duplicate
cust_…rows. - Idempotency. The transfer is fire-and-forget. If the request
times out, you may double-charge. Wrap the call in your own retry
layer keyed on a stable
order_iduntil the SDK exposes per-resourceidempotency_key:arguments. - Webhook latency. Don't render "thanks for your order" off the
webhook — render it off the synchronous
transfers.createresponse and use the webhook only for downstream effects (fulfillment, receipts, accounting).