Refunds
Refunds pattern for Ruby.
Practical recipe for issuing refunds from your support tooling and keeping local state consistent with refund webhooks.
Goal
A support agent can:
- Look up a charge by ID.
- Issue a full or partial refund with a reason tag.
- Trust that the refund eventually settles, with
refund.updatedwebhooks keeping your DB in sync.
Implementation
Issue the refund
The Easy API expresses refunds as reversals on the original
transfer — there is no top-level Refunds.create. The SDK exposes them
via client.transfers.create_refund.
require "easy_sdk"
client = EasyLabs::Client.new(api_key: ENV.fetch("EASY_API_KEY"))
def refund_charge(client, transfer_id:, amount_cents: nil, reason:)
charge = client.transfers.retrieve(transfer_id)
amount = amount_cents || charge[:amount] # default to full refund
if amount > charge[:amount]
raise ArgumentError, "Refund amount exceeds charge amount"
end
client.transfers.create_refund(
charge[:id],
refund_amount: amount,
tags: {
reason: reason,
issued_by: Current.agent.email,
issued_at: Time.now.iso8601
}
)
rescue EasyLabs::InvalidRequestError => e
# 400/422 — typically because the charge isn't refundable yet.
{ error: e.message, code: e.code }
endRead refund history
The parent transfer carries the full reversals collection — no separate list call needed.
charge = client.transfers.retrieve("tr_…")
charge[:reversals]&.each do |refund|
puts "#{refund[:id]} — #{refund[:amount]} #{refund[:state]}"
endSync from webhooks
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 "refund.created", "refund.updated"
Refund.upsert_by_easy_id(event[:data])
end
status 204
endTradeoffs
- Full vs. partial refunds.
refund_amountis in minor units — always pass the full charge amount for a full refund rather than relying on a "missing → full" default. Explicit is safer in support workflows. - Multiple partial refunds. You can issue multiple partial refunds against the same transfer until the cumulative amount matches the original charge. The API rejects further refunds beyond that.
- Tags as audit trail. Stash the agent identifier and reason in
tags— they're immutable on the refund record and showing them in your support tool gives you a free audit trail. - Webhook latency.
transfers.create_refundreturns aPENDING-shaped refund; final settlement (and chargeback resolution) comes throughrefund.updated. Don't tell the customer "refund complete" off the create call. - Subscription / invoice refunds. For refunding a subscription invoice, refund the underlying transfer here — there is no separate "void invoice + refund" combo on the SDK.