Class: Clickwrap::ExternalAction

Inherits:
ApplicationRecord show all
Defined in:
lib/clickwrap/models/external_action.rb

Overview

The outbox row for an action Clickwrap cannot make atomic.

Stripe, an identity provider, a timestamp authority: none of them can join your database transaction, and pretending otherwise is how a provider timeout becomes either a fictional success or a second debit. So the local transaction commits a pending authorization and an idempotency key, the provider is called outside it, and the outcome is appended back idempotently.

unknown is a first-class state, not an error state. A timeout is not a failure — it is an absence of information — and the reconciliation task exists precisely to resolve those later rather than guessing now.

Constant Summary collapse

STATES =
%w[pending succeeded failed unknown].freeze

Instance Method Summary collapse

Instance Method Details

#pending?Boolean

Returns:

  • (Boolean)


36
# File 'lib/clickwrap/models/external_action.rb', line 36

def pending? = state == "pending"

#record_provider_failure!(reason:, provider_receipt: nil) ⇒ Object



49
50
51
# File 'lib/clickwrap/models/external_action.rb', line 49

def record_provider_failure!(reason:, provider_receipt: nil)
  resolve!("failed", provider_receipt: provider_receipt, failure_reason: reason)
end

#record_provider_outcome_unknown!(reason:) ⇒ Object

For the genuinely ambiguous case: the request may or may not have been carried out, and the honest record says so rather than picking one.



55
56
57
# File 'lib/clickwrap/models/external_action.rb', line 55

def record_provider_outcome_unknown!(reason:)
  resolve!("unknown", failure_reason: reason, resolved: false)
end

#record_provider_success_and_consume!(provider_receipt = nil) ⇒ Object

Each resolution is one idempotent local transaction. Calling it twice with the same outcome is a no-op; calling it with a different outcome after the action already resolved raises, because silently overwriting "succeeded" with "failed" would rewrite the record of what the provider told us.



43
44
45
46
47
# File 'lib/clickwrap/models/external_action.rb', line 43

def record_provider_success_and_consume!(provider_receipt = nil)
  resolve!("succeeded", provider_receipt: provider_receipt) do
    Lifecycle.consume_authorization!(event: event, because: "External action succeeded")
  end
end

#resolved?Boolean

Returns:

  • (Boolean)


37
# File 'lib/clickwrap/models/external_action.rb', line 37

def resolved? = %w[succeeded failed].include?(state)

#to_sObject



59
# File 'lib/clickwrap/models/external_action.rb', line 59

def to_s = "external action #{idempotency_key} (#{state})"