Easy Labs
SDKsRubyExamples

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:

  1. Look up a charge by ID.
  2. Issue a full or partial refund with a reason tag.
  3. Trust that the refund eventually settles, with refund.updated webhooks 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 }
end

Read 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]}"
end

Sync 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
end

Tradeoffs

  • Full vs. partial refunds. refund_amount is 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_refund returns a PENDING-shaped refund; final settlement (and chargeback resolution) comes through refund.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.

On this page