Class: SpreeCmCommissioner::OrderHolds::LockForPayment

Inherits:
Object
  • Object
show all
Extended by:
ServiceModuleThrowable
Includes:
Spree::ServiceModule::Base
Defined in:
app/services/spree_cm_commissioner/order_holds/lock_for_payment.rb

Overview

Locks all active holds for payment entry.

Idempotent: if holds are already locked (payment_locked_at is set), returns early. On first call:

- Validates the inventory hold is present and not finalized (read-only pre-check).
- Extends seat blocks that have less than PAYMENT_LOCK_MIN_DURATION remaining.
- Stamps payment_locked_at on each seat block.
- Transitions inventory hold to :payment_locked, stamps payment_locked_at, extends if needed.
- Resyncs order.hold_expires_at to the earliest expiry across all holds.

Called via PaymentDecorator#after_commit on payment creation — not on order state transition. BulkReleaseStalePaymentLockedJob (cron) is the sole cleanup for :payment_locked holds.

Returns failure with error_type: ‘HoldsExpiredError’ if expected holds are missing or finalized.

Constant Summary collapse

PAYMENT_LOCK_MIN_DURATION =
ENV.fetch('PAYMENT_LOCK_MIN_DURATION_IN_MINUTES', '5').to_i.minutes
HoldsExpiredError =
Class.new(StandardError)

Instance Method Summary collapse

Methods included from ServiceModuleThrowable

call!

Instance Method Details

#call(order:) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'app/services/spree_cm_commissioner/order_holds/lock_for_payment.rb', line 25

def call(order:)
  return success(order) unless order.should_manage_blocks? || order.should_hold_inventory?

  # Idempotent: skip if already locked for payment.
  return success(order) if already_locked?(order)

  held_blocks = nil
  inventory_hold = nil

  ActiveRecord::Base.transaction do
    # Validate inventory hold is lockable before extending seats.
    validate_inventory_hold!(order)

    held_blocks = extend_seat_blocks!(order)
    inventory_hold = lock_inventory_hold!(order)

    sync_hold_expires_at!(order, held_blocks, inventory_hold)
  end

  CmAppLogger.log(
    label: "#{self.class.name}#call",
    data: {
      order_id: order.id,
      extended_block_ids: held_blocks.map(&:id),
      inventory_hold_id: inventory_hold&.id,
      hold_expires_at: order.hold_expires_at
    }
  )

  success(order)
rescue StandardError => e
  error = { error_type: e.class.name.demodulize, order_id: order.id, message: e.message }
  CmAppLogger.error(label: "#{self.class.name}#call failed", data: error)
  failure(nil, e.message)
end