Easy Labs
SDKsRubyExamples

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:

  1. Browser tokenises the card with BasisTheory and posts the token to our server.
  2. Server creates (or retrieves) a customer.
  3. Server attaches the tokenised card as a payment instrument.
  4. Server creates a transfer for the order amount.
  5. Server records the order locally.
  6. Webhook handler confirms the payment when payment.created arrives.

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
end

Tradeoffs

  • Server-side transfers.create vs. 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_id until the SDK exposes per-resource idempotency_key: arguments.
  • Webhook latency. Don't render "thanks for your order" off the webhook — render it off the synchronous transfers.create response and use the webhook only for downstream effects (fulfillment, receipts, accounting).

On this page