Class: Spree::Admin::BankTransfersController
- Inherits:
-
BaseController
- Object
- BaseController
- Spree::Admin::BankTransfersController
- Includes:
- Pagy::Method
- Defined in:
- app/controllers/spree/admin/bank_transfers_controller.rb
Constant Summary collapse
- MANUAL_PROVIDER =
Synthetic provider for transfers an admin typed in by hand. Keeps them distinguishable from anything a real reconciler delivered, and gives the (provider, provider_transaction_id) uniqueness index its own namespace so a hand-entered row can never collide with a provider-issued transaction id.
'manual'.freeze
Instance Method Summary collapse
- #apply ⇒ Object
-
#create ⇒ Object
Deliberately builds a TransferData and hands it to IngestTransfer rather than writing an IncomingTransfer directly: matching lives in exactly one place, so a hand-recorded transfer behaves identically to a provider-delivered one -- exact match auto-applies, anything else lands in the queue for a human.
- #ignore ⇒ Object
- #index ⇒ Object
-
#new ⇒ Object
"Record a received transfer".
Instance Method Details
#apply ⇒ Object
107 108 109 110 111 112 113 114 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 |
# File 'app/controllers/spree/admin/bank_transfers_controller.rb', line 107 def apply if @transfer.applied? flash[:error] = 'That transfer has already been applied.' return redirect_to spree.admin_bank_transfers_path end payment_session = find_bank_transfer_payment_session(params[:payment_session_id]) if payment_session.nil? flash[:error] = 'That payment session could not be found.' return redirect_to spree.admin_bank_transfers_path end if gateway_mismatch?(payment_session) flash[:error] = 'That transfer was received on a different bank-transfer gateway ' \ 'and cannot be applied to this session.' return redirect_to spree.admin_bank_transfers_path end # I3: the refusal now hands back the pair that needs confirming, and # the queue renders a distinct, explicitly-labelled confirm button # only for that pair. Confirmation is therefore a genuine second # step -- a deliberate act after seeing the numbers -- rather than # something the view pre-granted before the admin looked at anything. if money_mismatch?(payment_session) && !confirmed_mismatch? flash[:error] = "Amount/currency mismatch: the transfer is #{@transfer.money}, " \ "the session expects #{payment_session.money}. " \ 'Confirm to apply anyway.' return redirect_to spree.admin_bank_transfers_path( confirm_transfer_id: @transfer.id, confirm_payment_session_id: payment_session.id ) end Spree::BankPayments::ApplyTransfer.call( transfer: @transfer, payment_session: payment_session, applied_by: try_spree_current_user ) flash[:success] = 'Payment applied.' redirect_to spree.admin_bank_transfers_path end |
#create ⇒ Object
Deliberately builds a TransferData and hands it to IngestTransfer rather than writing an IncomingTransfer directly: matching lives in exactly one place, so a hand-recorded transfer behaves identically to a provider-delivered one -- exact match auto-applies, anything else lands in the queue for a human.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'app/controllers/spree/admin/bank_transfers_controller.rb', line 53 def create @transfer_form = transfer_form_params error = transfer_form_error(@transfer_form) if error flash.now[:error] = error return render :new, status: :unprocessable_entity end payment_method = @payment_methods.detect { |pm| pm.id.to_s == @transfer_form[:payment_method_id].to_s } transaction_id = manual_transaction_id(payment_method, @transfer_form) # The idempotency guard is IngestTransfer's find_or_create_by! on # (provider, provider_transaction_id). A hand-typed transfer has no # provider-issued id to key on, so we derive a deterministic one from # the submitted facts: an admin who double-submits the form (double # click, browser back-and-resubmit) reproduces the same digest, hits # the existing row, and applies nothing a second time. The trade is # that two genuinely distinct but byte-identical transfers on the # same day collapse into one -- rare, and far safer than the # alternative of crediting an order twice. already_recorded = Spree::BankPayments::IncomingTransfer.exists?( provider: MANUAL_PROVIDER, provider_transaction_id: transaction_id ) transfer = Spree::BankPayments::IngestTransfer.new( payment_method: payment_method, transfer_data: Spree::BankPayments::TransferData.new( provider: MANUAL_PROVIDER, provider_transaction_id: transaction_id, amount: BigDecimal(@transfer_form[:amount].to_s), currency: @transfer_form[:currency].to_s.strip.upcase, reference: @transfer_form[:reference].to_s.strip, payer_name: @transfer_form[:payer_name].to_s.strip.presence, occurred_at: parse_occurred_at(@transfer_form[:occurred_at]), raw: { 'source' => 'admin_manual_entry', 'recorded_by_id' => try_spree_current_user&.id } ) ).call flash[:success] = if already_recorded 'That transfer was already recorded — nothing was applied a second time.' elsif transfer.applied? "Transfer recorded and applied to order #{transfer.payment_session&.order&.number}." else 'Transfer recorded. It is waiting in the queue below for a match.' end redirect_to spree.admin_bank_transfers_path end |
#ignore ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'app/controllers/spree/admin/bank_transfers_controller.rb', line 151 def ignore if @transfer.applied? flash[:error] = 'That transfer has already been applied and cannot be ignored.' return redirect_to spree.admin_bank_transfers_path end reason = params[:reason].to_s.strip if reason.blank? flash[:error] = 'A reason is required to ignore a transfer.' return redirect_to spree.admin_bank_transfers_path end @transfer.update!(state: 'ignored', ignored_reason: reason) flash[:success] = 'Transfer ignored.' redirect_to spree.admin_bank_transfers_path end |
#index ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'app/controllers/spree/admin/bank_transfers_controller.rb', line 16 def index # includes(:bank_account) because the view asks every row whether its # account is pooled. The association is declared `-> { with_deleted }`, # which preloads fine, and it must stay that way: a soft-deleted # account would otherwise resolve to nil and silently drop the pooled # warning on exactly the rows still quoting its coordinates. @pagy, @transfers = pagy( Spree::BankPayments::IncomingTransfer.unmatched. includes(:bank_account).order(occurred_at: :desc) ) @suggestions = @transfers.each_with_object({}) do |transfer, acc| acc[transfer.id] = Spree::BankPayments::SuggestMatches.new(transfer: transfer).call end # Set only by #apply's mismatch refusal (see I3). Identifies the one # transfer/session pair the admin has already been shown the numbers # for and may now confirm. @confirm_transfer_id = params[:confirm_transfer_id].presence&.to_i @confirm_payment_session_id = params[:confirm_payment_session_id].presence&.to_i end |
#new ⇒ Object
"Record a received transfer". The Manual reconciler -- the default, and the only one this gem ships -- returns [] from #poll and nil from #parse_webhook, so nothing else can ever create an IncomingTransfer. Without this form a store on the shipped configuration takes a customer's money and has no action available to record it, and ExpireSessionsJob cancels the order a few days later.
44 45 46 |
# File 'app/controllers/spree/admin/bank_transfers_controller.rb', line 44 def new @transfer_form = blank_transfer_form end |