diff --git a/.env.test b/.env.test index 1fcf778303..62c4cefc8e 100644 --- a/.env.test +++ b/.env.test @@ -13,6 +13,9 @@ CAPYBARA_MAX_WAIT_TIME="10" # successful fallback to `en`. LOCALE="en_TST" +# For multilingual - ENV doesn't have array so pass it as string with commas +AVAILABLE_LOCALES="en_TST,es,pt" + OFN_REDIS_JOBS_URL="redis://localhost:6379/2" SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 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/app/models/spree/order.rb b/app/models/spree/order.rb index 741ebe0d55..ed608aa46b 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -674,8 +674,6 @@ module Spree end def process_each_payment - raise Core::GatewayError, Spree.t(:no_pending_payments) if pending_payments.empty? - pending_payments.each do |payment| if payment.amount.zero? && zero_priced_order? payment.update_columns(state: "completed", captured_at: Time.zone.now) diff --git a/config/application.rb b/config/application.rb index 71f47829ff..f20dfa9010 100644 --- a/config/application.rb +++ b/config/application.rb @@ -158,10 +158,10 @@ module Openfoodnetwork # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # The default locale is set in the environment. config.i18n.default_locale = OpenFoodNetwork::I18nConfig.default_locale config.i18n.available_locales = OpenFoodNetwork::I18nConfig.available_locales + config.i18n.fallbacks = OpenFoodNetwork::I18nConfig.fallbacks I18n.locale = config.i18n.locale = config.i18n.default_locale # Calculate digests for locale files so we can know when they change diff --git a/config/environments/development.rb b/config/environments/development.rb index 0aa1a6eda6..bfc893d885 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -111,8 +111,6 @@ Rails.application.configure do # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true - config.i18n.fallbacks = [:en] - # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true diff --git a/config/environments/production.rb b/config/environments/production.rb index ff8394a821..95ebad2351 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -91,10 +91,6 @@ Rails.application.configure do # Use https in email links config.action_mailer.default_url_options = { protocol: 'https' } - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = [:en] - # Send deprecation notices to registered listeners config.active_support.deprecation = :notify diff --git a/config/environments/staging.rb b/config/environments/staging.rb index b344d4446e..2d28f49a7f 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -60,10 +60,6 @@ Openfoodnetwork::Application.configure do # Enable threaded mode # config.threadsafe! - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found) - config.i18n.fallbacks = [:en] - # Send deprecation notices to registered listeners config.active_support.deprecation = :notify end diff --git a/config/environments/test.rb b/config/environments/test.rb index 3d50e5348d..089170d2a2 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -89,12 +89,6 @@ Rails.application.configure do # Raises error for missing translations. config.i18n.raise_on_missing_translations = true - # Tests assume English text on the site. - config.i18n.default_locale = "en" - config.i18n.available_locales = ['en', 'es', 'pt'] - config.i18n.fallbacks = [:en] - I18n.locale = config.i18n.locale = config.i18n.default_locale - # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true diff --git a/lib/open_food_network/i18n_config.rb b/lib/open_food_network/i18n_config.rb index d0d71d0450..6c132442d5 100644 --- a/lib/open_food_network/i18n_config.rb +++ b/lib/open_food_network/i18n_config.rb @@ -20,6 +20,10 @@ module OpenFoodNetwork (selectable_locales + [default_locale, source_locale]).uniq end + def self.fallbacks + [default_locale, source_locale].uniq + end + # The default locale that is used when the user doesn't have a preference. def self.default_locale ENV["LOCALE"] || ENV["I18N_LOCALE"] || source_locale diff --git a/spec/helpers/i18n_helper_spec.rb b/spec/helpers/i18n_helper_spec.rb index 10a082507b..1e8d68d5a6 100644 --- a/spec/helpers/i18n_helper_spec.rb +++ b/spec/helpers/i18n_helper_spec.rb @@ -15,7 +15,7 @@ RSpec.describe I18nHelper do it "sets the default locale" do helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end it "sets the chosen locale" do @@ -36,11 +36,11 @@ RSpec.describe I18nHelper do it "ignores unavailable locales" do allow(helper).to receive(:params) { { locale: "xx" } } helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end it "remembers the last chosen locale" do - allow(helper).to receive(:params) { { locale: "en" } } + allow(helper).to receive(:params) { { locale: "en_TST" } } helper.set_locale allow(helper).to receive(:params) { { locale: "es" } } @@ -71,7 +71,7 @@ RSpec.describe I18nHelper do allow(helper).to receive(:params) { {} } helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end end @@ -82,7 +82,7 @@ RSpec.describe I18nHelper do it "sets the default locale" do helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end it "sets the chosen locale" do @@ -102,7 +102,7 @@ RSpec.describe I18nHelper do end it "remembers the last chosen locale" do - allow(helper).to receive(:params) { { locale: "en" } } + allow(helper).to receive(:params) { { locale: "en_TST" } } helper.set_locale allow(helper).to receive(:params) { { locale: "es" } } diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index d9ae1a59f3..d8fe7e9f2a 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -282,11 +282,6 @@ RSpec.describe Spree::Order do let(:payment) { build(:payment) } before { allow(order).to receive_messages pending_payments: [payment], total: 10 } - it "returns false if no pending_payments available" do - allow(order).to receive_messages pending_payments: [] - expect(order.process_payments!).to be_falsy - end - context "when the processing is sucessful" do it "processes the payments" do expect(payment).to receive(:process!) diff --git a/spec/requests/payment_gateways/taler_spec.rb b/spec/requests/payment_gateways/taler_spec.rb index 0ddc88b927..26c5c8456a 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,65 @@ RSpec.describe "/payment_gateways/taler/:id" do payment.reload expect(payment.state).to eq "completed" end + + it "redirects when payment invalid" do + payment = Spree::Payment.last + payment.update!( + source: taler, + payment_method: taler, + state: "processing", # invalid state to start processing again + ) + + get payment_gateways_confirm_taler_path(payment_id: payment.id) + expect(response).to redirect_to "/checkout/payment" + + payment.reload + expect(payment.state).to eq "processing" + + order.reload + expect(order.state).to eq "confirmation" + 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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2078c0a3ca..f69ff6af6b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -152,7 +152,7 @@ RSpec.configure do |config| # Reset locale for all specs. config.around(:each) do |example| - locale = ENV.fetch('LOCALE', 'en_TST') + locale = OpenFoodNetwork::I18nConfig.default_locale I18n.with_locale(locale) { example.run } end diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 3e4bb3a632..39a370d146 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -27,10 +27,6 @@ module WebHelper yield end - def set_i18n_locale(locale = 'en') - page.execute_script("I18n.locale = '#{locale}'") - end - def pick_i18n_locale page.evaluate_script("I18n.locale;") end diff --git a/spec/system/admin/multilingual_spec.rb b/spec/system/admin/multilingual_spec.rb index 0c54b97cb3..523961a3d1 100644 --- a/spec/system/admin/multilingual_spec.rb +++ b/spec/system/admin/multilingual_spec.rb @@ -10,17 +10,12 @@ RSpec.describe 'Multilingual' do before do login_as admin_user - visit spree.admin_dashboard_path - end - - it 'has three locales available' do - expect(Rails.application.config.i18n[:default_locale]).to eq 'en' - expect(Rails.application.config.i18n[:locale]).to eq 'en' - expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] end it 'can switch language by params' do - expect(pick_i18n_locale).to eq 'en' + visit spree.admin_dashboard_path + + expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises' expect(page).to have_content 'My Enterprises' expect(admin_user.locale).to be_nil @@ -36,9 +31,9 @@ RSpec.describe 'Multilingual' do it 'fallbacks to default_locale' do visit spree.admin_dashboard_path(locale: 'it') - expect(pick_i18n_locale).to eq 'en' + expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises' expect(page).to have_content 'My Enterprises' - expect(admin_user.locale).to be_nil + expect(admin_user.reload.locale).to be_nil end end diff --git a/spec/system/consumer/authentication_spec.rb b/spec/system/consumer/authentication_spec.rb index 83cc79fbf0..5a777c809a 100644 --- a/spec/system/consumer/authentication_spec.rb +++ b/spec/system/consumer/authentication_spec.rb @@ -273,7 +273,7 @@ RSpec.describe "Authentication" do expect_logged_in expect(page).to have_content 'SHOP NOW' - expect(user.reload.locale).to eq "en" + expect(user.reload.locale).to eq "en_TST" end end diff --git a/spec/system/consumer/checkout/payment_spec.rb b/spec/system/consumer/checkout/payment_spec.rb index e9374b4a53..db21ffd81e 100644 --- a/spec/system/consumer/checkout/payment_spec.rb +++ b/spec/system/consumer/checkout/payment_spec.rb @@ -49,7 +49,6 @@ RSpec.describe "As a consumer, I want to checkout my order" do before do login_as(user) - visit checkout_path end context "payment step" do @@ -67,6 +66,7 @@ RSpec.describe "As a consumer, I want to checkout my order" do context "with a transaction fee" do before do + visit checkout_path click_button "Next - Order summary" end @@ -84,6 +84,7 @@ RSpec.describe "As a consumer, I want to checkout my order" do context "after completing the order" do before do + visit checkout_path click_on "Complete order" end it_behaves_like "displays the transaction fee", "order confirmation" @@ -277,7 +278,7 @@ RSpec.describe "As a consumer, I want to checkout my order" do describe "choosing" do shared_examples "different payment methods" do |pay_method| - context "checking out with #{pay_method}", if: pay_method.eql?("Stripe SCA") == false do + context "checking out with #{pay_method}" do before do visit checkout_step_path(:payment) end @@ -291,46 +292,9 @@ RSpec.describe "As a consumer, I want to checkout my order" do expect(order.reload.state).to eq "complete" end end - - context "for Stripe SCA", if: pay_method.eql?("Stripe SCA") do - around do |example| - with_stripe_setup { example.run } - end - - before do - stripe_enable - visit checkout_step_path(:payment) - end - - it "selects Stripe SCA and proceeds to the summary step" do - choose pay_method.to_s - fill_out_card_details - click_on "Next - Order summary" - proceed_to_summary - end - - context "when saving card" do - it "selects Stripe SCA and proceeds to the summary step" do - stub_customers_post_request(email: order.user.email) - stub_payment_method_attach_request - - choose pay_method.to_s - fill_out_card_details - check "Save card for future use" - - click_on "Next - Order summary" - proceed_to_summary - - # Verify card has been saved with correct stripe IDs - user_credit_card = order.reload.user.credit_cards.first - expect(user_credit_card.gateway_payment_profile_id).to eq "pm_123" - expect(user_credit_card.gateway_customer_profile_id).to eq "cus_A123" - end - end - end end - describe "shared examples" do + describe "payment method" do let!(:cash) { create(:payment_method, distributors: [distributor], name: "Cash") } context "Cash" do @@ -365,7 +329,40 @@ RSpec.describe "As a consumer, I want to checkout my order" do create(:stripe_sca_payment_method, distributors: [distributor], name: "Stripe SCA") } - it_behaves_like "different payment methods", "Stripe SCA" + around do |example| + with_stripe_setup { example.run } + end + + before do + stripe_enable + visit checkout_step_path(:payment) + end + + it "selects Stripe SCA and proceeds to the summary step" do + choose "Stripe SCA" + fill_out_card_details + click_on "Next - Order summary" + proceed_to_summary + end + + context "when saving card" do + it "selects Stripe SCA and proceeds to the summary step" do + stub_customers_post_request(email: order.user.email) + stub_payment_method_attach_request + + choose "Stripe SCA" + fill_out_card_details + check "Save card for future use" + + click_on "Next - Order summary" + proceed_to_summary + + # Verify card has been saved with correct stripe IDs + user_credit_card = order.reload.user.credit_cards.first + expect(user_credit_card.gateway_payment_profile_id).to eq "pm_123" + expect(user_credit_card.gateway_customer_profile_id).to eq "cus_A123" + end + end end context "Taler" do diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index 2d5df6446b..e6aeccdd9c 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -5,189 +5,72 @@ require 'system_helper' RSpec.describe 'Multilingual' do include AuthenticationHelper include WebHelper - include ShopWorkflow - include UIComponentHelper include CookieHelper + let(:user) { create(:user) } + it 'has three locales available' do - expect(Rails.application.config.i18n[:default_locale]).to eq 'en' - expect(Rails.application.config.i18n[:locale]).to eq 'en' - expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] + expect(Rails.application.config.i18n[:default_locale]).to eq 'en_TST' + expect(Rails.application.config.i18n[:locale]).to eq 'en_TST' + expect(Rails.application.config.i18n[:available_locales]).to eq ['en_TST', 'es', 'pt', 'en'] end - it '18n-js fallsback to default language' do - # in backend it doesn't until we change enforce_available_locales to `true` + it 'can switch language by params' do visit root_path - set_i18n_locale('it') + expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('label_shops')).to eq 'Shops' + expect(cookies_name).not_to include('locale') + expect(page).to have_content 'SHOPS' + + visit root_path(locale: 'es') + expect(pick_i18n_locale).to eq 'es' + expect(get_i18n_translation('label_shops')).to eq 'Tiendas' + expect_menu_and_cookie_in_es + + # it is not in the list of available of available_locales + visit root_path(locale: 'it') + expect(pick_i18n_locale).to eq 'es' + expect(get_i18n_translation('label_shops')).to eq 'Tiendas' + expect_menu_and_cookie_in_es end - context 'can switch language by params' do - it 'in root path' do - visit root_path - expect(pick_i18n_locale).to eq 'en' - expect(get_i18n_translation('label_shops')).to eq 'Shops' - expect(cookies_name).not_to include('locale') - expect(page).to have_content 'SHOPS' + it 'updates user locale from cookie if it is empty' do + visit root_path(locale: 'es') - visit root_path(locale: 'es') - expect(pick_i18n_locale).to eq 'es' - expect(get_i18n_translation('label_shops')).to eq 'Tiendas' - expect_menu_and_cookie_in_es + expect_menu_and_cookie_in_es + expect(user.locale).to be_nil + login_as user + visit root_path - # it is not in the list of available of available_locales - visit root_path(locale: 'it') - expect(pick_i18n_locale).to eq 'es' - expect(get_i18n_translation('label_shops')).to eq 'Tiendas' - expect_menu_and_cookie_in_es - end + expect_menu_and_cookie_in_es - context 'with a product in the cart' do - let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } - let!(:order_cycle) { - create(:simple_order_cycle, distributors: [distributor], variants: [product.variants.first]) - } - let(:product) { create(:simple_product) } - let(:order) { create(:order, order_cycle:, distributor:) } + # The user's locale is not changed if the language was chosen before + # login. Is it a bug or a feature? Probably not important... + expect(user.reload.locale).to eq nil - before do - pick_order order - add_product_to_cart order, product, quantity: 1 - end + visit root_path(locale: 'es') + expect(user.reload.locale).to eq 'es' - it "in the cart page" do - visit main_app.cart_path(locale: 'es') + logout - expect_menu_and_cookie_in_es - expect(page).to have_content 'Precio' - end - - it "visiting checkout as a guest user" do - visit checkout_path(locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content 'Iniciar sesión' - end - end + expect_menu_and_cookie_in_es + expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?' end - context 'with user' do - let(:user) { create(:user) } + it "allows switching language via the main navigation" do + visit root_path - it 'updates user locale from cookie if it is empty' do - visit root_path(locale: 'es') + expect(page).to have_content 'SHOPS' - expect_menu_and_cookie_in_es - expect(user.locale).to be_nil - login_as user - visit root_path + find('.language-switcher').click + within '.language-switcher .dropdown' do + expect(page).not_to have_link 'English' + expect(page).to have_link 'Español' - expect_menu_and_cookie_in_es + click_link 'Español' end - it 'updates user locale and stays in cookie after logout' do - login_as user - - visit root_path(locale: 'es') - user.reload - - expect(user.locale).to eq 'es' - - logout - - expect_menu_and_cookie_in_es - expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?' - end - - context "visiting checkout as logged user" do - let!(:zone) { create(:zone_with_member) } - let(:supplier) { create(:supplier_enterprise) } - let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) } - let(:product) { - create(:taxed_product, supplier_id: supplier.id, price: 10, zone:) - } - let(:variant) { product.variants.first } - let!(:order_cycle) { - create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], - coordinator: create(:distributor_enterprise), - variants: [variant]) - } - - let(:free_shipping) { - create(:shipping_method, require_ship_address: false) - } - let!(:payment) { - create(:payment_method, distributors: [distributor], - name: "Payment") - } - let(:order) { - create(:order_ready_for_confirmation, distributor:) - } - before do - pick_order order - login_as user - end - - it "on the details step" do - visit checkout_step_path(:details, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Sus detalles" - end - - it "on the payment step" do - visit checkout_step_path(:payment, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Puede revisar y confirmar su pedido" - end - - it "on the summary step" do - visit checkout_step_path(:summary, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Detalles de entrega" - end - end - end - - describe "using the language switcher UI" do - context "when there is only one language available" do - before do - allow(ENV).to receive(:[]).and_call_original - allow(ENV).to receive(:[]).with("LOCALE").and_return("en") - allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en") - end - - it "hides the dropdown language menu" do - visit root_path - expect(page).not_to have_css 'ul.right li.language-switcher.has-dropdown' - end - end - - context "when there are multiple languages available" do - before do - allow(ENV).to receive(:[]).and_call_original - allow(ENV).to receive(:[]).with("LOCALE").and_return("en") - allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en,es") - end - - it "allows switching language via the main navigation" do - visit root_path - - expect(page).to have_content 'SHOPS' - - find('.language-switcher').click - within '.language-switcher .dropdown' do - expect(page).not_to have_link 'English', href: '/locales/en' - expect(page).to have_link 'Español', href: '/locales/es' - - find('li a[href="/locales/es"]').click - end - - expect_menu_and_cookie_in_es - end - end + expect_menu_and_cookie_in_es end end diff --git a/spec/system/consumer/shopping/checkout_stripe_spec.rb b/spec/system/consumer/shopping/checkout_stripe_spec.rb index 5d279f1d3b..fe41a0675d 100644 --- a/spec/system/consumer/shopping/checkout_stripe_spec.rb +++ b/spec/system/consumer/shopping/checkout_stripe_spec.rb @@ -3,221 +3,34 @@ require 'system_helper' RSpec.describe "Check out with Stripe" do - include AuthenticationHelper - include ShopWorkflow - include CheckoutRequestsHelper - include StripeHelper - include StripeStubs - - let(:distributor) { create(:distributor_enterprise) } - let!(:order_cycle) { - create(:simple_order_cycle, distributors: [distributor], variants: [variant]) - } - let(:product) { create(:product, price: 10) } - let(:variant) { product.variants.first } - let(:order) { - create(:order, order_cycle:, distributor:, bill_address_id: nil, - ship_address_id: nil) - } - - let(:shipping_with_fee) { - create(:shipping_method, require_ship_address: false, name: "Donkeys", - calculator: Calculator::FlatRate.new(preferred_amount: 4.56)) - } - let(:free_shipping) { create(:shipping_method) } - let!(:check_with_fee) { - create(:payment_method, distributors: [distributor], - calculator: Calculator::FlatRate.new(preferred_amount: 5.67)) - } - - around do |example| - with_stripe_setup { example.run } - end - - before do - stripe_enable - pick_order order - add_product_to_cart order, product - distributor.shipping_methods << [shipping_with_fee, free_shipping] - end - - pending "using Stripe SCA" do - let!(:stripe_account) { create(:stripe_account, enterprise: distributor) } - let!(:stripe_sca_payment_method) { - create(:stripe_sca_payment_method, distributors: [distributor]) - } - let!(:shipping_method) { create(:shipping_method) } - let(:error_message) { "Card was declined: insufficient funds." } - - before do - stub_payment_intent_get_request - stub_payment_methods_post_request - end - + describe "using Stripe SCA" do context "with guest checkout" do - before do - stub_retrieve_payment_method_request("pm_123") - stub_list_customers_request(email: order.user.email, response: {}) - stub_get_customer_payment_methods_request(customer: "cus_A456", response: {}) - end - context "when the card is accepted" do - before do - stub_payment_intents_post_request(order:) - stub_successful_capture_request order: - end - - it "completes checkout successfully" do - checkout_with_stripe - - expect(page).to have_content "Confirmed" - expect(page.find("#amount-paid").text).to have_content "$19.99" - - expect(order.reload.completed?).to eq true - expect(order.payments.first.state).to eq "completed" - end + it "completes checkout successfully" end context "when the card is rejected" do - before do - stub_payment_intents_post_request(order:) - stub_failed_capture_request order:, response: { message: error_message } - end - - it "shows an error message from the Stripe response" do - checkout_with_stripe - - expect(page).to have_content error_message - expect(order.reload.state).to eq "cart" - expect(order.payments.first.state).to eq "failed" - end + it "shows an error message from the Stripe response" end context "when the card needs extra SCA authorization" do - before do - stripe_redirect_url = checkout_path(payment_intent: "pi_123") - stub_payment_intents_post_request_with_redirect order:, - redirect_url: stripe_redirect_url - end - describe "and the authorization succeeds" do - before do - stub_successful_capture_request order: - end - - it "completes checkout successfully" do - checkout_with_stripe - - # We make stripe return stripe_redirect_url (which is already sending the user back - # to the checkout) as if the authorization was done. We can then control the actual - # authorization or failure of the payment through the mock - # stub_successful_capture_request - - expect(page).to have_content "Confirmed" - expect(order.reload.completed?).to eq true - expect(order.payments.first.state).to eq "completed" - end + it "completes checkout successfully" end describe "and the authorization fails" do - before do - stub_failed_capture_request order:, response: { message: error_message } - end - - it "shows an error message from the Stripe response" do - checkout_with_stripe - - # We make stripe return stripe_redirect_url (which is already sending the user back to - # the checkout) as if the authorization was done. We can then control the actual - # authorization or failure of the payment through the mock stub_failed_capture_request - expect(page).to have_content error_message - expect(order.reload.state).to eq "cart" - expect(order.payments.first.state).to eq "failed" - end + it "shows an error message from the Stripe response" end end context "with multiple payment attempts; one failed and one succeeded" do - before do - stub_payment_intents_post_request order: - end - - it "records failed payment attempt and allows order completion" do - # First payment attempt is rejected - stub_failed_capture_request(order:, response: { message: error_message }) - checkout_with_stripe - expect(page).to have_content error_message - - expect(order.reload.payments.count).to eq 1 - expect(order.state).to eq "cart" - expect(order.payments.first.state).to eq "failed" - - # Second payment attempt is accepted - stub_successful_capture_request(order:) - place_order - expect(page).to have_content "Confirmed" - - expect(order.reload.payments.count).to eq 2 - expect(order.state).to eq "complete" - expect(order.payments.last.state).to eq "completed" - end + it "records failed payment attempt and allows order completion" end end context "with a logged in user" do - let(:user) { order.user } - - before do - login_as user - end - context "saving a card and re-using it" do - before do - stub_retrieve_payment_method_request("pm_123") - stub_list_customers_request(email: order.user.email, response: {}) - stub_get_customer_payment_methods_request(customer: "cus_A456", response: {}) - stub_get_customer_payment_methods_request(customer: "cus_A123", response: {}) - stub_payment_methods_post_request( - request: { payment_method: "pm_123", customer: "cus_A123" }, - response: { pm_id: "pm_123" } - ) - stub_add_metadata_request(payment_method: "pm_123", response: {}) - stub_payment_intents_post_request(order:) - stub_successful_capture_request(order:) - stub_customers_post_request email: "test@test.com" # First checkout with default details - stub_customers_post_request email: user.email # Second checkout with saved user details - stub_payment_method_attach_request - end - - it "allows saving a card and re-using it" do - checkout_with_stripe guest_checkout: false, remember_card: true - - expect(page).to have_content "Confirmed" - expect(order.reload.completed?).to eq true - expect(order.payments.first.state).to eq "completed" - - # Verify card has been saved with correct stripe IDs - user_credit_card = order.reload.user.credit_cards.first - expect(user_credit_card.gateway_payment_profile_id).to eq "pm_123" - expect(user_credit_card.gateway_customer_profile_id).to eq "cus_A123" - - # Prepare a second order - new_order = create(:order, user:, order_cycle:, - distributor:, bill_address_id: nil, - ship_address_id: nil) - pick_order(new_order) - add_product_to_cart(new_order, product, quantity: 10) - stub_payment_intents_post_request order: new_order - stub_successful_capture_request order: new_order - - # Checkout with saved card - visit checkout_path - choose free_shipping.name - choose stripe_sca_payment_method.name - expect(page).to have_content "Use a saved card" - expect(page).to have_select 'selected_card', selected: "Visa x-4242 Exp:10/2050" - place_order - end + it "allows saving a card and re-using it" end end end