Class: Payment::Gateways::Echeck

Inherits:
BasePaymentGateway
  • Object
show all
Defined in:
app/services/payment/gateways/echeck.rb

Overview

Manual eCheck gateway. Heatwave runs eChecks through Forte
out-of-band, so this strategy records audit transactions and flips
Payment state without contacting an API. Each public action also
notifies AR via #notify_forte_pending so they can process the actual
transfer — guarded because FinancialsMailer#forte_payment_pending was
removed and may not be defined.

Defined Under Namespace

Classes: Result

Instance Method Summary collapse

Constructor Details

#initialize(payment, _delivery = nil) ⇒ Echeck

Returns a new instance of Echeck.



14
15
16
# File 'app/services/payment/gateways/echeck.rb', line 14

def initialize(payment, _delivery = nil)
  @payment = payment
end

Instance Method Details

#authorizePayment::Gateways::Echeck::Result

Stamp the payment with a masked account number, write a fake
authorization transaction, and notify AR. Forte processing
happens externally.



23
24
25
26
27
# File 'app/services/payment/gateways/echeck.rb', line 23

def authorize
  authorize_echeck

  Result.new(success: true, message: 'eCheck payment authorized (manual processing)')
end

#capture(capture_amount, _options = {}) ⇒ Payment::Gateways::Echeck::Result

Append a manual capture transaction and flip the Payment into
captured. Funds movement happens at Forte separately.

Parameters:

  • capture_amount (BigDecimal, Numeric)
  • options (Hash)

    reserved

Returns:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'app/services/payment/gateways/echeck.rb', line 64

def capture(capture_amount, _options = {})
  tx = OrderTransaction.new(
    action: 'capture',
    amount: (capture_amount * 100).to_i,
    success: true,
    reference: @payment.authorization_code || @payment.reference,
    message: 'Manual eCheck capture',
    params: { manual: true },
    test: !Rails.env.production?
  )
  @payment.transactions.push(tx)
  @payment.payment_captured!

  Rails.logger.info("#{Time.current}: eCheck capture of Payment ID #{@payment.id} Amount #{capture_amount}")

  Result.new(success: true, message: 'ok')
end

#create_receipt(invoice, amount, balance) ⇒ Payment::Gateways::Echeck::Result

Create a Receipt against invoice for amount. When the eCheck
captured more than the invoice balance, also notifies admin so AR
knows to issue a manual refund.

Parameters:

  • invoice (Invoice)
  • amount (BigDecimal, Numeric)
  • balance (BigDecimal, Numeric)

    invoice balance prior to this receipt

Returns:



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'app/services/payment/gateways/echeck.rb', line 169

def create_receipt(invoice, amount, balance)
  new_receipt = @payment.receipts.new(
    company: invoice.company,
    customer: invoice.customer,
    category: 'Echeck',
    amount: amount,
    reference: @payment.reference,
    currency: invoice.currency,
    payment: @payment,
    bank_account: invoice.company.,
    gl_date: Date.current,
    receipt_date: Date.current
  )
  new_receipt.receipt_details << ReceiptDetail.new(
    category: 'Invoice', invoice: invoice, amount: amount, gl_date: Date.current
  )
  begin
    new_receipt.save!
    Rails.logger.info("#{Time.current}: Created new receipt id: #{new_receipt.id}")
    if @payment.amount > balance
      Mailer.admin_notification(
        "Echeck amount already captured greater than balance on invoice id: #{invoice.id}, ref: #{invoice.reference_number}",
        "Amount already captured for eCheck payment id: #{@payment.id} was greater than order balance. eCheck amount: #{@payment.amount}, Invoice balance: #{balance}. Inform accounting who should process a refund."
      ).deliver
    end
    success = true
  rescue StandardError => e
    report_exception e, payment_id: @payment.id, message: "Unable to create new receipt for echeck"
    success = false
  end
  Result.new(success: success, receipt: new_receipt)
end

#purchase(receipt, _options = {}) ⇒ Payment::Gateways::Echeck::Result

One-shot "process this eCheck now" entry point used by
ReceiptsController#create when the operator submits the receipt
form with "Process Payment" checked. Forte moves the funds
out-of-band, so this authorizes the eCheck (masked reference, manual
authorization transaction, pending -> authorized), attaches and
persists the operator-entered Receipt, and returns the same
success / receipt contract that
CreditCard#purchase hands back to the controller.

Parameters:

  • receipt (Receipt)

    the receipt built from the payment form

  • _options (Hash) (defaults to: {})

    reserved for API parity with the CC gateway

Returns:



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/services/payment/gateways/echeck.rb', line 41

def purchase(receipt, _options = {})
  authorize_echeck

  receipt.reference = @payment.reference
  receipt.card_type = @payment.card_type
  receipt.payment   = @payment

  begin
    receipt.save!
    Rails.logger.info("#{Time.current}: eCheck purchase of Payment ID #{@payment.id} Amount #{@payment.amount} - created receipt id #{receipt.id}")
    Result.new(success: true, message: 'ok', receipt: receipt)
  rescue StandardError => e
    report_exception e, payment_id: @payment.id, message: 'eCheck authorized but receipt failed to save'
    Result.new(success: false, message: 'eCheck authorized but receipt failed to save', receipt: receipt)
  end
end

#refund(refund_amount, credit_memo) ⇒ Payment::Gateways::Echeck::Result

Issue a manual eCheck refund and create the matching Receipt
against credit_memo. Errors creating the receipt are reported to
AppSignal but the refund transaction itself still records.

Parameters:

  • refund_amount (BigDecimal, Numeric)
  • credit_memo (CreditMemo)

Returns:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'app/services/payment/gateways/echeck.rb', line 115

def refund(refund_amount, credit_memo)
  tx = OrderTransaction.new(
    action: 'refund',
    amount: (refund_amount * 100).to_i,
    success: true,
    reference: @payment.authorization_code || @payment.reference,
    message: 'Manual eCheck refund',
    params: { manual: true },
    test: !Rails.env.production?
  )
  @payment.transactions.push(tx)
  @payment.payment_refunded!

  Rails.logger.info("#{Time.current}: eCheck Refund of Payment ID #{@payment.id} Amount #{refund_amount}")

  new_receipt = Receipt.new(
    company: credit_memo.company,
    customer: credit_memo.customer,
    category: 'Echeck',
    amount: -refund_amount,
    reference: @payment.reference,
    card_type: @payment.card_type,
    currency: @payment.currency,
    payment: @payment,
    bank_account: @payment.invoice.company.,
    gl_date: Date.current,
    receipt_date: Date.current
  )
  new_receipt.receipt_details << ReceiptDetail.new(
    category: 'Credit Memo', credit_memo: credit_memo, amount: -refund_amount, gl_date: Date.current
  )

  receipt_ok = false
  begin
    new_receipt.save!
    receipt_ok = true
    Rails.logger.info("#{Time.current}: Created new receipt id: #{new_receipt.id}")
  rescue StandardError => e
    report_exception e, payment_id: @payment.id, message: "Payment refunded but no receipt"
  end

  notify_forte_pending

  Result.new(success: receipt_ok, receipt: new_receipt)
end

#void(_report_fraud = false) ⇒ Payment::Gateways::Echeck::Result

Mark an authorized eCheck voided. Records a manual void
transaction and re-notifies AR so they can stop the Forte run.

Parameters:

  • report_fraud (Boolean)

    retained for parity with CC gateway

Returns:



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'app/services/payment/gateways/echeck.rb', line 87

def void(_report_fraud = false)
  return Result.new(success: false) unless @payment.authorized?

  tx = OrderTransaction.new(
    action: 'void',
    success: true,
    reference: @payment.authorization_code || @payment.reference,
    message: 'Manual eCheck void',
    params: { manual: true },
    test: !Rails.env.production?
  )
  @payment.transactions.push(tx)
  @payment.payment_voided!

  Rails.logger.info("#{Time.current}: eCheck Void of Payment ID #{@payment.id}")

  notify_forte_pending

  Result.new(success: true, message: 'voided')
end