From 35e03ea3c769d93dd77cb579c0f70f3225b68266 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 16 Feb 2026 13:42:46 +1100 Subject: [PATCH] Check stock before completing order after Taler payment --- .../payment_gateways/taler_controller.rb | 23 +++++++- spec/requests/payment_gateways/taler_spec.rb | 54 +++++++++++++++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/app/controllers/payment_gateways/taler_controller.rb b/app/controllers/payment_gateways/taler_controller.rb index aff2799242..9e9e55eff1 100644 --- a/app/controllers/payment_gateways/taler_controller.rb +++ b/app/controllers/payment_gateways/taler_controller.rb @@ -2,15 +2,36 @@ module PaymentGateways class TalerController < BaseController + include OrderStockCheck include OrderCompletion + class StockError < StandardError + end + # The Taler merchant backend has taken the payment. # Now we just need to confirm that and update our local database # before finalising the order. def confirm payment = Spree::Payment.find(params[:payment_id]) + + # Process payment early because it's probably paid already. + # We want to capture that before any validations raise errors. + unless payment.process! + return redirect_to order_failed_route(step: "payment") + end + @order = payment.order - process_payment_completion! + OrderLocker.lock_order_and_variants(@order) do + raise StockError unless sufficient_stock? + + process_payment_completion! + end + rescue Spree::Core::GatewayError => e + flash[:notice] = e.message + redirect_to order_failed_route(step: "payment") + rescue StockError + flash[:notice] = t("checkout.payment_cancelled_due_to_stock") + redirect_to main_app.checkout_step_path(step: "details") end end end diff --git a/spec/requests/payment_gateways/taler_spec.rb b/spec/requests/payment_gateways/taler_spec.rb index 0ddc88b927..bd087b9e82 100644 --- a/spec/requests/payment_gateways/taler_spec.rb +++ b/spec/requests/payment_gateways/taler_spec.rb @@ -3,16 +3,19 @@ require 'spec_helper' RSpec.describe "/payment_gateways/taler/:id" do - it "completes the order", :vcr do - shop = create(:distributor_enterprise) - taler = Spree::PaymentMethod::Taler.create!( + let(:shop) { create(:distributor_enterprise) } + let(:taler) { + Spree::PaymentMethod::Taler.create!( name: "Taler", environment: "test", distributors: [shop], preferred_backend_url: "https://backend.demo.taler.net/instances/sandbox", preferred_api_key: "sandbox", ) - order = create(:order_ready_for_confirmation, payment_method: taler) + } + let!(:order) { create(:order_ready_for_confirmation, payment_method: taler) } + + it "completes the order", :vcr do payment = Spree::Payment.last payment.update!( source: taler, @@ -30,4 +33,47 @@ RSpec.describe "/payment_gateways/taler/:id" do payment.reload expect(payment.state).to eq "completed" end + + it "redirects when payment failed" do + payment = Spree::Payment.last + payment.update!( + source: taler, + payment_method: taler, + response_code: "2026.020-03R3ETNZZ0DVA", + ) + + allow_any_instance_of(Taler::Order) + .to receive(:fetch).with("order_status").and_return("claimed") + + get payment_gateways_confirm_taler_path(payment_id: payment.id) + expect(response).to redirect_to "/checkout/payment" + + payment.reload + expect(payment.state).to eq "failed" + + order.reload + expect(order.state).to eq "confirmation" + end + + it "handles all variants going out of stock" do + payment = Spree::Payment.last + payment.update!( + source: taler, + payment_method: taler, + response_code: "2026.020-03R3ETNZZ0DVA", + ) + + allow_any_instance_of(Taler::Order) + .to receive(:fetch).with("order_status").and_return("paid") + order.line_items[0].variant.on_hand = 0 + + get payment_gateways_confirm_taler_path(payment_id: payment.id) + expect(response).to redirect_to "/checkout/details" + + payment.reload + expect(payment.state).to eq "completed" + + order.reload + expect(order.state).to eq "confirmation" + end end