Class: CloverSandboxSimulator::Services::Clover::RefundService

Inherits:
BaseService
  • Object
show all
Defined in:
lib/clover_sandbox_simulator/services/clover/refund_service.rb

Overview

Manages Clover refunds for payments Supports full and partial refunds with various refund reasons

Refund strategies (in priority order):

  1. Ecommerce API (POST /v1/refunds) - for card payments created via Ecommerce API

  2. Voided line items - for non-card payments (Cash, Check, Gift Card)

  3. Platform API - if available (usually read-only in sandbox)

  4. Simulated refund - fallback for testing

See: docs.clover.com/dev/reference/createrefund

Constant Summary collapse

REFUND_REASONS =

Valid refund/void reasons (matching Clover’s void reasons)

%w[
  customer_request
  quality_issue
  wrong_order
  duplicate_charge
].freeze
VOID_REASONS =

Clover void reasons (from merchant settings)

[
  "Returned goods",
  "Accidental charge",
  "Fraudulent charge",
  "Customer complaint",
  "Order not correct",
  "Out of stock",
  "Damaged goods",
  "Goods not received"
].freeze
REASON_MAPPING =

Map our refund reasons to Clover void reasons

{
  "customer_request" => "Customer complaint",
  "quality_issue" => "Damaged goods",
  "wrong_order" => "Order not correct",
  "duplicate_charge" => "Accidental charge"
}.freeze
ECOMMERCE_REASONS =

Ecommerce refund reasons

%w[
  requested_by_customer
  duplicate
  fraudulent
].freeze
ECOMMERCE_REASON_MAPPING =

Map our reasons to Ecommerce API reasons

{
  "customer_request" => "requested_by_customer",
  "quality_issue" => "requested_by_customer",
  "wrong_order" => "requested_by_customer",
  "duplicate_charge" => "duplicate"
}.freeze

Instance Attribute Summary

Attributes inherited from BaseService

#config, #logger

Instance Method Summary collapse

Constructor Details

#initialize(config: nil) ⇒ RefundService

Returns a new instance of RefundService.



60
61
62
63
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 60

def initialize(config: nil)
  super
  @ecommerce_service = nil
end

Instance Method Details

#create_ecommerce_refund(charge_id:, amount: nil, reason: "customer_request") ⇒ Hash?

Create a refund for an Ecommerce charge (card payment) This is the preferred method for card payments

Parameters:

  • charge_id (String)

    The Ecommerce charge ID

  • amount (Integer, nil) (defaults to: nil)

    Refund amount in cents (nil for full refund)

  • reason (String) (defaults to: "customer_request")

    Reason for refund

Returns:

  • (Hash, nil)

    Refund response



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 135

def create_ecommerce_refund(charge_id:, amount: nil, reason: "customer_request")
  ecommerce_reason = ECOMMERCE_REASON_MAPPING[reason] || "requested_by_customer"

  if amount
    logger.info "Creating Ecommerce partial refund for charge #{charge_id}: $#{amount / 100.0}"
  else
    logger.info "Creating Ecommerce full refund for charge #{charge_id}"
  end

  ecommerce_service.create_refund(
    charge_id: charge_id,
    amount: amount,
    reason: ecommerce_reason
  )
end

#create_full_refund(payment_id:, reason: "customer_request") ⇒ Hash?

Create a full refund for a payment

Parameters:

  • payment_id (String)

    The payment ID to refund

  • reason (String) (defaults to: "customer_request")

    Reason for refund

Returns:

  • (Hash, nil)

    Refund response



378
379
380
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 378

def create_full_refund(payment_id:, reason: "customer_request")
  create_refund(payment_id: payment_id, amount: nil, reason: reason)
end

#create_partial_refund(payment_id:, amount:, reason: "customer_request") ⇒ Hash?

Create a partial refund for a payment

Parameters:

  • payment_id (String)

    The payment ID to refund

  • amount (Integer)

    Refund amount in cents

  • reason (String) (defaults to: "customer_request")

    Reason for refund

Returns:

  • (Hash, nil)

    Refund response

Raises:

  • (ArgumentError)


388
389
390
391
392
393
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 388

def create_partial_refund(payment_id:, amount:, reason: "customer_request")
  raise ArgumentError, "Amount is required for partial refund" unless amount
  raise ArgumentError, "Amount must be positive" unless amount.positive?

  create_refund(payment_id: payment_id, amount: amount, reason: reason)
end

#create_refund(payment_id:, amount: nil, reason: "customer_request", charge_id: nil) ⇒ Hash?

Create a refund for a payment Tries multiple strategies based on payment type and API availability

Parameters:

  • payment_id (String)

    The payment ID to refund (Platform API) or charge ID (Ecommerce)

  • amount (Integer, nil) (defaults to: nil)

    Refund amount in cents (nil for full refund)

  • reason (String) (defaults to: "customer_request")

    Reason for refund (default: customer_request)

  • charge_id (String, nil) (defaults to: nil)

    Ecommerce charge ID (if known, uses Ecommerce API directly)

Returns:

  • (Hash, nil)

    Refund response or nil on failure



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 103

def create_refund(payment_id:, amount: nil, reason: "customer_request", charge_id: nil)
  unless REFUND_REASONS.include?(reason)
    logger.warn "Unknown refund reason '#{reason}', using 'customer_request'"
    reason = "customer_request"
  end

  if amount
    logger.info "Creating partial refund for payment #{payment_id}: $#{amount / 100.0} (#{reason})"
  else
    logger.info "Creating full refund for payment #{payment_id} (#{reason})"
  end

  # Try multiple approaches for sandbox compatibility
  response = try_ecommerce_refund(charge_id || payment_id, amount, reason) ||
             try_void_line_items(payment_id, amount, reason) ||
             try_platform_refund(payment_id, amount, reason) ||
             simulate_refund(payment_id, amount, reason)

  if response && response["id"]
    logger.info "Refund successful: #{response["id"]} - $#{(response["amount"] || 0) / 100.0}"
  end

  response
end

#fetch_refunds(limit: nil, offset: nil) ⇒ Array<Hash>

Fetch all refunds for the merchant

Parameters:

  • limit (Integer, nil) (defaults to: nil)

    Maximum number of refunds to return

  • offset (Integer, nil) (defaults to: nil)

    Offset for pagination

Returns:

  • (Array<Hash>)

    Array of refund objects



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 70

def fetch_refunds(limit: nil, offset: nil)
  logger.info "Fetching refunds..."

  params = {}
  params[:limit] = limit if limit
  params[:offset] = offset if offset

  response = request(:get, endpoint("refunds"), params: params.empty? ? nil : params)
  refunds = response&.dig("elements") || []

  logger.info "Found #{refunds.size} refunds"
  refunds
end

#get_refund(refund_id) ⇒ Hash?

Get a specific refund by ID

Parameters:

  • refund_id (String)

    The refund ID

Returns:

  • (Hash, nil)

    Refund object or nil if not found



88
89
90
91
92
93
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 88

def get_refund(refund_id)
  logger.info "Fetching refund: #{refund_id}"

  response = request(:get, endpoint("refunds/#{refund_id}"))
  response
end

#random_reasonString

Generate a random refund reason

Returns:

  • (String)

    Random refund reason



398
399
400
# File 'lib/clover_sandbox_simulator/services/clover/refund_service.rb', line 398

def random_reason
  REFUND_REASONS.sample
end