Class: Spree::BankPayments::ApplyTransfer

Inherits:
Object
  • Object
show all
Defined in:
app/services/spree/bank_payments/apply_transfer.rb

Overview

The single place money actually moves. IngestTransfer calls this with no applied_by for automatic application; the admin hand-match queue (Task 13) calls it with the acting admin user. One transaction, one path — no second implementation of "what applying a transfer means".

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(transfer:, payment_session:, applied_by: nil) ⇒ ApplyTransfer

Returns a new instance of ApplyTransfer.



12
13
14
15
16
# File 'app/services/spree/bank_payments/apply_transfer.rb', line 12

def initialize(transfer:, payment_session:, applied_by: nil)
  @transfer = transfer
  @payment_session = payment_session
  @applied_by = applied_by
end

Class Method Details

.callObject



8
9
10
# File 'app/services/spree/bank_payments/apply_transfer.rb', line 8

def self.call(...)
  new(...).call
end

Instance Method Details

#callObject



18
19
20
21
22
23
24
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
60
61
62
63
64
65
66
67
68
# File 'app/services/spree/bank_payments/apply_transfer.rb', line 18

def call
  ActiveRecord::Base.transaction do
    # Payment completes before the session. Spree::PaymentSession uses
    # publishes_lifecycle_events, which publishes synchronously inside
    # this transaction — completing the session first would let a
    # "session completed" event (plausibly a customer receipt) escape
    # before we know the payment itself will actually complete. If
    # payment.complete! raises, the DB rolls back but that event
    # cannot be recalled.
    payment = payment_session.find_or_create_payment!

    # find_or_create_payment! (Spree core) stamps the *session's*
    # amount onto the payment -- correct on the automatic path, where
    # IngestTransfer only ever gets here on an exact amount/currency
    # match, so this is a no-op there. On the manual admin path (Task
    # 13) an admin can confirm applying a transfer to a session whose
    # amount doesn't match what actually arrived; crediting the
    # session's amount in that case would mark the order 'paid' for
    # money it never received. Re-stamp the payment with what the
    # transfer says actually arrived before completing it, so Spree's
    # own payment_state computation reflects the real balance
    # (balance_due/credit_owed) instead of a false 'paid'.
    #
    # Only mutate while the payment is still editable (pre-completion):
    # Payment#editable? is checkout/pending, and completing flips
    # payment_total via the state machine, so touching amount after
    # completion would need a second recalculation this class doesn't
    # own. `find_or_create_payment!` always returns either a payment it
    # just created (checkout state) or one it found by the same
    # response_code -- if that existing payment is already completed
    # (shouldn't happen: the `applied?` guard upstream prevents
    # re-applying), leave its amount alone rather than mutate a
    # finalized record.
    if !payment.completed? && payment.amount != transfer.amount
      payment.update!(amount: transfer.amount)
    end

    payment.complete! unless payment.completed?
    payment_session.complete! unless payment_session.completed?

    attrs = { state: 'applied', payment_session: payment_session }
    if applied_by
      attrs[:applied_by_id] = applied_by.id
      attrs[:applied_at] = Time.current
    end

    transfer.update!(attrs)
  end

  transfer
end