From abd4f0b9239f87f28c6da29b00cda1e02c8ce709 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 2 Dec 2019 17:43:21 +0000 Subject: [PATCH 01/97] Add custom_data column to paper_trail versions table so we can track a specific list of ids in a model Activate paper_trail in order_cycles and schedules and track each others ids An alternative way of doing this would be to use a gem for paper_trail associations but this way we avoid adding a new dependency to the app --- app/models/order_cycle.rb | 1 + app/models/schedule.rb | 2 ++ config/initializers/paper_trail.rb | 6 ++++++ db/migrate/20191202165700_add_custom_data_to_versions.rb | 5 +++++ db/schema.rb | 1 + 5 files changed, 15 insertions(+) create mode 100644 db/migrate/20191202165700_add_custom_data_to_versions.rb diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 9a30c493e5..e9afe06e5a 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -17,6 +17,7 @@ class OrderCycle < ActiveRecord::Base has_many :distributors, source: :receiver, through: :cached_outgoing_exchanges, uniq: true has_and_belongs_to_many :schedules, join_table: 'order_cycle_schedules' + has_paper_trail meta: { custom_data: :schedule_ids } attr_accessor :incoming_exchanges, :outgoing_exchanges diff --git a/app/models/schedule.rb b/app/models/schedule.rb index 67443f7784..1f8c5b9b3a 100644 --- a/app/models/schedule.rb +++ b/app/models/schedule.rb @@ -1,5 +1,7 @@ class Schedule < ActiveRecord::Base has_and_belongs_to_many :order_cycles, join_table: 'order_cycle_schedules' + has_paper_trail meta: { custom_data: :order_cycle_ids } + has_many :coordinators, uniq: true, through: :order_cycles attr_accessible :name, :order_cycle_ids diff --git a/config/initializers/paper_trail.rb b/config/initializers/paper_trail.rb index 39a6679176..e955bac9be 100644 --- a/config/initializers/paper_trail.rb +++ b/config/initializers/paper_trail.rb @@ -1 +1,7 @@ PaperTrail.config.track_associations = false + +module PaperTrail + class Version < ActiveRecord::Base + attr_accessible :custom_data + end +end diff --git a/db/migrate/20191202165700_add_custom_data_to_versions.rb b/db/migrate/20191202165700_add_custom_data_to_versions.rb new file mode 100644 index 0000000000..45a10ed67e --- /dev/null +++ b/db/migrate/20191202165700_add_custom_data_to_versions.rb @@ -0,0 +1,5 @@ +class AddCustomDataToVersions < ActiveRecord::Migration + def change + add_column :versions, :custom_data, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 288c838266..03550d3930 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1202,6 +1202,7 @@ ActiveRecord::Schema.define(:version => 20191023172424) do t.string "whodunnit" t.text "object" t.datetime "created_at" + t.string "custom_data" end add_index "versions", ["item_type", "item_id"], :name => "index_versions_on_item_type_and_item_id" From a2801e40a2192de12e48be8c9eceaa0917ef87d6 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 5 Dec 2019 11:26:40 +0000 Subject: [PATCH 02/97] Improve readability of proxy_order_syncer and add some log messages --- lib/open_food_network/proxy_order_syncer.rb | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/open_food_network/proxy_order_syncer.rb b/lib/open_food_network/proxy_order_syncer.rb index 416774d506..62dc575115 100644 --- a/lib/open_food_network/proxy_order_syncer.rb +++ b/lib/open_food_network/proxy_order_syncer.rb @@ -17,29 +17,36 @@ module OpenFoodNetwork end def sync! - return sync_all! if @subscriptions + return sync_subscriptions! if @subscriptions + return initialise_proxy_orders! unless @subscription.id - create_proxy_orders! - remove_orphaned_proxy_orders! + sync_subscription! end private - def sync_all! + def sync_subscriptions! @subscriptions.each do |subscription| @subscription = subscription - create_proxy_orders! - remove_orphaned_proxy_orders! + sync_subscription! end end def initialise_proxy_orders! uninitialised_order_cycle_ids.each do |order_cycle_id| + Rails.logger.info "Initializing Proxy Order " \ + "of subscription #{@subscription.id} in order cycle #{order_cycle_id}" proxy_orders << ProxyOrder.new(subscription: subscription, order_cycle_id: order_cycle_id) end end + def sync_subscription! + Rails.logger.info "Syncing Proxy Orders of subscription #{@subscription.id}" + create_proxy_orders! + remove_orphaned_proxy_orders! + end + def create_proxy_orders! return unless not_closed_in_range_order_cycles.any? @@ -58,6 +65,8 @@ module OpenFoodNetwork orphaned_proxy_orders.scoped.delete_all end + # Remove Proxy Orders that have not been placed yet + # and are in Order Cycles that are out of range def orphaned_proxy_orders orphaned = proxy_orders.where(placed_at: nil) order_cycle_ids = in_range_order_cycles.pluck(:id) From 01d69c89aaf163733fe7f6eb578227267874f364 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 5 Dec 2019 11:58:38 +0000 Subject: [PATCH 03/97] Add some log messages to help debug problems in subscription placement and subscription confirmation processes --- app/jobs/subscription_confirm_job.rb | 1 + app/jobs/subscription_placement_job.rb | 1 + lib/open_food_network/subscription_summarizer.rb | 1 + spec/lib/open_food_network/subscription_summarizer_spec.rb | 6 ++++-- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/jobs/subscription_confirm_job.rb b/app/jobs/subscription_confirm_job.rb index 2e0b05e4fe..be7f589cfc 100644 --- a/app/jobs/subscription_confirm_job.rb +++ b/app/jobs/subscription_confirm_job.rb @@ -6,6 +6,7 @@ class SubscriptionConfirmJob ids = proxy_orders.pluck(:id) proxy_orders.update_all(confirmed_at: Time.zone.now) ProxyOrder.where(id: ids).each do |proxy_order| + Rails.logger.info "Confirming Order for Proxy Order #{proxy_order.id}" @order = proxy_order.order process! end diff --git a/app/jobs/subscription_placement_job.rb b/app/jobs/subscription_placement_job.rb index 26c67997ba..3cc1a5d6ea 100644 --- a/app/jobs/subscription_placement_job.rb +++ b/app/jobs/subscription_placement_job.rb @@ -5,6 +5,7 @@ class SubscriptionPlacementJob ids = proxy_orders.pluck(:id) proxy_orders.update_all(placed_at: Time.zone.now) ProxyOrder.where(id: ids).each do |proxy_order| + Rails.logger.info "Placing Order for Proxy Order #{proxy_order.id}" proxy_order.initialise_order! process(proxy_order.order) end diff --git a/lib/open_food_network/subscription_summarizer.rb b/lib/open_food_network/subscription_summarizer.rb index 7a429a9e3b..6e4b95da8b 100644 --- a/lib/open_food_network/subscription_summarizer.rb +++ b/lib/open_food_network/subscription_summarizer.rb @@ -17,6 +17,7 @@ module OpenFoodNetwork end def record_issue(type, order, message = nil) + Rails.logger.info "Issue in Subscription Order #{order.id}: #{type}" summary_for(order).record_issue(type, order, message) end diff --git a/spec/lib/open_food_network/subscription_summarizer_spec.rb b/spec/lib/open_food_network/subscription_summarizer_spec.rb index 2ecdfac1ea..a0d2b3f7bf 100644 --- a/spec/lib/open_food_network/subscription_summarizer_spec.rb +++ b/spec/lib/open_food_network/subscription_summarizer_spec.rb @@ -1,3 +1,4 @@ +require 'spec_helper' require 'open_food_network/subscription_summarizer' module OpenFoodNetwork @@ -5,6 +6,8 @@ module OpenFoodNetwork let(:order) { create(:order) } let(:summarizer) { OpenFoodNetwork::SubscriptionSummarizer.new } + before { allow(Rails.logger).to receive(:info) } + describe "#summary_for" do let(:order) { double(:order, distributor_id: 123) } @@ -53,6 +56,7 @@ module OpenFoodNetwork describe "#record_issue" do it "requests a summary for the order and calls #record_issue on it" do + expect(order).to receive(:id) expect(summary).to receive(:record_issue).with(:type, order, "message").once summarizer.record_issue(:type, order, "message") end @@ -69,7 +73,6 @@ module OpenFoodNetwork end it "sends error info to the rails logger and calls #record_issue on itself with an error message" do - expect(Rails.logger).to receive(:info) expect(summarizer).to receive(:record_issue).with(:processing, order, "Errors: Some error") summarizer.record_and_log_error(:processing, order) end @@ -81,7 +84,6 @@ module OpenFoodNetwork end it "falls back to calling record_issue" do - expect(Rails.logger).to_not receive(:info) expect(summarizer).to receive(:record_issue).with(:processing, order) summarizer.record_and_log_error(:processing, order) end From 3b399b899c3764e5dcde72e5df72c9cc902c45c7 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 5 Dec 2019 12:13:10 +0000 Subject: [PATCH 04/97] Extract methods in subscription_placement_job to fix rubocop complexity issues --- .rubocop_manual_todo.yml | 1 - app/jobs/subscription_placement_job.rb | 24 ++++++++++++-------- spec/jobs/subscription_placement_job_spec.rb | 16 ++++++------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 1644580ae5..e4baf8d472 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -389,7 +389,6 @@ Metrics/AbcSize: - app/helpers/spree/admin/base_helper.rb - app/helpers/spree/admin/zones_helper.rb - app/helpers/spree/orders_helper.rb - - app/jobs/subscription_placement_job.rb - app/mailers/producer_mailer.rb - app/models/calculator/flat_percent_per_item.rb - app/models/column_preference.rb diff --git a/app/jobs/subscription_placement_job.rb b/app/jobs/subscription_placement_job.rb index 3cc1a5d6ea..b66c06abba 100644 --- a/app/jobs/subscription_placement_job.rb +++ b/app/jobs/subscription_placement_job.rb @@ -5,9 +5,7 @@ class SubscriptionPlacementJob ids = proxy_orders.pluck(:id) proxy_orders.update_all(placed_at: Time.zone.now) ProxyOrder.where(id: ids).each do |proxy_order| - Rails.logger.info "Placing Order for Proxy Order #{proxy_order.id}" - proxy_order.initialise_order! - process(proxy_order.order) + place_order_for(proxy_order) end send_placement_summary_emails @@ -29,16 +27,18 @@ class SubscriptionPlacementJob .joins(:subscription).merge(Subscription.not_canceled.not_paused) end - def process(order) + def place_order_for(proxy_order) + Rails.logger.info "Placing Order for Proxy Order #{proxy_order.id}" + proxy_order.initialise_order! + place_order(proxy_order.order) + end + + def place_order(order) record_order(order) return record_issue(:complete, order) if order.completed? changes = cap_quantity_and_store_changes(order) - if order.line_items.where('quantity > 0').empty? - order.reload.adjustments.destroy_all - order.update! - return send_empty_email(order, changes) - end + return handle_empty_order(order, changes) if order.line_items.where('quantity > 0').empty? move_to_completion(order) send_placement_email(order, changes) @@ -59,6 +59,12 @@ class SubscriptionPlacementJob changes end + def handle_empty_order(order, changes) + order.reload.adjustments.destroy_all + order.update! + send_empty_email(order, changes) + end + def move_to_completion(order) AdvanceOrderService.new(order).call! end diff --git a/spec/jobs/subscription_placement_job_spec.rb b/spec/jobs/subscription_placement_job_spec.rb index bd008682e3..d298726369 100644 --- a/spec/jobs/subscription_placement_job_spec.rb +++ b/spec/jobs/subscription_placement_job_spec.rb @@ -45,7 +45,7 @@ describe SubscriptionPlacementJob do before do allow(job).to receive(:proxy_orders) { ProxyOrder.where(id: proxy_order.id) } - allow(job).to receive(:process) + allow(job).to receive(:place_order) end it "marks placeable proxy_orders as processed by setting placed_at" do @@ -55,7 +55,7 @@ describe SubscriptionPlacementJob do it "processes placeable proxy_orders" do job.perform - expect(job).to have_received(:process).with(proxy_order.reload.order) + expect(job).to have_received(:place_order).with(proxy_order.reload.order) end end end @@ -143,7 +143,7 @@ describe SubscriptionPlacementJob do it "records an issue and ignores it" do ActionMailer::Base.deliveries.clear expect(job).to receive(:record_issue).with(:complete, order).once - expect{ job.send(:process, order) }.to_not change{ order.reload.state } + expect{ job.send(:place_order, order) }.to_not change{ order.reload.state } expect(order.payments.first.state).to eq "checkout" expect(ActionMailer::Base.deliveries.count).to be 0 end @@ -156,7 +156,7 @@ describe SubscriptionPlacementJob do end it "uses the same shipping method after advancing the order" do - job.send(:process, order) + job.send(:place_order, order) expect(order.state).to eq "complete" order.reload expect(order.shipping_method).to eq(shipping_method) @@ -169,7 +169,7 @@ describe SubscriptionPlacementJob do end it "does not place the order, clears, all adjustments, and sends an empty_order email" do - expect{ job.send(:process, order) }.to_not change{ order.reload.completed_at }.from(nil) + expect{ job.send(:place_order, order) }.to_not change{ order.reload.completed_at }.from(nil) expect(order.adjustments).to be_empty expect(order.total).to eq 0 expect(order.adjustment_total).to eq 0 @@ -182,13 +182,13 @@ describe SubscriptionPlacementJob do it "processes the order to completion, but does not process the payment" do # If this spec starts complaining about no shipping methods being available # on CI, there is probably another spec resetting the currency though Rails.cache.clear - expect{ job.send(:process, order) }.to change{ order.reload.completed_at }.from(nil) + expect{ job.send(:place_order, order) }.to change{ order.reload.completed_at }.from(nil) expect(order.completed_at).to be_within(5.seconds).of Time.zone.now expect(order.payments.first.state).to eq "checkout" end it "does not enqueue confirmation emails" do - expect{ job.send(:process, order) }.to_not enqueue_job ConfirmOrderJob + expect{ job.send(:place_order, order) }.to_not enqueue_job ConfirmOrderJob expect(job).to have_received(:send_placement_email).with(order, anything).once end @@ -198,7 +198,7 @@ describe SubscriptionPlacementJob do it "records an error and does not attempt to send an email" do expect(job).to_not receive(:send_placement_email) expect(job).to receive(:record_and_log_error).once - job.send(:process, order) + job.send(:place_order, order) end end end From 1eba17f048e06a2806df4b58c37d31af557e8d21 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 18 Feb 2020 15:13:44 +0000 Subject: [PATCH 05/97] Make select column explicit to avoid too many columns sql error --- app/models/enterprise.rb | 4 ++-- app/models/enterprise_fee.rb | 2 +- app/models/enterprise_relationship.rb | 2 +- app/models/exchange.rb | 2 +- app/models/spree/order_decorator.rb | 6 ++++-- app/models/spree/payment_method_decorator.rb | 2 +- app/models/spree/shipping_method_decorator.rb | 2 +- app/models/spree/variant_decorator.rb | 2 +- lib/open_food_network/order_and_distributor_report.rb | 2 +- 9 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 1deee05a40..8211a17157 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -321,7 +321,7 @@ class Enterprise < ActiveRecord::Base def distributed_taxons Spree::Taxon. joins(:products). - where('spree_products.id IN (?)', Spree::Product.in_distributor(self)). + where('spree_products.id IN (?)', Spree::Product.in_distributor(self).select(&:id)). select('DISTINCT spree_taxons.*') end @@ -337,7 +337,7 @@ class Enterprise < ActiveRecord::Base def supplied_taxons Spree::Taxon. joins(:products). - where('spree_products.id IN (?)', Spree::Product.in_supplier(self)). + where('spree_products.id IN (?)', Spree::Product.in_supplier(self).select(&:id)). select('DISTINCT spree_taxons.*') end diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index 64c362b0fe..f26fee44eb 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -27,7 +27,7 @@ class EnterpriseFee < ActiveRecord::Base if user.has_spree_role?('admin') scoped else - where('enterprise_id IN (?)', user.enterprises) + where('enterprise_id IN (?)', user.enterprises.select(&:id)) end } diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index 6acd672f2f..e0b9c62c03 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -22,7 +22,7 @@ class EnterpriseRelationship < ActiveRecord::Base } scope :involving_enterprises, ->(enterprises) { - where('parent_id IN (?) OR child_id IN (?)', enterprises, enterprises) + where('parent_id IN (?) OR child_id IN (?)', enterprises.select(&:id), enterprises.select(&:id)) } scope :permitting, ->(enterprise_ids) { where('child_id IN (?)', enterprise_ids) } diff --git a/app/models/exchange.rb b/app/models/exchange.rb index ce28a7255d..4531ec042b 100644 --- a/app/models/exchange.rb +++ b/app/models/exchange.rb @@ -49,7 +49,7 @@ class Exchange < ActiveRecord::Base } scope :with_product, lambda { |product| joins(:exchange_variants). - where('exchange_variants.variant_id IN (?)', product.variants_including_master) + where('exchange_variants.variant_id IN (?)', product.variants_including_master.select(&:id)) } scope :by_enterprise_name, -> { joins('INNER JOIN enterprises AS sender ON (sender.id = exchanges.sender_id)'). diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 28d487d6fd..f0085d25e6 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -56,7 +56,9 @@ Spree::Order.class_eval do # Find orders that are distributed by the user or have products supplied by the user # WARNING: This only filters orders, you'll need to filter line items separately using LineItem.managed_by with_line_items_variants_and_products_outer. - where('spree_orders.distributor_id IN (?) OR spree_products.supplier_id IN (?)', user.enterprises, user.enterprises). + where('spree_orders.distributor_id IN (?) OR spree_products.supplier_id IN (?)', + user.enterprises.select(&:id), + user.enterprises.select(&:id)). select('DISTINCT spree_orders.*') end } @@ -65,7 +67,7 @@ Spree::Order.class_eval do if user.has_spree_role?('admin') scoped else - where('spree_orders.distributor_id IN (?)', user.enterprises) + where('spree_orders.distributor_id IN (?)', user.enterprises.select(&:id)) end } diff --git a/app/models/spree/payment_method_decorator.rb b/app/models/spree/payment_method_decorator.rb index f32944fdcb..6b917fcb4e 100644 --- a/app/models/spree/payment_method_decorator.rb +++ b/app/models/spree/payment_method_decorator.rb @@ -20,7 +20,7 @@ Spree::PaymentMethod.class_eval do scoped else joins(:distributors). - where('distributors_payment_methods.distributor_id IN (?)', user.enterprises). + where('distributors_payment_methods.distributor_id IN (?)', user.enterprises.select(&:id)). select('DISTINCT spree_payment_methods.*') end } diff --git a/app/models/spree/shipping_method_decorator.rb b/app/models/spree/shipping_method_decorator.rb index 813c947f38..ba7836899d 100644 --- a/app/models/spree/shipping_method_decorator.rb +++ b/app/models/spree/shipping_method_decorator.rb @@ -15,7 +15,7 @@ Spree::ShippingMethod.class_eval do scoped else joins(:distributors). - where('distributors_shipping_methods.distributor_id IN (?)', user.enterprises). + where('distributors_shipping_methods.distributor_id IN (?)', user.enterprises.select(&:id)). select('DISTINCT spree_shipping_methods.*') end } diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 84750cbe94..566394d946 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -49,7 +49,7 @@ Spree::Variant.class_eval do } scope :for_distribution, lambda { |order_cycle, distributor| - where('spree_variants.id IN (?)', order_cycle.variants_distributed_by(distributor)) + where('spree_variants.id IN (?)', order_cycle.variants_distributed_by(distributor).select(&:id)) } scope :visible_for, lambda { |enterprise| diff --git a/lib/open_food_network/order_and_distributor_report.rb b/lib/open_food_network/order_and_distributor_report.rb index b32ab803cc..64f46f41d1 100644 --- a/lib/open_food_network/order_and_distributor_report.rb +++ b/lib/open_food_network/order_and_distributor_report.rb @@ -68,7 +68,7 @@ module OpenFoodNetwork else orders. where('spree_orders.id NOT IN (?)', - @permissions.editable_orders) + @permissions.editable_orders.select(&:id)) end end From e15c61d86225e44b573c880bbc63fb47a19dd701 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 13 Feb 2020 16:13:08 +1100 Subject: [PATCH 06/97] Add spec for order confirmation view --- .../shared/_order_details.html.haml_spec.rb | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 spec/views/spree/shared/_order_details.html.haml_spec.rb diff --git a/spec/views/spree/shared/_order_details.html.haml_spec.rb b/spec/views/spree/shared/_order_details.html.haml_spec.rb new file mode 100644 index 0000000000..2c0da407e4 --- /dev/null +++ b/spec/views/spree/shared/_order_details.html.haml_spec.rb @@ -0,0 +1,24 @@ +require "spec_helper" + +describe "spree/shared/_order_details.html.haml" do + include AuthenticationWorkflow + helper Spree::BaseHelper + + let(:order) { create(:completed_order_with_fees) } + + before do + assign(:order, order) + allow(view).to receive_messages( + order: order, + current_order: order, + ) + end + + it "shows how the order is paid for" do + order.payments.first.payment_method.name = "Bartering" + + render + + expect(rendered).to have_content("Paying via: Bartering") + end +end From 7306d379a5d7ae6ff7981a79449eec310aa8bda2 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 13 Feb 2020 16:14:31 +1100 Subject: [PATCH 07/97] Display payment method literally Don't allow for HTML and potentially bad scripts. But this also prevents accidental display errors. If someone wrote, "We only take ", it would mess with the site. --- app/views/spree/shared/_order_details.html.haml | 4 ++-- spec/views/spree/shared/_order_details.html.haml_spec.rb | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index 667bf8b306..5020566c58 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -13,9 +13,9 @@ .pad .text-big = t :order_payment - %strong= order.payments.first.andand.payment_method.andand.name.andand.html_safe + %strong= order.payments.first.andand.payment_method.andand.name %p.text-small.text-skinny.pre-line - %em= order.payments.first.andand.payment_method.andand.description.andand.html_safe + %em= order.payments.first.andand.payment_method.andand.description .order-summary.text-small %strong diff --git a/spec/views/spree/shared/_order_details.html.haml_spec.rb b/spec/views/spree/shared/_order_details.html.haml_spec.rb index 2c0da407e4..e702b64f20 100644 --- a/spec/views/spree/shared/_order_details.html.haml_spec.rb +++ b/spec/views/spree/shared/_order_details.html.haml_spec.rb @@ -21,4 +21,12 @@ describe "spree/shared/_order_details.html.haml" do expect(rendered).to have_content("Paying via: Bartering") end + + it "displays payment methods safely" do + order.payments.first.payment_method.name = "Barter→ing" + + render + + expect(rendered).to have_content("Paying via: Barter→ing") + end end From 2c2023df033c85b220a1769e044c566a1b162935 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 13 Feb 2020 16:30:31 +1100 Subject: [PATCH 08/97] Show last payment method in order confirmation If we had multiple failed payments and then a successful payment, the order confirmation was displaying the payment method of the first failed payment. That was confusing and is now changed to the last payment method. --- app/helpers/orders_helper.rb | 10 +++++++++ .../spree/shared/_order_details.html.haml | 4 ++-- .../shared/_order_details.html.haml_spec.rb | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 app/helpers/orders_helper.rb diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb new file mode 100644 index 0000000000..ae94325a83 --- /dev/null +++ b/app/helpers/orders_helper.rb @@ -0,0 +1,10 @@ +module OrdersHelper + def order_paid_via(order) + # `sort_by` avoids additional database queries when payments are loaded + # already. There is usually only one payment and this shouldn't cause + # any overhead compared to `order(:updated_at)`. + # + # Using `last` without sort is not deterministic. + order.payments.sort_by(&:updated_at).last.andand.payment_method + end +end diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index 5020566c58..0a42574432 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -13,9 +13,9 @@ .pad .text-big = t :order_payment - %strong= order.payments.first.andand.payment_method.andand.name + %strong= order_paid_via(order).andand.name %p.text-small.text-skinny.pre-line - %em= order.payments.first.andand.payment_method.andand.description + %em= order_paid_via(order).andand.description .order-summary.text-small %strong diff --git a/spec/views/spree/shared/_order_details.html.haml_spec.rb b/spec/views/spree/shared/_order_details.html.haml_spec.rb index e702b64f20..a12d16b1d3 100644 --- a/spec/views/spree/shared/_order_details.html.haml_spec.rb +++ b/spec/views/spree/shared/_order_details.html.haml_spec.rb @@ -29,4 +29,26 @@ describe "spree/shared/_order_details.html.haml" do expect(rendered).to have_content("Paying via: Barter→ing") end + + it "shows the last used payment method" do + first_payment = order.payments.first + second_payment = create( + :payment, + order: order, + payment_method: create(:payment_method, name: "Cash") + ) + third_payment = create( + :payment, + order: order, + payment_method: create(:payment_method, name: "Credit") + ) + first_payment.update_column(:updated_at, 3.days.ago) + second_payment.update_column(:updated_at, 2.days.ago) + third_payment.update_column(:updated_at, 1.day.ago) + order.payments.reload + + render + + expect(rendered).to have_content("Paying via: Credit") + end end From 3e0a5bac6a7e44e1dee4837b2c086fddde105f0a Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 18 Feb 2020 10:40:58 +1100 Subject: [PATCH 09/97] Move helper to service for re-use --- .../orders_helper.rb => services/order_payment_finder.rb} | 6 ++++-- app/views/spree/shared/_order_details.html.haml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) rename app/{helpers/orders_helper.rb => services/order_payment_finder.rb} (78%) diff --git a/app/helpers/orders_helper.rb b/app/services/order_payment_finder.rb similarity index 78% rename from app/helpers/orders_helper.rb rename to app/services/order_payment_finder.rb index ae94325a83..7011941cf7 100644 --- a/app/helpers/orders_helper.rb +++ b/app/services/order_payment_finder.rb @@ -1,5 +1,7 @@ -module OrdersHelper - def order_paid_via(order) +# frozen_string_literal: true + +module OrderPaymentFinder + def self.last_payment_method(order) # `sort_by` avoids additional database queries when payments are loaded # already. There is usually only one payment and this shouldn't cause # any overhead compared to `order(:updated_at)`. diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index 0a42574432..2a9c6ee266 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -13,9 +13,9 @@ .pad .text-big = t :order_payment - %strong= order_paid_via(order).andand.name + %strong= OrderPaymentFinder.last_payment_method(order).andand.name %p.text-small.text-skinny.pre-line - %em= order_paid_via(order).andand.description + %em= OrderPaymentFinder.last_payment_method(order).andand.description .order-summary.text-small %strong From 6b66458bfd320df378f8948adcf448087c95767d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 18 Feb 2020 10:42:09 +1100 Subject: [PATCH 10/97] Replace andand with new Ruby syntax --- app/services/order_payment_finder.rb | 2 +- app/views/spree/shared/_order_details.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/order_payment_finder.rb b/app/services/order_payment_finder.rb index 7011941cf7..7040db3c6c 100644 --- a/app/services/order_payment_finder.rb +++ b/app/services/order_payment_finder.rb @@ -7,6 +7,6 @@ module OrderPaymentFinder # any overhead compared to `order(:updated_at)`. # # Using `last` without sort is not deterministic. - order.payments.sort_by(&:updated_at).last.andand.payment_method + order.payments.sort_by(&:updated_at).last&.payment_method end end diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index 2a9c6ee266..22c8ccdf8f 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -13,9 +13,9 @@ .pad .text-big = t :order_payment - %strong= OrderPaymentFinder.last_payment_method(order).andand.name + %strong= OrderPaymentFinder.last_payment_method(order)&.name %p.text-small.text-skinny.pre-line - %em= OrderPaymentFinder.last_payment_method(order).andand.description + %em= OrderPaymentFinder.last_payment_method(order)&.description .order-summary.text-small %strong From 799c1f08dea5aca8004c725f5e3f89a3c69cc33f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 18 Feb 2020 10:43:32 +1100 Subject: [PATCH 11/97] Optimise finding last payment Suggested by Rubocop. --- app/services/order_payment_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/order_payment_finder.rb b/app/services/order_payment_finder.rb index 7040db3c6c..f489c7b005 100644 --- a/app/services/order_payment_finder.rb +++ b/app/services/order_payment_finder.rb @@ -7,6 +7,6 @@ module OrderPaymentFinder # any overhead compared to `order(:updated_at)`. # # Using `last` without sort is not deterministic. - order.payments.sort_by(&:updated_at).last&.payment_method + order.payments.max_by(&:updated_at)&.payment_method end end From 11fbe7d5c96a8a2f256d59ffba49e5f3316e7c8f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 18 Feb 2020 10:45:12 +1100 Subject: [PATCH 12/97] Show last payment method in order confirmations --- app/views/spree/order_mailer/_payment.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/spree/order_mailer/_payment.html.haml b/app/views/spree/order_mailer/_payment.html.haml index 9e7a3a22c8..60fc1056f4 100644 --- a/app/views/spree/order_mailer/_payment.html.haml +++ b/app/views/spree/order_mailer/_payment.html.haml @@ -8,7 +8,7 @@ = t :email_payment_summary %h4 = t :email_payment_method - %strong= @order.payments.first.andand.payment_method.andand.name.andand.html_safe + %strong= OrderPaymentFinder.last_payment_method(@order)&.name %p - %em= @order.payments.first.andand.payment_method.andand.description.andand.html_safe + %em= OrderPaymentFinder.last_payment_method(@order)&.description %p   From 03fa3e22690ad89775880de6198cea61bdb8e54b Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 20 Feb 2020 15:59:53 +1100 Subject: [PATCH 13/97] Find last payment deterministically --- app/services/order_payment_finder.rb | 10 ++++++---- .../spree/shared/_order_details.html.haml_spec.rb | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/services/order_payment_finder.rb b/app/services/order_payment_finder.rb index f489c7b005..c28d0e139a 100644 --- a/app/services/order_payment_finder.rb +++ b/app/services/order_payment_finder.rb @@ -2,11 +2,13 @@ module OrderPaymentFinder def self.last_payment_method(order) - # `sort_by` avoids additional database queries when payments are loaded + # `max_by` avoids additional database queries when payments are loaded # already. There is usually only one payment and this shouldn't cause - # any overhead compared to `order(:updated_at)`. + # any overhead compared to `order(:created_at).last`. Using `last` + # without order is not deterministic. # - # Using `last` without sort is not deterministic. - order.payments.max_by(&:updated_at)&.payment_method + # We are not using `updated_at` because all payments are touched when the + # order is updated and then all payments have the same `updated_at` value. + order.payments.max_by(&:created_at)&.payment_method end end diff --git a/spec/views/spree/shared/_order_details.html.haml_spec.rb b/spec/views/spree/shared/_order_details.html.haml_spec.rb index a12d16b1d3..3bd04caf7c 100644 --- a/spec/views/spree/shared/_order_details.html.haml_spec.rb +++ b/spec/views/spree/shared/_order_details.html.haml_spec.rb @@ -42,9 +42,9 @@ describe "spree/shared/_order_details.html.haml" do order: order, payment_method: create(:payment_method, name: "Credit") ) - first_payment.update_column(:updated_at, 3.days.ago) - second_payment.update_column(:updated_at, 2.days.ago) - third_payment.update_column(:updated_at, 1.day.ago) + first_payment.update_column(:created_at, 3.days.ago) + second_payment.update_column(:created_at, 2.days.ago) + third_payment.update_column(:created_at, 1.day.ago) order.payments.reload render From 2412658e519c2b647493880b134446a0cf11316b Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Thu, 20 Feb 2020 11:41:49 +0000 Subject: [PATCH 14/97] Update db/schema timestamp according to last change --- db/schema.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 03550d3930..cbac7ede25 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20191023172424) do +ActiveRecord::Schema.define(:version => 20191202165700) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -1196,9 +1196,9 @@ ActiveRecord::Schema.define(:version => 20191023172424) do add_index "variant_overrides", ["variant_id", "hub_id"], :name => "index_variant_overrides_on_variant_id_and_hub_id" create_table "versions", :force => true do |t| - t.string "item_type", :null => false - t.integer "item_id", :null => false - t.string "event", :null => false + t.string "item_type", :null => false + t.integer "item_id", :null => false + t.string "event", :null => false t.string "whodunnit" t.text "object" t.datetime "created_at" From 06200c9d3c047af8cfc5aae84ee9bdc8c61ad14c Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Thu, 20 Feb 2020 18:04:43 +0000 Subject: [PATCH 15/97] Add new cops (disabled) to rubocop config --- .rubocop_styleguide.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.rubocop_styleguide.yml b/.rubocop_styleguide.yml index 01067a59ce..9615d7b9ba 100644 --- a/.rubocop_styleguide.yml +++ b/.rubocop_styleguide.yml @@ -117,6 +117,15 @@ Style/FormatString: Enabled: false StyleGuide: http://relaxed.ruby.style/#styleformatstring +Style/HashEachMethods: + Enabled: false + +Style/HashTransformKeys: + Enabled: false + +Style/HashTransformValues: + Enabled: false + Style/IfUnlessModifier: Enabled: false StyleGuide: http://relaxed.ruby.style/#styleifunlessmodifier From 5724c3bb0af61dfc206c91752ab6380ec19344c2 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 17:23:56 +0000 Subject: [PATCH 16/97] Add code from ActiveMerchant v1.98.0 that supports the Stripe Payment Intents API This commit can be reverted once we upgrade to v1.98.0 --- .../billing/gateways/stripe_decorator.rb | 16 ++ .../gateways/stripe_payment_intents.rb | 242 ++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/stripe_decorator.rb create mode 100644 lib/active_merchant/billing/gateways/stripe_payment_intents.rb diff --git a/lib/active_merchant/billing/gateways/stripe_decorator.rb b/lib/active_merchant/billing/gateways/stripe_decorator.rb new file mode 100644 index 0000000000..5d3aa90958 --- /dev/null +++ b/lib/active_merchant/billing/gateways/stripe_decorator.rb @@ -0,0 +1,16 @@ +# Here we bring commit 823faaeab0d6d3bd75ee037ec894ab7c9d95d3a9 from ActiveMerchant v1.98.0 +# This is needed to make StripePaymentIntents work correctly +# This can be removed once we upgrade to ActiveMerchant v1.98.0 +ActiveMerchant::Billing::StripeGateway.class_eval do + def authorization_from(success, url, method, response) + return response.fetch('error', {})['charge'] unless success + + if url == 'customers' + [response['id'], response.dig('sources', 'data').first&.dig('id')].join('|') + elsif method == :post && (url.match(/customers\/.*\/cards/) || url.match(/payment_methods\/.*\/attach/)) + [response['customer'], response['id']].join('|') + else + response['id'] + end + end +end diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb new file mode 100644 index 0000000000..f022c68323 --- /dev/null +++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb @@ -0,0 +1,242 @@ +# Here we bring commit 823faaeab0d6d3bd75ee037ec894ab7c9d95d3a9 from ActiveMerchant v1.98.0 +# This class integrates with the new StripePaymentIntents API +# This can be removed once we upgrade to ActiveMerchant v1.98.0 +require 'active_support/core_ext/hash/slice' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This gateway uses the current Stripe {Payment Intents API}[https://stripe.com/docs/api/payment_intents]. + # For the legacy API, see the Stripe gateway + class StripePaymentIntentsGateway < StripeGateway + ALLOWED_METHOD_STATES = %w[automatic manual].freeze + ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze + CREATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email save_payment_method] + CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session] + UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email setup_future_usage] + DEFAULT_API_VERSION = '2019-05-16' + + def create_intent(money, payment_method, options = {}) + post = {} + add_amount(post, money, options, true) + add_capture_method(post, options) + add_confirmation_method(post, options) + add_customer(post, options) + add_payment_method_token(post, payment_method, options) + add_metadata(post, options) + add_return_url(post, options) + add_connected_account(post, options) + add_shipping_address(post, options) + setup_future_usage(post, options) + + CREATE_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + + commit(:post, 'payment_intents', post, options) + end + + def show_intent(intent_id, options) + commit(:get, "payment_intents/#{intent_id}", nil, options) + end + + def confirm_intent(intent_id, payment_method, options = {}) + post = {} + add_payment_method_token(post, payment_method, options) + CONFIRM_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + + commit(:post, "payment_intents/#{intent_id}/confirm", post, options) + end + + def create_payment_method(payment_method, options = {}) + post = {} + post[:type] = 'card' + post[:card] = {} + post[:card][:number] = payment_method.number + post[:card][:exp_month] = payment_method.month + post[:card][:exp_year] = payment_method.year + post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value + + commit(:post, 'payment_methods', post, options) + end + + def update_intent(money, intent_id, payment_method, options = {}) + post = {} + post[:amount] = money if money + + add_payment_method_token(post, payment_method, options) + add_payment_method_types(post, options) + add_customer(post, options) + add_metadata(post, options) + add_shipping_address(post, options) + add_connected_account(post, options) + + UPDATE_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + + commit(:post, "payment_intents/#{intent_id}", post, options) + end + + def authorize(money, payment_method, options = {}) + create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual')) + end + + def purchase(money, payment_method, options = {}) + create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'automatic')) + end + + def capture(money, intent_id, options = {}) + post = {} + post[:amount_to_capture] = money + add_connected_account(post, options) + commit(:post, "payment_intents/#{intent_id}/capture", post, options) + end + + def void(intent_id, options = {}) + post = {} + post[:cancellation_reason] = options[:cancellation_reason] if ALLOWED_CANCELLATION_REASONS.include?(options[:cancellation_reason]) + commit(:post, "payment_intents/#{intent_id}/cancel", post, options) + end + + def refund(money, intent_id, options = {}) + intent = commit(:get, "payment_intents/#{intent_id}", nil, options) + charge_id = intent.params.dig('charges', 'data')[0].dig('id') + super(money, charge_id, options) + end + + # Note: Not all payment methods are currently supported by the {Payment Methods API}[https://stripe.com/docs/payments/payment-methods] + # Current implementation will create a PaymentMethod object if the method is a token or credit card + # All other types will default to legacy Stripe store + def store(payment_method, options = {}) + params = {} + post = {} + + # If customer option is provided, create a payment method and attach to customer id + # Otherwise, create a customer, then attach + #if payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard) + add_payment_method_token(params, payment_method, options) + if options[:customer] + customer_id = options[:customer] + else + post[:validate] = options[:validate] unless options[:validate].nil? + post[:description] = options[:description] if options[:description] + post[:email] = options[:email] if options[:email] + customer = commit(:post, 'customers', post, options) + customer_id = customer.params['id'] + end + commit(:post, "payment_methods/#{params[:payment_method]}/attach", { customer: customer_id }, options) + #else + # super(payment, options) + #end + end + + def unstore(identification, options = {}, deprecated_options = {}) + if identification.include?('pm_') + _, payment_method = identification.split('|') + commit(:post, "payment_methods/#{payment_method}/detach", nil, options) + else + super(identification, options, deprecated_options) + end + end + + private + + def add_whitelisted_attribute(post, options, attribute) + post[attribute] = options[attribute] if options[attribute] + post + end + + def add_capture_method(post, options) + capture_method = options[:capture_method].to_s + post[:capture_method] = capture_method if ALLOWED_METHOD_STATES.include?(capture_method) + post + end + + def add_confirmation_method(post, options) + confirmation_method = options[:confirmation_method].to_s + post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method) + post + end + + def add_customer(post, options) + customer = options[:customer].to_s + post[:customer] = customer if customer.start_with?('cus_') + post + end + + def add_return_url(post, options) + return unless options[:confirm] + post[:confirm] = options[:confirm] + post[:return_url] = options[:return_url] if options[:return_url] + post + end + + def add_payment_method_token(post, payment_method, options) + return if payment_method.nil? + + if payment_method.is_a?(ActiveMerchant::Billing::CreditCard) + p = create_payment_method(payment_method, options) + payment_method = p.params['id'] + end + + if payment_method.is_a?(StripePaymentToken) + post[:payment_method] = payment_method.payment_data['id'] + elsif payment_method.is_a?(String) + if payment_method.include?('|') + customer_id, payment_method_id = payment_method.split('|') + token = payment_method_id + post[:customer] = customer_id + else + token = payment_method + end + post[:payment_method] = token + end + end + + def add_payment_method_types(post, options) + payment_method_types = options[:payment_method_types] if options[:payment_method_types] + return if payment_method_types.nil? + + post[:payment_method_types] = Array(payment_method_types) + post + end + + def setup_future_usage(post, options = {}) + post[:setup_future_usage] = options[:setup_future_usage] if %w( on_session off_session ).include?(options[:setup_future_usage]) + post[:off_session] = options[:off_session] if options[:off_session] && options[:confirm] == true + post + end + + def add_connected_account(post, options = {}) + return unless transfer_data = options[:transfer_data] + post[:transfer_data] = {} + post[:transfer_data][:destination] = transfer_data[:destination] if transfer_data[:destination] + post[:transfer_data][:amount] = transfer_data[:amount] if transfer_data[:amount] + post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of] + post[:transfer_group] = options[:transfer_group] if options[:transfer_group] + post[:application_fee_amount] = options[:application_fee] if options[:application_fee] + post + end + + def add_shipping_address(post, options = {}) + return unless shipping = options[:shipping] + post[:shipping] = {} + post[:shipping][:address] = {} + post[:shipping][:address][:line1] = shipping[:address][:line1] + post[:shipping][:address][:city] = shipping[:address][:city] if shipping[:address][:city] + post[:shipping][:address][:country] = shipping[:address][:country] if shipping[:address][:country] + post[:shipping][:address][:line2] = shipping[:address][:line2] if shipping[:address][:line2] + post[:shipping][:address][:postal_code] = shipping[:address][:postal_code] if shipping[:address][:postal_code] + post[:shipping][:address][:state] = shipping[:address][:state] if shipping[:address][:state] + + post[:shipping][:name] = shipping[:name] + post[:shipping][:carrier] = shipping[:carrier] if shipping[:carrier] + post[:shipping][:phone] = shipping[:phone] if shipping[:phone] + post[:shipping][:tracking_number] = shipping[:tracking_number] if shipping[:tracking_number] + post + end + end + end +end From f691d1aafd2c826ec97ebf85e0696e30c9a5d7c1 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 17:27:17 +0000 Subject: [PATCH 17/97] Add new payment method StripeSCA that will use the Stripe Payment Intents API instead of the Stripe Charges API that the current StripeConnect gatreway uses --- .../darkswarm/services/checkout.js.coffee | 4 +- .../services/stripe_elements.js.coffee | 22 ++++- .../spree/admin/payment_methods_controller.rb | 16 +++- app/models/spree/gateway/stripe_sca.rb | 88 +++++++++++++++++++ .../_provider_settings.html.haml | 3 + config/application.rb | 1 + 6 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 app/models/spree/gateway/stripe_sca.rb diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee index 204c35ac66..83cf018365 100644 --- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee @@ -7,6 +7,8 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeE purchase: -> if @paymentMethod()?.method_type == 'stripe' && !@secrets.selected_card StripeElements.requestToken(@secrets, @submit) + else if @paymentMethod()?.method_type == 'stripe_sca' && !@secrets.selected_card + StripeElements.createPaymentMethod(@secrets, @submit) else @submit() @@ -59,7 +61,7 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeE last_name: @order.bill_address.lastname } - if @paymentMethod()?.method_type == 'stripe' + if @paymentMethod()?.method_type == 'stripe' || @paymentMethod()?.method_type == 'stripe_sca' if @secrets.selected_card angular.extend munged_order, { existing_card_id: @secrets.selected_card diff --git a/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee b/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee index 32b0535251..89b79e78fd 100644 --- a/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee +++ b/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee @@ -1,12 +1,10 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) -> new class StripeElements - # TODO: add locale here for translations of error messages etc. from Stripe - # These are both set from the StripeElements directive stripe: null card: null - # New Stripe Elements method + # Create Token to be used with the Stripe Charges API requestToken: (secrets, submit, loading_message = t("processing_payment")) -> return unless @stripe? && @card? @@ -23,6 +21,24 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) -> secrets.card = response.token.card submit() + # Create Payment Method to be used with the Stripe Payment Intents API + createPaymentMethod: (secrets, submit, loading_message = t("processing_payment")) -> + return unless @stripe? && @card? + + Loading.message = loading_message + cardData = @makeCardData(secrets) + + @stripe.createPaymentMethod({ type: 'card', card: @card } + @card, cardData).then (response) => + if(response.error) + Loading.clear() + RailsFlashLoader.loadFlash({error: t("error") + ": #{response.error.message}"}) + else + secrets.token = response.paymentMethod.id + secrets.cc_type = response.paymentMethod.card.brand + secrets.card = response.paymentMethod.card + submit() + # Maps the brand returned by Stripe to that required by activemerchant mapCC: (ccType) -> switch ccType diff --git a/app/controllers/spree/admin/payment_methods_controller.rb b/app/controllers/spree/admin/payment_methods_controller.rb index 53bf22d06a..ad0bff9a3b 100644 --- a/app/controllers/spree/admin/payment_methods_controller.rb +++ b/app/controllers/spree/admin/payment_methods_controller.rb @@ -110,7 +110,7 @@ module Spree else Gateway.providers.reject{ |p| p.name.include? "Bogus" }.sort_by(&:name) end - @providers.reject!{ |p| p.name.ends_with? "StripeConnect" } unless show_stripe? + @providers.reject!{ |provider| stripe_provider?(provider) } unless show_stripe? @calculators = PaymentMethod.calculators.sort_by(&:name) end @@ -134,12 +134,12 @@ module Spree # current payment_method is already a Stripe method def show_stripe? Spree::Config.stripe_connect_enabled || - @payment_method.try(:type) == "Spree::Gateway::StripeConnect" + stripe_payment_method? end def restrict_stripe_account_change return unless @payment_method - return unless @payment_method.type == "Spree::Gateway::StripeConnect" + return unless stripe_payment_method? return unless @payment_method.preferred_enterprise_id.andand > 0 @stripe_account_holder = Enterprise.find(@payment_method.preferred_enterprise_id) @@ -147,6 +147,16 @@ module Spree params[:payment_method][:preferred_enterprise_id] = @stripe_account_holder.id end + + def stripe_payment_method? + @payment_method.try(:type) == "Spree::Gateway::StripeConnect" || + @payment_method.try(:type) == "Spree::Gateway::StripeSCA" + end + + def stripe_provider?(provider) + provider.name.ends_with?("StripeConnect") || + provider.name.ends_with?("StripeSCA") + end end end end diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb new file mode 100644 index 0000000000..c8f4d56637 --- /dev/null +++ b/app/models/spree/gateway/stripe_sca.rb @@ -0,0 +1,88 @@ +require 'stripe/profile_storer' +require 'active_merchant/billing/gateways/stripe_payment_intents' +require 'active_merchant/billing/gateways/stripe_decorator' + +module Spree + class Gateway + class StripeSCA < Gateway + preference :enterprise_id, :integer + + validate :ensure_enterprise_selected + + attr_accessible :preferred_enterprise_id + + def method_type + 'stripe_sca' + end + + def provider_class + ActiveMerchant::Billing::StripePaymentIntentsGateway + end + + def payment_profiles_supported? + true + end + + def stripe_account_id + StripeAccount.find_by_enterprise_id(preferred_enterprise_id).andand.stripe_user_id + end + + # NOTE: the name of this method is determined by Spree::Payment::Processing + def purchase(money, creditcard, gateway_options) + provider.purchase(*options_for_purchase_or_auth(money, creditcard, gateway_options)) + rescue Stripe::StripeError => e + # This will be an error caused by generating a stripe token + failed_activemerchant_billing_response(e.message) + end + + # NOTE: the name of this method is determined by Spree::Payment::Processing + def void(response_code, _creditcard, gateway_options) + gateway_options[:stripe_account] = stripe_account_id + provider.void(response_code, gateway_options) + end + + # NOTE: the name of this method is determined by Spree::Payment::Processing + def credit(money, _creditcard, response_code, gateway_options) + gateway_options[:stripe_account] = stripe_account_id + provider.refund(money, response_code, gateway_options) + end + + def create_profile(payment) + return unless payment.source.gateway_customer_profile_id.nil? + + profile_storer = Stripe::ProfileStorer.new(payment, provider, stripe_account_id) + profile_storer.create_customer_from_token + end + + private + + # In this gateway, what we call 'secret_key' is the 'login' + def options + options = super + options.merge(login: Stripe.api_key) + end + + def options_for_purchase_or_auth(money, creditcard, gateway_options) + options = {} + options[:description] = "Spree Order ID: #{gateway_options[:order_id]}" + options[:currency] = gateway_options[:currency] + options[:stripe_account] = stripe_account_id + + options[:customer] = creditcard.gateway_customer_profile_id + creditcard = creditcard.gateway_payment_profile_id + + [money, creditcard, options] + end + + def failed_activemerchant_billing_response(error_message) + ActiveMerchant::Billing::Response.new(false, error_message) + end + + def ensure_enterprise_selected + return if preferred_enterprise_id.andand > 0 + + errors.add(:stripe_account_owner, I18n.t(:error_required)) + end + end + end +end diff --git a/app/views/spree/admin/payment_methods/_provider_settings.html.haml b/app/views/spree/admin/payment_methods/_provider_settings.html.haml index c64ad9f1d2..38f2ff06e9 100644 --- a/app/views/spree/admin/payment_methods/_provider_settings.html.haml +++ b/app/views/spree/admin/payment_methods/_provider_settings.html.haml @@ -1,6 +1,9 @@ += @payment_method - case @payment_method - when Spree::Gateway::StripeConnect = render 'stripe_connect' +- when Spree::Gateway::StripeSCA + = render 'stripe_connect' - else - if @payment_method.preferences.present? %fieldset.alpha.eleven.columns.no-border-bottom#gateway_fields diff --git a/config/application.rb b/config/application.rb index 057d50ff2f..2799e52d2f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -92,6 +92,7 @@ module Openfoodnetwork app.config.spree.payment_methods << Spree::Gateway::Migs app.config.spree.payment_methods << Spree::Gateway::Pin app.config.spree.payment_methods << Spree::Gateway::StripeConnect + app.config.spree.payment_methods << Spree::Gateway::StripeSCA end # Settings in config/environments/* take precedence over those specified here. From 283abf9a88baaaaaba0bd9e14bb36ef5b0b5e0d9 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 17:42:01 +0000 Subject: [PATCH 18/97] Remove dead code from Stripe connect gateway Update Source is dead since a74c502fd9b1c34b3f76d15601e06d532c2dbfc8 --- app/models/spree/gateway/stripe_connect.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/models/spree/gateway/stripe_connect.rb b/app/models/spree/gateway/stripe_connect.rb index 052423e5e0..d1a7a70c2b 100644 --- a/app/models/spree/gateway/stripe_connect.rb +++ b/app/models/spree/gateway/stripe_connect.rb @@ -9,12 +9,6 @@ module Spree attr_accessible :preferred_enterprise_id - CARD_TYPE_MAPPING = { - 'American Express' => 'american_express', - 'Diners Club' => 'diners_club', - 'Visa' => 'visa' - }.freeze - def method_type 'stripe' end @@ -77,11 +71,6 @@ module Spree [money, creditcard, options] end - def update_source!(source) - source.cc_type = CARD_TYPE_MAPPING[source.cc_type] if CARD_TYPE_MAPPING.include?(source.cc_type) - source - end - def token_from_card_profile_ids(creditcard) token_or_card_id = creditcard.gateway_payment_profile_id customer = creditcard.gateway_customer_profile_id From a52c4b542c2c7a604f8d9383f2b419ea48461794 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 17:45:26 +0000 Subject: [PATCH 19/97] Make destroy stored cards work for stripe SCA by setting stripe account id before making the call to the stripe api This account id cannot be sent when dealing with the old StripeConnect gateway --- app/controllers/spree/credit_cards_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/spree/credit_cards_controller.rb b/app/controllers/spree/credit_cards_controller.rb index 79f60b4323..a46937eb6c 100644 --- a/app/controllers/spree/credit_cards_controller.rb +++ b/app/controllers/spree/credit_cards_controller.rb @@ -54,10 +54,17 @@ module Spree # Currently can only destroy the whole customer object def destroy_at_stripe - stripe_customer = Stripe::Customer.retrieve(@credit_card.gateway_customer_profile_id) + options = { stripe_account: stripe_account_id } if @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" + + stripe_customer = Stripe::Customer.retrieve(@credit_card.gateway_customer_profile_id, options || {}) stripe_customer.delete if stripe_customer end + def stripe_account_id + StripeAccount.find_by_enterprise_id(@credit_card.payment_method.preferred_enterprise_id).andand.stripe_user_id + end + + def create_customer(token) Stripe::Customer.create(email: spree_current_user.email, source: token) end From 9fa4bad0b45e30da7e9e414d392961135d7c093f Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 17:48:01 +0000 Subject: [PATCH 20/97] Add stripe SCA checkotu payment template and move stripe object definition to it and the other stripe template We need to set the stripe object with the stripe account id to work with the payment intents api but we cannot set it to work with the stripe charges api This makes the two payment methods incompatible: a given enterprise cannot use both the old stripe integration and this new one at the same time. --- app/views/checkout/_payment.html.haml | 5 ----- .../spree/checkout/payment/_stripe.html.haml | 5 +++++ .../checkout/payment/_stripe_sca.html.haml | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 app/views/spree/checkout/payment/_stripe_sca.html.haml diff --git a/app/views/checkout/_payment.html.haml b/app/views/checkout/_payment.html.haml index fdbd103203..928578158f 100644 --- a/app/views/checkout/_payment.html.haml +++ b/app/views/checkout/_payment.html.haml @@ -1,8 +1,3 @@ -- content_for :injection_data do - - if Stripe.publishable_key - :javascript - angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) - %fieldset#payment %ng-form{"ng-controller" => "PaymentCtrl", name: "payment"} diff --git a/app/views/spree/checkout/payment/_stripe.html.haml b/app/views/spree/checkout/payment/_stripe.html.haml index eace9f00a7..2929b90d3e 100644 --- a/app/views/spree/checkout/payment/_stripe.html.haml +++ b/app/views/spree/checkout/payment/_stripe.html.haml @@ -1,3 +1,8 @@ +- content_for :injection_data do + - if Stripe.publishable_key + :javascript + angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) + .row{ "ng-show" => "savedCreditCards.length > 0" } .small-12.columns %h6= t('.used_saved_card') diff --git a/app/views/spree/checkout/payment/_stripe_sca.html.haml b/app/views/spree/checkout/payment/_stripe_sca.html.haml new file mode 100644 index 0000000000..00ded42afe --- /dev/null +++ b/app/views/spree/checkout/payment/_stripe_sca.html.haml @@ -0,0 +1,22 @@ +- content_for :injection_data do + - if Stripe.publishable_key + :javascript + angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}", { stripeAccount: "#{StripeAccount.find_by_enterprise_id(payment_method.preferred_enterprise_id).andand.stripe_user_id}" })) + +.row{ "ng-show" => "savedCreditCards.length > 0" } + .small-12.columns + %h6= t('.used_saved_card') + %select{ name: "selected_card", required: false, ng: { model: "secrets.selected_card", options: "card.id as card.formatted for card in savedCreditCards" } } + %option{ value: "" }= "{{ secrets.selected_card ? '#{t('.enter_new_card')}' : '#{t('.choose_one')}' }}" + + %h6{ ng: { if: '!secrets.selected_card' } } + = t('.or_enter_new_card') + +%div{ ng: { if: '!secrets.selected_card' } } + %stripe-elements + + - if spree_current_user + .row + .small-12.columns.text-right + = check_box_tag 'secrets.save_requested_by_customer', '1', false, 'ng-model' => 'secrets.save_requested_by_customer' + = label_tag 'secrets.save_requested_by_customer', t('.remember_this_card') From db1065a69e60a06a596a44c48081d2f09e029e0c Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 17:49:03 +0000 Subject: [PATCH 21/97] Make saving a card on checkout work with the payment intents api by making profile storer work with the slightly different api responses from stripe --- lib/stripe/profile_storer.rb | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/stripe/profile_storer.rb b/lib/stripe/profile_storer.rb index 1b8928e884..1d6cfd7f82 100644 --- a/lib/stripe/profile_storer.rb +++ b/lib/stripe/profile_storer.rb @@ -4,9 +4,10 @@ module Stripe class ProfileStorer - def initialize(payment, provider) + def initialize(payment, provider, stripe_account_id = nil) @payment = payment @provider = provider + @stripe_account_id = stripe_account_id end def create_customer_from_token @@ -14,7 +15,11 @@ module Stripe response = @provider.store(token, options) if response.success? - attrs = source_attrs_from(response) + if response.params['customer'] # Payment Intents API + attrs = stripe_sca_attrs_from(response) + else + attrs = stripe_connect_attrs_from(response) + end @payment.source.update_attributes!(attrs) else @payment.__send__(:gateway_error, response.message) @@ -24,11 +29,13 @@ module Stripe private def options - { - email: @payment.order.email, - login: Stripe.api_key, - address: address_for(@payment) - } + options = { + email: @payment.order.email, + login: Stripe.api_key, + address: address_for(@payment) + } + options = options.merge({ stripe_account: @stripe_account_id }) if @stripe_account_id.present? + options end def address_for(payment) @@ -52,9 +59,17 @@ module Stripe end end - def source_attrs_from(response) + def stripe_sca_attrs_from(response) { - cc_type: @payment.source.cc_type, # side-effect of update_source! + cc_type: @payment.source.cc_type, + gateway_customer_profile_id: response.params['customer'], + gateway_payment_profile_id: response.params['id'] + } + end + + def stripe_connect_attrs_from(response) + { + cc_type: @payment.source.cc_type, gateway_customer_profile_id: response.params['id'], gateway_payment_profile_id: response.params['default_source'] || response.params['default_card'] } From c773cde191924dc54bc23ae931f6021b652ba230 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 18:03:07 +0000 Subject: [PATCH 22/97] Add admin payment template for stripe sca and respective js code to make it work --- .../admin/payments/services/payment.js.coffee | 11 +++++++++++ .../payments/services/stripe_elements.js.coffee | 17 ++++++++++++++++- .../services/stripe_elements.js.coffee | 3 +-- .../payments/source_forms/_stripe_sca.html.haml | 16 ++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml diff --git a/app/assets/javascripts/admin/payments/services/payment.js.coffee b/app/assets/javascripts/admin/payments/services/payment.js.coffee index f079137818..f5cab3061c 100644 --- a/app/assets/javascripts/admin/payments/services/payment.js.coffee +++ b/app/assets/javascripts/admin/payments/services/payment.js.coffee @@ -30,11 +30,22 @@ angular.module('admin.payments').factory 'Payment', (AdminStripeElements, curren month: @form_data.card.exp_month year: @form_data.card.exp_year } + when 'stripe_sca' + angular.extend munged_payment.payment, { + source_attributes: + gateway_payment_profile_id: @form_data.token + cc_type: @form_data.cc_type + last_digits: @form_data.card.last4 + month: @form_data.card.exp_month + year: @form_data.card.exp_year + } munged_payment purchase: -> if @paymentMethodType() == 'stripe' AdminStripeElements.requestToken(@form_data, @submit) + else if @paymentMethodType() == 'stripe_sca' + AdminStripeElements.createPaymentMethod(@form_data, @submit) else @submit() diff --git a/app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee b/app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee index 03971be228..9ecf2b1db1 100644 --- a/app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee +++ b/app/assets/javascripts/admin/payments/services/stripe_elements.js.coffee @@ -5,7 +5,7 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta stripe: null card: null - # New Stripe Elements method + # Create Token to be used with the Stripe Charges API requestToken: (secrets, submit) -> return unless @stripe? && @card? @@ -20,6 +20,21 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta secrets.card = response.token.card submit() + # Create Payment Method to be used with the Stripe Payment Intents API + createPaymentMethod: (secrets, submit) -> + return unless @stripe? && @card? + + cardData = @makeCardData(secrets) + + @stripe.createPaymentMethod({ type: 'card', card: @card }, @card, cardData).then (response) => + if(response.error) + StatusMessage.display 'error', response.error.message + else + secrets.token = response.paymentMethod.id + secrets.cc_type = response.paymentMethod.card.brand + secrets.card = response.paymentMethod.card + submit() + # Maps the brand returned by Stripe to that required by activemerchant mapCC: (ccType) -> switch ccType diff --git a/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee b/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee index 89b79e78fd..acd220f092 100644 --- a/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee +++ b/app/assets/javascripts/darkswarm/services/stripe_elements.js.coffee @@ -28,8 +28,7 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) -> Loading.message = loading_message cardData = @makeCardData(secrets) - @stripe.createPaymentMethod({ type: 'card', card: @card } - @card, cardData).then (response) => + @stripe.createPaymentMethod({ type: 'card', card: @card }, @card, cardData).then (response) => if(response.error) Loading.clear() RailsFlashLoader.loadFlash({error: t("error") + ": #{response.error.message}"}) diff --git a/app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml b/app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml new file mode 100644 index 0000000000..b8939ea4a7 --- /dev/null +++ b/app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml @@ -0,0 +1,16 @@ +.stripe + %script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"} + - if Stripe.publishable_key + :javascript + angular.module('admin.payments').value("stripeObject", Stripe("#{Stripe.publishable_key}", { stripeAccount: "#{StripeAccount.find_by_enterprise_id(payment_method.preferred_enterprise_id).andand.stripe_user_id}" })) + + .row + .three.columns + = label_tag :cardholder_name, t(:cardholder_name) + .six.columns + = text_field_tag :cardholder_name, nil, {size: 40, "ng-model" => 'form_data.name'} + .row + .three.columns + = label_tag :card_details, t(:card_details) + .six.columns + %stripe-elements From ec7b91bb686cdc5271d9a5188ecb44d00611fcde Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 19:21:39 +0000 Subject: [PATCH 23/97] Make ProfileStorer a bit easier to read --- lib/stripe/profile_storer.rb | 42 ++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/stripe/profile_storer.rb b/lib/stripe/profile_storer.rb index 1d6cfd7f82..50a0ba4b31 100644 --- a/lib/stripe/profile_storer.rb +++ b/lib/stripe/profile_storer.rb @@ -15,11 +15,7 @@ module Stripe response = @provider.store(token, options) if response.success? - if response.params['customer'] # Payment Intents API - attrs = stripe_sca_attrs_from(response) - else - attrs = stripe_connect_attrs_from(response) - end + attrs = source_attrs_from(response) @payment.source.update_attributes!(attrs) else @payment.__send__(:gateway_error, response.message) @@ -30,11 +26,11 @@ module Stripe def options options = { - email: @payment.order.email, - login: Stripe.api_key, - address: address_for(@payment) - } - options = options.merge({ stripe_account: @stripe_account_id }) if @stripe_account_id.present? + email: @payment.order.email, + login: Stripe.api_key, + address: address_for(@payment) + } + options = options.merge(stripe_account: @stripe_account_id) if @stripe_account_id.present? options end @@ -59,20 +55,28 @@ module Stripe end end - def stripe_sca_attrs_from(response) + def source_attrs_from(response) { cc_type: @payment.source.cc_type, - gateway_customer_profile_id: response.params['customer'], - gateway_payment_profile_id: response.params['id'] + gateway_customer_profile_id: customer_profile_id(response), + gateway_payment_profile_id: payment_profile_id(response) } end - def stripe_connect_attrs_from(response) - { - cc_type: @payment.source.cc_type, - gateway_customer_profile_id: response.params['id'], - gateway_payment_profile_id: response.params['default_source'] || response.params['default_card'] - } + def customer_profile_id(response) + if response.params['customer'] # Payment Intents API + response.params['customer'] + else + response.params['id'] + end + end + + def payment_profile_id(response) + if response.params['customer'] # Payment Intents API + response.params['id'] + else + response.params['default_source'] || response.params['default_card'] + end end end end From 1b820ea85c6df06dacd65bdb36f6d00214979194 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 19:27:16 +0000 Subject: [PATCH 24/97] Fix rubocop issues in credit_cards_controller --- .../spree/credit_cards_controller.rb | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/controllers/spree/credit_cards_controller.rb b/app/controllers/spree/credit_cards_controller.rb index a46937eb6c..3da82cd698 100644 --- a/app/controllers/spree/credit_cards_controller.rb +++ b/app/controllers/spree/credit_cards_controller.rb @@ -10,10 +10,14 @@ module Spree render json: @credit_card, serializer: ::Api::CreditCardSerializer, status: :ok else message = t(:card_could_not_be_saved) - render json: { flash: { error: I18n.t(:spree_gateway_error_flash_for_checkout, error: message) } }, status: :bad_request + render json: { flash: { error: I18n.t(:spree_gateway_error_flash_for_checkout, + error: message) } }, + status: :bad_request end rescue Stripe::CardError => e - render json: { flash: { error: I18n.t(:spree_gateway_error_flash_for_checkout, error: e.message) } }, status: :bad_request + render json: { flash: { error: I18n.t(:spree_gateway_error_flash_for_checkout, + error: e.message) } }, + status: :bad_request end def update @@ -54,17 +58,22 @@ module Spree # Currently can only destroy the whole customer object def destroy_at_stripe - options = { stripe_account: stripe_account_id } if @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" + if @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" + options = { stripe_account: stripe_account_id } + end - stripe_customer = Stripe::Customer.retrieve(@credit_card.gateway_customer_profile_id, options || {}) + stripe_customer = Stripe::Customer.retrieve(@credit_card.gateway_customer_profile_id, + options || {}) stripe_customer.delete if stripe_customer end def stripe_account_id - StripeAccount.find_by_enterprise_id(@credit_card.payment_method.preferred_enterprise_id).andand.stripe_user_id + StripeAccount. + find_by_enterprise_id(@credit_card.payment_method.preferred_enterprise_id). + andand. + stripe_user_id end - def create_customer(token) Stripe::Customer.create(email: spree_current_user.email, source: token) end From ac8f3c811f182d12b59b4fd5b90e011ee68d9207 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 13 Jan 2020 19:57:45 +0000 Subject: [PATCH 25/97] Fix rubocop issues in some stripe integration related files --- .rubocop_manual_todo.yml | 6 ++ app/models/spree/gateway/stripe_sca.rb | 4 +- .../billing/gateways/stripe_decorator.rb | 5 +- .../gateways/stripe_payment_intents.rb | 92 +++++++++++++------ lib/stripe/profile_storer.rb | 6 +- 5 files changed, 76 insertions(+), 37 deletions(-) diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index cec6bd232e..bc88b63cd3 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -413,6 +413,8 @@ Metrics/AbcSize: - app/services/create_order_cycle.rb - app/services/order_syncer.rb - app/services/subscription_validator.rb + - lib/active_merchant/billing/gateways/stripe_decorator.rb + - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/discourse/single_sign_on.rb - lib/open_food_network/bulk_coop_report.rb - lib/open_food_network/customers_report.rb @@ -506,6 +508,7 @@ Metrics/CyclomaticComplexity: - app/models/spree/product_decorator.rb - app/models/variant_override_set.rb - app/services/cart_service.rb + - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/discourse/single_sign_on.rb - lib/open_food_network/bulk_coop_report.rb - lib/open_food_network/enterprise_issue_validator.rb @@ -531,6 +534,7 @@ Metrics/PerceivedComplexity: - app/models/spree/ability_decorator.rb - app/models/spree/order_decorator.rb - app/models/spree/product_decorator.rb + - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/discourse/single_sign_on.rb - lib/open_food_network/bulk_coop_report.rb - lib/open_food_network/enterprise_issue_validator.rb @@ -600,6 +604,7 @@ Metrics/MethodLength: - app/serializers/api/cached_enterprise_serializer.rb - app/services/order_cycle_form.rb - engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb + - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/discourse/single_sign_on.rb - lib/open_food_network/bulk_coop_report.rb - lib/open_food_network/column_preference_defaults.rb @@ -663,6 +668,7 @@ Metrics/ClassLength: - app/serializers/api/enterprise_shopfront_serializer.rb - app/services/cart_service.rb - engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb + - lib/active_merchant/billing/gateways/stripe_payment_intents.rb - lib/open_food_network/bulk_coop_report.rb - lib/open_food_network/enterprise_fee_calculator.rb - lib/open_food_network/order_cycle_form_applicator.rb diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index c8f4d56637..931b178475 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'stripe/profile_storer' require 'active_merchant/billing/gateways/stripe_payment_intents' require 'active_merchant/billing/gateways/stripe_decorator' @@ -79,7 +81,7 @@ module Spree end def ensure_enterprise_selected - return if preferred_enterprise_id.andand > 0 + return if preferred_enterprise_id.andand.positive? errors.add(:stripe_account_owner, I18n.t(:error_required)) end diff --git a/lib/active_merchant/billing/gateways/stripe_decorator.rb b/lib/active_merchant/billing/gateways/stripe_decorator.rb index 5d3aa90958..fcd23a6446 100644 --- a/lib/active_merchant/billing/gateways/stripe_decorator.rb +++ b/lib/active_merchant/billing/gateways/stripe_decorator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Here we bring commit 823faaeab0d6d3bd75ee037ec894ab7c9d95d3a9 from ActiveMerchant v1.98.0 # This is needed to make StripePaymentIntents work correctly # This can be removed once we upgrade to ActiveMerchant v1.98.0 @@ -7,7 +9,8 @@ ActiveMerchant::Billing::StripeGateway.class_eval do if url == 'customers' [response['id'], response.dig('sources', 'data').first&.dig('id')].join('|') - elsif method == :post && (url.match(/customers\/.*\/cards/) || url.match(/payment_methods\/.*\/attach/)) + elsif method == :post && + (url.match(%r{customers/.*/cards}) || url.match(%r{payment_methods/.*/attach})) [response['customer'], response['id']].join('|') else response['id'] diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb index f022c68323..3326d700c1 100644 --- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Here we bring commit 823faaeab0d6d3bd75ee037ec894ab7c9d95d3a9 from ActiveMerchant v1.98.0 # This class integrates with the new StripePaymentIntents API # This can be removed once we upgrade to ActiveMerchant v1.98.0 @@ -5,14 +7,18 @@ require 'active_support/core_ext/hash/slice' module ActiveMerchant #:nodoc: module Billing #:nodoc: - # This gateway uses the current Stripe {Payment Intents API}[https://stripe.com/docs/api/payment_intents]. + # This gateway uses the current Stripe + # {Payment Intents API}[https://stripe.com/docs/api/payment_intents]. # For the legacy API, see the Stripe gateway class StripePaymentIntentsGateway < StripeGateway ALLOWED_METHOD_STATES = %w[automatic manual].freeze ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze - CREATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email save_payment_method] - CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session] - UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email setup_future_usage] + CREATE_INTENT_ATTRIBUTES = + %i[description statement_descriptor receipt_email save_payment_method].freeze + CONFIRM_INTENT_ATTRIBUTES = + %i[receipt_email return_url save_payment_method setup_future_usage off_session].freeze + UPDATE_INTENT_ATTRIBUTES = + %i[description statement_descriptor receipt_email setup_future_usage].freeze DEFAULT_API_VERSION = '2019-05-16' def create_intent(money, payment_method, options = {}) @@ -80,11 +86,15 @@ module ActiveMerchant #:nodoc: end def authorize(money, payment_method, options = {}) - create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual')) + create_intent(money, + payment_method, + options.merge!(confirm: true, capture_method: 'manual')) end def purchase(money, payment_method, options = {}) - create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'automatic')) + create_intent(money, + payment_method, + options.merge!(confirm: true, capture_method: 'automatic')) end def capture(money, intent_id, options = {}) @@ -96,7 +106,9 @@ module ActiveMerchant #:nodoc: def void(intent_id, options = {}) post = {} - post[:cancellation_reason] = options[:cancellation_reason] if ALLOWED_CANCELLATION_REASONS.include?(options[:cancellation_reason]) + if ALLOWED_CANCELLATION_REASONS.include?(options[:cancellation_reason]) + post[:cancellation_reason] = options[:cancellation_reason] + end commit(:post, "payment_intents/#{intent_id}/cancel", post, options) end @@ -106,8 +118,10 @@ module ActiveMerchant #:nodoc: super(money, charge_id, options) end - # Note: Not all payment methods are currently supported by the {Payment Methods API}[https://stripe.com/docs/payments/payment-methods] - # Current implementation will create a PaymentMethod object if the method is a token or credit card + # Note: Not all payment methods are currently supported by the + # {Payment Methods API}[https://stripe.com/docs/payments/payment-methods] + # Current implementation will create + # a PaymentMethod object if the method is a token or credit card # All other types will default to legacy Stripe store def store(payment_method, options = {}) params = {} @@ -115,21 +129,24 @@ module ActiveMerchant #:nodoc: # If customer option is provided, create a payment method and attach to customer id # Otherwise, create a customer, then attach - #if payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard) - add_payment_method_token(params, payment_method, options) - if options[:customer] - customer_id = options[:customer] - else - post[:validate] = options[:validate] unless options[:validate].nil? - post[:description] = options[:description] if options[:description] - post[:email] = options[:email] if options[:email] - customer = commit(:post, 'customers', post, options) - customer_id = customer.params['id'] - end - commit(:post, "payment_methods/#{params[:payment_method]}/attach", { customer: customer_id }, options) - #else - # super(payment, options) - #end + # if payment_method.is_a?(StripePaymentToken) || + # payment_method.is_a?(ActiveMerchant::Billing::CreditCard) + add_payment_method_token(params, payment_method, options) + if options[:customer] + customer_id = options[:customer] + else + post[:validate] = options[:validate] unless options[:validate].nil? + post[:description] = options[:description] if options[:description] + post[:email] = options[:email] if options[:email] + customer = commit(:post, 'customers', post, options) + customer_id = customer.params['id'] + end + commit(:post, + "payment_methods/#{params[:payment_method]}/attach", + { customer: customer_id }, options) + # else + # super(payment, options) + # end end def unstore(identification, options = {}, deprecated_options = {}) @@ -156,7 +173,9 @@ module ActiveMerchant #:nodoc: def add_confirmation_method(post, options) confirmation_method = options[:confirmation_method].to_s - post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method) + if ALLOWED_METHOD_STATES.include?(confirmation_method) + post[:confirmation_method] = confirmation_method + end post end @@ -168,6 +187,7 @@ module ActiveMerchant #:nodoc: def add_return_url(post, options) return unless options[:confirm] + post[:confirm] = options[:confirm] post[:return_url] = options[:return_url] if options[:return_url] post @@ -204,15 +224,22 @@ module ActiveMerchant #:nodoc: end def setup_future_usage(post, options = {}) - post[:setup_future_usage] = options[:setup_future_usage] if %w( on_session off_session ).include?(options[:setup_future_usage]) - post[:off_session] = options[:off_session] if options[:off_session] && options[:confirm] == true + if %w(on_session off_session).include?(options[:setup_future_usage]) + post[:setup_future_usage] = options[:setup_future_usage] + end + if options[:off_session] && options[:confirm] == true + post[:off_session] = options[:off_session] + end post end def add_connected_account(post, options = {}) return unless transfer_data = options[:transfer_data] + post[:transfer_data] = {} - post[:transfer_data][:destination] = transfer_data[:destination] if transfer_data[:destination] + if transfer_data[:destination] + post[:transfer_data][:destination] = transfer_data[:destination] + end post[:transfer_data][:amount] = transfer_data[:amount] if transfer_data[:amount] post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of] post[:transfer_group] = options[:transfer_group] if options[:transfer_group] @@ -222,13 +249,18 @@ module ActiveMerchant #:nodoc: def add_shipping_address(post, options = {}) return unless shipping = options[:shipping] + post[:shipping] = {} post[:shipping][:address] = {} post[:shipping][:address][:line1] = shipping[:address][:line1] post[:shipping][:address][:city] = shipping[:address][:city] if shipping[:address][:city] - post[:shipping][:address][:country] = shipping[:address][:country] if shipping[:address][:country] + if shipping[:address][:country] + post[:shipping][:address][:country] = shipping[:address][:country] + end post[:shipping][:address][:line2] = shipping[:address][:line2] if shipping[:address][:line2] - post[:shipping][:address][:postal_code] = shipping[:address][:postal_code] if shipping[:address][:postal_code] + if shipping[:address][:postal_code] + post[:shipping][:address][:postal_code] = shipping[:address][:postal_code] + end post[:shipping][:address][:state] = shipping[:address][:state] if shipping[:address][:state] post[:shipping][:name] = shipping[:name] diff --git a/lib/stripe/profile_storer.rb b/lib/stripe/profile_storer.rb index 50a0ba4b31..b4cccdb470 100644 --- a/lib/stripe/profile_storer.rb +++ b/lib/stripe/profile_storer.rb @@ -64,11 +64,7 @@ module Stripe end def customer_profile_id(response) - if response.params['customer'] # Payment Intents API - response.params['customer'] - else - response.params['id'] - end + response.params['customer'] || response.params['id'] end def payment_profile_id(response) From c7b01c37af12d9d552b9feea8e622a61e0d6152d Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Tue, 14 Jan 2020 15:59:57 +0000 Subject: [PATCH 26/97] Fix a problem in credit cards controller spec and test case where stripe_account_id must be included in the stripe api call --- app/controllers/spree/credit_cards_controller.rb | 2 +- .../spree/credit_cards_controller_spec.rb | 16 ++++++++++++++++ spec/factories.rb | 9 ++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/controllers/spree/credit_cards_controller.rb b/app/controllers/spree/credit_cards_controller.rb index 3da82cd698..e0b23277da 100644 --- a/app/controllers/spree/credit_cards_controller.rb +++ b/app/controllers/spree/credit_cards_controller.rb @@ -58,7 +58,7 @@ module Spree # Currently can only destroy the whole customer object def destroy_at_stripe - if @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" + if @credit_card.payment_method && @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" options = { stripe_account: stripe_account_id } end diff --git a/spec/controllers/spree/credit_cards_controller_spec.rb b/spec/controllers/spree/credit_cards_controller_spec.rb index e8996433f3..b9ed2ed77b 100644 --- a/spec/controllers/spree/credit_cards_controller_spec.rb +++ b/spec/controllers/spree/credit_cards_controller_spec.rb @@ -172,6 +172,22 @@ describe Spree::CreditCardsController, type: :controller do expect(response).to redirect_to spree.account_path(anchor: 'cards') end end + + context "where the payment method is StripeSCA" do + let(:stripe_payment_method) { create(:stripe_sca_payment_method) } + let!(:card) { create(:credit_card, gateway_customer_profile_id: 'cus_AZNMJ', payment_method: stripe_payment_method) } + + before do + stub_request(:delete, "https://api.stripe.com/v1/customers/cus_AZNMJ"). + to_return(status: 200, body: JSON.generate(deleted: true, id: "cus_AZNMJ")) + end + + it "the request to destroy the Stripe customer includes the stripe_account_id" do + expect(Stripe::Customer).to receive(:retrieve).with(card.gateway_customer_profile_id, { stripe_account: "abc123" }) + + expect{ delete :destroy, params }.to change(Spree::CreditCard, :count).by(-1) + end + end end end end diff --git a/spec/factories.rb b/spec/factories.rb index 4f9c6478f5..99b704de80 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -151,8 +151,15 @@ FactoryBot.define do preferred_enterprise_id { distributors.first.id } end + factory :stripe_sca_payment_method, class: Spree::Gateway::StripeSCA do + name 'StripeSCA' + environment 'test' + distributors { [FactoryBot.create(:stripe_account).enterprise] } + preferred_enterprise_id { distributors.first.id } + end + factory :stripe_account do - enterprise { FactoryBot.create :distributor_enterprise } + enterprise { FactoryBot.create(:distributor_enterprise) } stripe_user_id "abc123" stripe_publishable_key "xyz456" end From 0e815439b35a6aeb46c7564c1796a9a648100b3a Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Tue, 14 Jan 2020 18:39:09 +0000 Subject: [PATCH 27/97] Duplicate stripe_connect_spec and adapt to new stripe_sca stripe_connect_spec will be deleted at some point when all users are migrated to the sca api --- spec/requests/checkout/stripe_sca_spec.rb | 318 ++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 spec/requests/checkout/stripe_sca_spec.rb diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb new file mode 100644 index 0000000000..f86568b5b4 --- /dev/null +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -0,0 +1,318 @@ +require 'spec_helper' + +describe "checking out an order with a Stripe SCA payment method", type: :request do + include ShopWorkflow + include AuthenticationWorkflow + include OpenFoodNetwork::ApiHelper + + let!(:order_cycle) { create(:simple_order_cycle) } + let!(:enterprise) { create(:distributor_enterprise) } + let!(:exchange) do + create( + :exchange, + order_cycle: order_cycle, + sender: order_cycle.coordinator, + receiver: enterprise, + incoming: false, + pickup_time: "Monday" + ) + end + let!(:shipping_method) do + create( + :shipping_method, + calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0), + distributors: [enterprise] + ) + end + let!(:payment_method) { create(:stripe_sca_payment_method, distributors: [enterprise]) } + let!(:stripe_account) { create(:stripe_account, enterprise: enterprise) } + let!(:line_item) { create(:line_item, price: 12.34) } + let!(:order) { line_item.order } + let(:address) { create(:address) } + let(:token) { "token123" } + let(:new_token) { "newtoken123" } + let(:card_id) { "card_XyZ456" } + let(:customer_id) { "cus_A123" } + let(:payments_attributes) do + { + payment_method_id: payment_method.id, + source_attributes: { + gateway_payment_profile_id: token, + cc_type: "visa", + last_digits: "4242", + month: 10, + year: 2025, + first_name: 'Jill', + last_name: 'Jeffreys' + } + } + end + let(:allowed_address_attributes) do + [ + "firstname", + "lastname", + "address1", + "address2", + "phone", + "city", + "zipcode", + "state_id", + "country_id" + ] + end + let(:params) do + { + format: :json, order: { + shipping_method_id: shipping_method.id, + payments_attributes: [payments_attributes], + bill_address_attributes: address.attributes.slice(*allowed_address_attributes), + ship_address_attributes: address.attributes.slice(*allowed_address_attributes) + } + } + end + + before do + order_cycle_distributed_variants = double(:order_cycle_distributed_variants) + allow(OrderCycleDistributedVariants).to receive(:new) { order_cycle_distributed_variants } + allow(order_cycle_distributed_variants).to receive(:distributes_order_variants?) { true } + + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + order.update_attributes(distributor_id: enterprise.id, order_cycle_id: order_cycle.id) + order.reload.update_totals + set_order order + end + + context "when a new card is submitted" do + let(:store_response_mock) do + { + status: 200, + body: JSON.generate( + id: customer_id, + default_card: card_id, + sources: { data: [{ id: "1" }] } + ) + } + end + let(:token_response_mock) do + { status: 200, body: JSON.generate(id: new_token) } + end + let(:charge_response_mock) do + { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } + end + + context "and the user doesn't request that the card is saved for later" do + before do + # Charges the card + stub_request(:post, "https://api.stripe.com/v1/payment_intents") + .with(basic_auth: ["sk_test_12345", ""], body: /#{token}.*#{order.number}/) + .to_return(charge_response_mock) + end + + context "and the charge request is successful" do + it "should process the payment without storing card details" do + put update_checkout_path, params + + expect(json_response["path"]).to eq spree.order_path(order) + expect(order.payments.completed.count).to be 1 + + card = order.payments.completed.first.source + + expect(card.gateway_customer_profile_id).to eq nil + expect(card.gateway_payment_profile_id).to eq token + expect(card.cc_type).to eq "visa" + expect(card.last_digits).to eq "4242" + expect(card.first_name).to eq "Jill" + expect(card.last_name).to eq "Jeffreys" + end + end + + context "when the charge request returns an error message" do + let(:charge_response_mock) do + { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } + end + + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]).to eq "charge-failure" + expect(order.payments.completed.count).to be 0 + end + end + end + + context "and the customer requests that the card is saved for later" do + before do + source_attributes = params[:order][:payments_attributes][0][:source_attributes] + source_attributes[:save_requested_by_customer] = '1' + + # Saves the card against the user + stub_request(:post, "https://api.stripe.com/v1/customers") + .with(basic_auth: ["sk_test_12345", ""], body: { card: token, email: order.email }) + .to_return(store_response_mock) + + # Requests a token from the newly saved card + stub_request(:post, "https://api.stripe.com/v1/tokens") + .with(body: { card: card_id, customer: customer_id }) + .to_return(token_response_mock) + + # Charges the card + stub_request(:post, "https://api.stripe.com/v1/payment_intents") + .with( + basic_auth: ["sk_test_12345", ""], + body: /#{token}.*#{order.number}/ + ).to_return(charge_response_mock) + end + + context "and the store, token and charge requests are successful" do + it "should process the payment, and stores the card/customer details" do + put update_checkout_path, params + + expect(json_response["path"]).to eq spree.order_path(order) + expect(order.payments.completed.count).to be 1 + + card = order.payments.completed.first.source + + expect(card.gateway_customer_profile_id).to eq customer_id + expect(card.gateway_payment_profile_id).to eq card_id + expect(card.cc_type).to eq "visa" + expect(card.last_digits).to eq "4242" + expect(card.first_name).to eq "Jill" + expect(card.last_name).to eq "Jeffreys" + end + end + + context "when the store request returns an error message" do + let(:store_response_mock) do + { status: 402, body: JSON.generate(error: { message: "store-failure" }) } + end + + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]) + .to eq(I18n.t(:spree_gateway_error_flash_for_checkout, error: 'store-failure')) + expect(order.payments.completed.count).to be 0 + end + end + + context "when the charge request returns an error message" do + let(:charge_response_mock) do + { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } + end + + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]).to eq "charge-failure" + expect(order.payments.completed.count).to be 0 + end + end + + context "when the token request returns an error message" do + let(:token_response_mock) do + { status: 402, body: JSON.generate(error: { message: "token-failure" }) } + end + + # Note, no requests have been stubbed + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]).to eq "token-failure" + expect(order.payments.completed.count).to be 0 + end + end + end + end + + context "when an existing card is submitted" do + let(:credit_card) do + create( + :credit_card, + user_id: order.user_id, + gateway_payment_profile_id: card_id, + gateway_customer_profile_id: customer_id, + last_digits: "4321", + cc_type: "master", + first_name: "Sammy", + last_name: "Signpost", + month: 11, year: 2026 + ) + end + + let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } } + let(:charge_response_mock) do + { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } + end + + before do + params[:order][:existing_card_id] = credit_card.id + quick_login_as(order.user) + + # Requests a token + #stub_request(:post, "https://api.stripe.com/v1/tokens") + # .with(body: { "card" => card_id, "customer" => customer_id }) + # .to_return(token_response_mock) + + # Charges the card + stub_request(:post, "https://api.stripe.com/v1/payment_intents") + .with(basic_auth: ["sk_test_12345", ""], body: %r{.*#{customer_id}.*#{order.number}.*#{card_id}.*}) + .to_return(charge_response_mock) + end + + context "and the charge and token requests are accepted" do + it "should process the payment, and keep the profile ids and other card details" do + put update_checkout_path, params + + expect(json_response["path"]).to eq spree.order_path(order) + expect(order.payments.completed.count).to be 1 + + card = order.payments.completed.first.source + + expect(card.gateway_customer_profile_id).to eq customer_id + expect(card.gateway_payment_profile_id).to eq card_id + expect(card.cc_type).to eq "master" + expect(card.last_digits).to eq "4321" + expect(card.first_name).to eq "Sammy" + expect(card.last_name).to eq "Signpost" + end + end + + context "when the charge request returns an error message" do + let(:charge_response_mock) do + { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } + end + + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]).to eq "charge-failure" + expect(order.payments.completed.count).to be 0 + end + end + + context "when the token request returns an error message" do + let(:token_response_mock) do + { status: 402, body: JSON.generate(error: { message: "token-error" }) } + end + + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]).to eq "token-error" + expect(order.payments.completed.count).to be 0 + end + end + end +end From 6bb04f6cc6992ad40169ed22d4b19ca9909f4928 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 15 Jan 2020 12:43:27 +0000 Subject: [PATCH 28/97] Adapt stripe_sca_spec to actual stripe SCA API --- .../gateways/stripe_payment_intents.rb | 3 + spec/requests/checkout/stripe_sca_spec.rb | 145 +++++++----------- 2 files changed, 62 insertions(+), 86 deletions(-) diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb index 3326d700c1..9c5c602c7a 100644 --- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb @@ -140,6 +140,9 @@ module ActiveMerchant #:nodoc: post[:email] = options[:email] if options[:email] customer = commit(:post, 'customers', post, options) customer_id = customer.params['id'] + + # return the stripe response if expected customer id is not present + return customer if customer_id.nil? end commit(:post, "payment_methods/#{params[:payment_method]}/attach", diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index f86568b5b4..33bf6fba27 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -29,15 +29,14 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques let!(:line_item) { create(:line_item, price: 12.34) } let!(:order) { line_item.order } let(:address) { create(:address) } - let(:token) { "token123" } - let(:new_token) { "newtoken123" } - let(:card_id) { "card_XyZ456" } + let(:stripe_payment_method) { "pm_123" } + let(:new_stripe_payment_method) { "new_pm_123" } let(:customer_id) { "cus_A123" } let(:payments_attributes) do { payment_method_id: payment_method.id, source_attributes: { - gateway_payment_profile_id: token, + gateway_payment_profile_id: stripe_payment_method, cc_type: "visa", last_digits: "4242", month: 10, @@ -70,6 +69,9 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques } } end + let(:payment_intent_response_mock) do + { status: 200, body: JSON.generate(object: "payment_intent", amount: 2000, charges: { data: [{ id: "ch_1234", amount: 2000 }]}) } + end before do order_cycle_distributed_variants = double(:order_cycle_distributed_variants) @@ -83,32 +85,15 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end context "when a new card is submitted" do - let(:store_response_mock) do - { - status: 200, - body: JSON.generate( - id: customer_id, - default_card: card_id, - sources: { data: [{ id: "1" }] } - ) - } - end - let(:token_response_mock) do - { status: 200, body: JSON.generate(id: new_token) } - end - let(:charge_response_mock) do - { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } - end - context "and the user doesn't request that the card is saved for later" do before do # Charges the card stub_request(:post, "https://api.stripe.com/v1/payment_intents") - .with(basic_auth: ["sk_test_12345", ""], body: /#{token}.*#{order.number}/) - .to_return(charge_response_mock) + .with(basic_auth: ["sk_test_12345", ""], body: /#{stripe_payment_method}.*#{order.number}/) + .to_return(payment_intent_response_mock) end - context "and the charge request is successful" do + context "and the paymeent intent request is successful" do it "should process the payment without storing card details" do put update_checkout_path, params @@ -118,7 +103,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques card = order.payments.completed.first.source expect(card.gateway_customer_profile_id).to eq nil - expect(card.gateway_payment_profile_id).to eq token + expect(card.gateway_payment_profile_id).to eq stripe_payment_method expect(card.cc_type).to eq "visa" expect(card.last_digits).to eq "4242" expect(card.first_name).to eq "Jill" @@ -126,9 +111,9 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end end - context "when the charge request returns an error message" do - let(:charge_response_mock) do - { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } + context "when the payment intent request returns an error message" do + let(:payment_intent_response_mock) do + { status: 402, body: JSON.generate(error: { message: "payment-intent-failure" }) } end it "should not process the payment" do @@ -136,36 +121,50 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques expect(response.status).to be 400 - expect(json_response["flash"]["error"]).to eq "charge-failure" + expect(json_response["flash"]["error"]).to eq "payment-intent-failure" expect(order.payments.completed.count).to be 0 end end end context "and the customer requests that the card is saved for later" do + let(:payment_method_response_mock) do + { + status: 200, + body: JSON.generate(id: new_stripe_payment_method, customer: customer_id) + } + end + + let(:customer_response_mock) do + { + status: 200, + body: JSON.generate(id: customer_id, sources: { data: [{ id: "1" }] }) + } + end + before do source_attributes = params[:order][:payments_attributes][0][:source_attributes] source_attributes[:save_requested_by_customer] = '1' # Saves the card against the user stub_request(:post, "https://api.stripe.com/v1/customers") - .with(basic_auth: ["sk_test_12345", ""], body: { card: token, email: order.email }) - .to_return(store_response_mock) + .with(basic_auth: ["sk_test_12345", ""], body: { email: order.email }) + .to_return(customer_response_mock) - # Requests a token from the newly saved card - stub_request(:post, "https://api.stripe.com/v1/tokens") - .with(body: { card: card_id, customer: customer_id }) - .to_return(token_response_mock) + # Requests a payment method from the newly saved card + stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{stripe_payment_method}/attach") + .with(body: { customer: customer_id }) + .to_return(payment_method_response_mock) # Charges the card stub_request(:post, "https://api.stripe.com/v1/payment_intents") .with( basic_auth: ["sk_test_12345", ""], - body: /#{token}.*#{order.number}/ - ).to_return(charge_response_mock) + body: /.*#{order.number}/ + ).to_return(payment_intent_response_mock) end - context "and the store, token and charge requests are successful" do + context "and the customer, payment_method and payment_intent requests are successful" do it "should process the payment, and stores the card/customer details" do put update_checkout_path, params @@ -175,7 +174,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques card = order.payments.completed.first.source expect(card.gateway_customer_profile_id).to eq customer_id - expect(card.gateway_payment_profile_id).to eq card_id + expect(card.gateway_payment_profile_id).to eq new_stripe_payment_method expect(card.cc_type).to eq "visa" expect(card.last_digits).to eq "4242" expect(card.first_name).to eq "Jill" @@ -183,9 +182,9 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end end - context "when the store request returns an error message" do - let(:store_response_mock) do - { status: 402, body: JSON.generate(error: { message: "store-failure" }) } + context "when the customer request returns an error message" do + let(:customer_response_mock) do + { status: 402, body: JSON.generate(error: { message: "customer-store-failure" }) } end it "should not process the payment" do @@ -194,14 +193,14 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques expect(response.status).to be 400 expect(json_response["flash"]["error"]) - .to eq(I18n.t(:spree_gateway_error_flash_for_checkout, error: 'store-failure')) + .to eq(I18n.t(:spree_gateway_error_flash_for_checkout, error: 'customer-store-failure')) expect(order.payments.completed.count).to be 0 end end - context "when the charge request returns an error message" do - let(:charge_response_mock) do - { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } + context "when the payment intent request returns an error message" do + let(:payment_intent_response_mock) do + { status: 402, body: JSON.generate(error: { message: "payment-intent-failure" }) } end it "should not process the payment" do @@ -209,23 +208,22 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques expect(response.status).to be 400 - expect(json_response["flash"]["error"]).to eq "charge-failure" + expect(json_response["flash"]["error"]).to eq "payment-intent-failure" expect(order.payments.completed.count).to be 0 end end - context "when the token request returns an error message" do - let(:token_response_mock) do - { status: 402, body: JSON.generate(error: { message: "token-failure" }) } + context "when the payment_metho request returns an error message" do + let(:payment_method_response_mock) do + { status: 402, body: JSON.generate(error: { message: "payment-method-failure" }) } end - # Note, no requests have been stubbed it "should not process the payment" do put update_checkout_path, params expect(response.status).to be 400 - expect(json_response["flash"]["error"]).to eq "token-failure" + expect(json_response["flash"]["error"]).to include "payment-method-failure" expect(order.payments.completed.count).to be 0 end end @@ -237,7 +235,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques create( :credit_card, user_id: order.user_id, - gateway_payment_profile_id: card_id, + gateway_payment_profile_id: stripe_payment_method, gateway_customer_profile_id: customer_id, last_digits: "4321", cc_type: "master", @@ -247,27 +245,17 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques ) end - let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } } - let(:charge_response_mock) do - { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } - end - before do params[:order][:existing_card_id] = credit_card.id quick_login_as(order.user) - # Requests a token - #stub_request(:post, "https://api.stripe.com/v1/tokens") - # .with(body: { "card" => card_id, "customer" => customer_id }) - # .to_return(token_response_mock) - # Charges the card stub_request(:post, "https://api.stripe.com/v1/payment_intents") - .with(basic_auth: ["sk_test_12345", ""], body: %r{.*#{customer_id}.*#{order.number}.*#{card_id}.*}) - .to_return(charge_response_mock) + .with(basic_auth: ["sk_test_12345", ""], body: %r{#{customer_id}.*#{stripe_payment_method}}) + .to_return(payment_intent_response_mock) end - context "and the charge and token requests are accepted" do + context "and the payment intent and payment method requests are accepted" do it "should process the payment, and keep the profile ids and other card details" do put update_checkout_path, params @@ -277,7 +265,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques card = order.payments.completed.first.source expect(card.gateway_customer_profile_id).to eq customer_id - expect(card.gateway_payment_profile_id).to eq card_id + expect(card.gateway_payment_profile_id).to eq stripe_payment_method expect(card.cc_type).to eq "master" expect(card.last_digits).to eq "4321" expect(card.first_name).to eq "Sammy" @@ -285,9 +273,9 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end end - context "when the charge request returns an error message" do - let(:charge_response_mock) do - { status: 402, body: JSON.generate(error: { message: "charge-failure" }) } + context "when the payment intent request returns an error message" do + let(:payment_intent_response_mock) do + { status: 402, body: JSON.generate(error: { message: "payment-intent-failure" }) } end it "should not process the payment" do @@ -295,22 +283,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques expect(response.status).to be 400 - expect(json_response["flash"]["error"]).to eq "charge-failure" - expect(order.payments.completed.count).to be 0 - end - end - - context "when the token request returns an error message" do - let(:token_response_mock) do - { status: 402, body: JSON.generate(error: { message: "token-error" }) } - end - - it "should not process the payment" do - put update_checkout_path, params - - expect(response.status).to be 400 - - expect(json_response["flash"]["error"]).to eq "token-error" + expect(json_response["flash"]["error"]).to eq "payment-intent-failure" expect(order.payments.completed.count).to be 0 end end From aff934c814f38f38e6badc91fb504ea678d8a52b Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 15 Jan 2020 12:51:38 +0000 Subject: [PATCH 29/97] Remove unnecessary test setup code --- spec/requests/checkout/stripe_connect_spec.rb | 10 ---------- spec/requests/checkout/stripe_sca_spec.rb | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/spec/requests/checkout/stripe_connect_spec.rb b/spec/requests/checkout/stripe_connect_spec.rb index a64bb44ab8..697664b806 100644 --- a/spec/requests/checkout/stripe_connect_spec.rb +++ b/spec/requests/checkout/stripe_connect_spec.rb @@ -7,16 +7,6 @@ describe "checking out an order with a Stripe Connect payment method", type: :re let!(:order_cycle) { create(:simple_order_cycle) } let!(:enterprise) { create(:distributor_enterprise) } - let!(:exchange) do - create( - :exchange, - order_cycle: order_cycle, - sender: order_cycle.coordinator, - receiver: enterprise, - incoming: false, - pickup_time: "Monday" - ) - end let!(:shipping_method) do create( :shipping_method, diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index 33bf6fba27..c5233910cc 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -7,16 +7,6 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques let!(:order_cycle) { create(:simple_order_cycle) } let!(:enterprise) { create(:distributor_enterprise) } - let!(:exchange) do - create( - :exchange, - order_cycle: order_cycle, - sender: order_cycle.coordinator, - receiver: enterprise, - incoming: false, - pickup_time: "Monday" - ) - end let!(:shipping_method) do create( :shipping_method, From 668fd1c7c0452966b0f2e590286fbe26694aaec4 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 15 Jan 2020 13:15:56 +0000 Subject: [PATCH 30/97] Add spec for profile storer to cover happy path for both response attribute cases: existin stripe integration and new stripe sca --- spec/lib/stripe/profile_storer_spec.rb | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 spec/lib/stripe/profile_storer_spec.rb diff --git a/spec/lib/stripe/profile_storer_spec.rb b/spec/lib/stripe/profile_storer_spec.rb new file mode 100644 index 0000000000..beec53feec --- /dev/null +++ b/spec/lib/stripe/profile_storer_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Stripe + describe ProfileStorer do + describe "create_customer_from_token" do + let(:payment) { create(:payment) } + let(:stripe_payment_method) { create(:stripe_payment_method) } + let(:stripe_account_id) { "12312" } + let(:profile_storer) { Stripe::ProfileStorer.new(payment, stripe_payment_method.provider) } + + let(:customer_id) { "cus_A123" } + let(:card_id) { "card_2342" } + let(:customer_response_mock) { { status: 200, body: customer_response_body } } + + before do + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + + stub_request(:post, "https://api.stripe.com/v1/customers") + .with(basic_auth: ["sk_test_12345", ""], body: { email: payment.order.email }) + .to_return(customer_response_mock) + end + + context "when called from Stripe Connect" do + let(:customer_response_body) { + JSON.generate(id: customer_id, default_card: card_id, sources: { data: [{ id: "1" }] }) + } + + it "fetches the customer id and the card id from the correct response fields" do + profile_storer.create_customer_from_token + + expect(payment.source.gateway_customer_profile_id).to eq customer_id + expect(payment.source.gateway_payment_profile_id).to eq card_id + end + end + + context "when called from Stripe SCA" do + let(:customer_response_body) { + JSON.generate(customer: customer_id, id: card_id, sources: { data: [{ id: "1" }] }) + } + + it "fetches the customer id and the card id from the correct response fields" do + profile_storer.create_customer_from_token + + expect(payment.source.gateway_customer_profile_id).to eq customer_id + expect(payment.source.gateway_payment_profile_id).to eq card_id + end + end + end + end +end From b8457ebeced22747eaadfd7553eb0d7b67371d98 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 16 Jan 2020 13:18:59 +0000 Subject: [PATCH 31/97] Make profile storer a bit easier to read --- lib/stripe/profile_storer.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/stripe/profile_storer.rb b/lib/stripe/profile_storer.rb index b4cccdb470..1806a48bd6 100644 --- a/lib/stripe/profile_storer.rb +++ b/lib/stripe/profile_storer.rb @@ -25,13 +25,17 @@ module Stripe private def options - options = { + { email: @payment.order.email, login: Stripe.api_key, address: address_for(@payment) - } - options = options.merge(stripe_account: @stripe_account_id) if @stripe_account_id.present? - options + }.merge(stripe_account_option) + end + + def stripe_account_option + return {} if @stripe_account_id.blank? + + { stripe_account: @stripe_account_id } end def address_for(payment) From 66440f9e4c837e75e616c50f794e09589611a951 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 16 Jan 2020 13:19:17 +0000 Subject: [PATCH 32/97] Add missing translations for new payment method stripe sca --- config/locales/en.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 2393ee26d7..25af1defa2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3383,6 +3383,12 @@ See the %{link} to find out more about %{sitename}'s features and to start using used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: ! '%Y-%m-%d' js_format: 'yy-mm-dd' From 4e84310d6344479a2841de70c21a2ce183851824 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 16 Jan 2020 18:38:29 +0000 Subject: [PATCH 33/97] Add StripeSCA where StripeConnect is treated as an exception in the setting up of process of a payment method and subscriptions Here we are copy pasting and adding stripe SCA because we are planning to delete the StripeConnect that will be replaced by the stripe sca implementation --- .../subscriptions/controllers/details_controller.js.coffee | 2 +- app/models/spree/payment_method_decorator.rb | 2 ++ app/models/subscription.rb | 2 +- app/serializers/api/admin/payment_method_serializer.rb | 2 +- app/services/subscription_validator.rb | 2 +- lib/open_food_network/subscription_payment_updater.rb | 3 ++- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee b/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee index 55d2a46b42..cb331a62f3 100644 --- a/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee +++ b/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee @@ -16,7 +16,7 @@ angular.module("admin.subscriptions").controller "DetailsController", ($scope, $ return if !newValue? paymentMethod = ($scope.paymentMethods.filter (pm) -> pm.id == newValue)[0] return unless paymentMethod? - $scope.cardRequired = (paymentMethod.type == "Spree::Gateway::StripeConnect") + $scope.cardRequired = (paymentMethod.type == "Spree::Gateway::StripeConnect" || paymentMethod.type == "Spree::Gateway::StripeSCA") $scope.loadCustomer() if $scope.cardRequired && !$scope.customer $scope.loadCustomer = -> diff --git a/app/models/spree/payment_method_decorator.rb b/app/models/spree/payment_method_decorator.rb index f32944fdcb..a6d4b19c48 100644 --- a/app/models/spree/payment_method_decorator.rb +++ b/app/models/spree/payment_method_decorator.rb @@ -68,6 +68,8 @@ Spree::PaymentMethod.class_eval do "Pin Payments" when "Spree::Gateway::StripeConnect" "Stripe" + when "Spree::Gateway::StripeSCA" + "Stripe SCA" when "Spree::Gateway::PayPalExpress" "PayPal Express" else diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 443a6ef7a4..03b2c0c5a7 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,5 +1,5 @@ class Subscription < ActiveRecord::Base - ALLOWED_PAYMENT_METHOD_TYPES = ["Spree::PaymentMethod::Check", "Spree::Gateway::StripeConnect"].freeze + ALLOWED_PAYMENT_METHOD_TYPES = ["Spree::PaymentMethod::Check", "Spree::Gateway::StripeConnect", "Spree::Gateway::StripeSCA"].freeze belongs_to :shop, class_name: 'Enterprise' belongs_to :customer diff --git a/app/serializers/api/admin/payment_method_serializer.rb b/app/serializers/api/admin/payment_method_serializer.rb index 3d66ddbc03..18fccbe9e7 100644 --- a/app/serializers/api/admin/payment_method_serializer.rb +++ b/app/serializers/api/admin/payment_method_serializer.rb @@ -4,7 +4,7 @@ module Api delegate :serializable_hash, to: :method_serializer def method_serializer - if object.type == 'Spree::Gateway::StripeConnect' + if object.type == 'Spree::Gateway::StripeConnect' || object.type == 'Spree::Gateway::StripeSCA' Api::Admin::PaymentMethod::StripeSerializer.new(object) else Api::Admin::PaymentMethod::BaseSerializer.new(object) diff --git a/app/services/subscription_validator.rb b/app/services/subscription_validator.rb index 8d9a678f9a..9ba52145e4 100644 --- a/app/services/subscription_validator.rb +++ b/app/services/subscription_validator.rb @@ -82,7 +82,7 @@ class SubscriptionValidator def credit_card_ok? return unless customer && payment_method - return unless payment_method.type == "Spree::Gateway::StripeConnect" + return unless payment_method.type == "Spree::Gateway::StripeConnect" || payment_method.type == "Spree::Gateway::StripeSCA" return errors.add(:payment_method, :charges_not_allowed) unless customer.allow_charges return if customer.user.andand.default_card.present? diff --git a/lib/open_food_network/subscription_payment_updater.rb b/lib/open_food_network/subscription_payment_updater.rb index 1c4d275db1..d1cbcb4c51 100644 --- a/lib/open_food_network/subscription_payment_updater.rb +++ b/lib/open_food_network/subscription_payment_updater.rb @@ -30,7 +30,8 @@ module OpenFoodNetwork end def card_required? - payment.payment_method.is_a? Spree::Gateway::StripeConnect + payment.payment_method.is_a?(Spree::Gateway::StripeConnect) || + payment.payment_method.is_a?(Spree::Gateway::StripeSCA) end def card_set? From 38fd028a9f4e86ff7ed8d23e0f5f530e84d516f3 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Thu, 16 Jan 2020 18:47:13 +0000 Subject: [PATCH 34/97] Fix some rubocop issues from previous commit --- app/controllers/spree/credit_cards_controller.rb | 3 ++- app/models/subscription.rb | 4 +++- app/serializers/api/admin/payment_method_serializer.rb | 3 ++- app/services/subscription_validator.rb | 7 ++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/controllers/spree/credit_cards_controller.rb b/app/controllers/spree/credit_cards_controller.rb index e0b23277da..fbad74e943 100644 --- a/app/controllers/spree/credit_cards_controller.rb +++ b/app/controllers/spree/credit_cards_controller.rb @@ -58,7 +58,8 @@ module Spree # Currently can only destroy the whole customer object def destroy_at_stripe - if @credit_card.payment_method && @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" + if @credit_card.payment_method && + @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" options = { stripe_account: stripe_account_id } end diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 03b2c0c5a7..ca1fd676e8 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,5 +1,7 @@ class Subscription < ActiveRecord::Base - ALLOWED_PAYMENT_METHOD_TYPES = ["Spree::PaymentMethod::Check", "Spree::Gateway::StripeConnect", "Spree::Gateway::StripeSCA"].freeze + ALLOWED_PAYMENT_METHOD_TYPES = ["Spree::PaymentMethod::Check", + "Spree::Gateway::StripeConnect", + "Spree::Gateway::StripeSCA"].freeze belongs_to :shop, class_name: 'Enterprise' belongs_to :customer diff --git a/app/serializers/api/admin/payment_method_serializer.rb b/app/serializers/api/admin/payment_method_serializer.rb index 18fccbe9e7..9862b81dcd 100644 --- a/app/serializers/api/admin/payment_method_serializer.rb +++ b/app/serializers/api/admin/payment_method_serializer.rb @@ -4,7 +4,8 @@ module Api delegate :serializable_hash, to: :method_serializer def method_serializer - if object.type == 'Spree::Gateway::StripeConnect' || object.type == 'Spree::Gateway::StripeSCA' + if object.type == 'Spree::Gateway::StripeConnect' || + object.type == 'Spree::Gateway::StripeSCA' Api::Admin::PaymentMethod::StripeSerializer.new(object) else Api::Admin::PaymentMethod::BaseSerializer.new(object) diff --git a/app/services/subscription_validator.rb b/app/services/subscription_validator.rb index 9ba52145e4..4cce8a3af3 100644 --- a/app/services/subscription_validator.rb +++ b/app/services/subscription_validator.rb @@ -82,13 +82,18 @@ class SubscriptionValidator def credit_card_ok? return unless customer && payment_method - return unless payment_method.type == "Spree::Gateway::StripeConnect" || payment_method.type == "Spree::Gateway::StripeSCA" + return unless stripe_payment_method?(payment_method) return errors.add(:payment_method, :charges_not_allowed) unless customer.allow_charges return if customer.user.andand.default_card.present? errors.add(:payment_method, :no_default_card) end + def stripe_payment_method?(payment_method) + payment_method.type == "Spree::Gateway::StripeConnect" || + payment_method.type == "Spree::Gateway::StripeSCA" + end + def subscription_line_items_present? return if subscription_line_items.reject(&:marked_for_destruction?).any? From 6fb74c88cdd42d5d49413ff070a2ec9021fc378b Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Sun, 19 Jan 2020 21:33:56 +0000 Subject: [PATCH 35/97] Fix a typo --- spec/requests/checkout/stripe_sca_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index c5233910cc..dcdb0f4291 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -203,7 +203,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end end - context "when the payment_metho request returns an error message" do + context "when the payment_method request returns an error message" do let(:payment_method_response_mock) do { status: 402, body: JSON.generate(error: { message: "payment-method-failure" }) } end From b3ac5d8f4187454deaf2347bed35a3685cfb32cf Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Sun, 19 Jan 2020 21:46:52 +0000 Subject: [PATCH 36/97] Improve code readability a little --- .../admin/payments/services/payment.js.coffee | 11 +---------- .../spree/admin/payment_methods_controller.rb | 7 +++---- lib/open_food_network/subscription_payment_updater.rb | 4 ++-- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/admin/payments/services/payment.js.coffee b/app/assets/javascripts/admin/payments/services/payment.js.coffee index f5cab3061c..a87497226d 100644 --- a/app/assets/javascripts/admin/payments/services/payment.js.coffee +++ b/app/assets/javascripts/admin/payments/services/payment.js.coffee @@ -21,16 +21,7 @@ angular.module('admin.payments').factory 'Payment', (AdminStripeElements, curren year: @form_data.card_year verification_value: @form_data.card_verification_value } - when 'stripe' - angular.extend munged_payment.payment, { - source_attributes: - gateway_payment_profile_id: @form_data.token - cc_type: @form_data.cc_type - last_digits: @form_data.card.last4 - month: @form_data.card.exp_month - year: @form_data.card.exp_year - } - when 'stripe_sca' + when 'stripe', 'stripe_sca' angular.extend munged_payment.payment, { source_attributes: gateway_payment_profile_id: @form_data.token diff --git a/app/controllers/spree/admin/payment_methods_controller.rb b/app/controllers/spree/admin/payment_methods_controller.rb index ad0bff9a3b..c13712b9a1 100644 --- a/app/controllers/spree/admin/payment_methods_controller.rb +++ b/app/controllers/spree/admin/payment_methods_controller.rb @@ -149,13 +149,12 @@ module Spree end def stripe_payment_method? - @payment_method.try(:type) == "Spree::Gateway::StripeConnect" || - @payment_method.try(:type) == "Spree::Gateway::StripeSCA" + ["Spree::Gateway::StripeConnect", + "Spree::Gateway::StripeSCA"].include? @payment_method.try(:type) end def stripe_provider?(provider) - provider.name.ends_with?("StripeConnect") || - provider.name.ends_with?("StripeSCA") + provider.name.ends_with?("StripeConnect", "StripeSCA") end end end diff --git a/lib/open_food_network/subscription_payment_updater.rb b/lib/open_food_network/subscription_payment_updater.rb index d1cbcb4c51..4a9c8fa144 100644 --- a/lib/open_food_network/subscription_payment_updater.rb +++ b/lib/open_food_network/subscription_payment_updater.rb @@ -30,8 +30,8 @@ module OpenFoodNetwork end def card_required? - payment.payment_method.is_a?(Spree::Gateway::StripeConnect) || - payment.payment_method.is_a?(Spree::Gateway::StripeSCA) + [Spree::Gateway::StripeConnect, + Spree::Gateway::StripeSCA].include? payment.payment_method.class end def card_set? From 4480c2f0f0277333e624dd99d20e241a3a6fa82f Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Sun, 26 Jan 2020 18:13:16 +0000 Subject: [PATCH 37/97] Add logic to stripe_sca gateway to handle cards stored in the platform account with the stripe Charges API: card_* --- app/models/spree/gateway/stripe_sca.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index 931b178475..272faff407 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -70,10 +70,24 @@ module Spree options[:currency] = gateway_options[:currency] options[:stripe_account] = stripe_account_id - options[:customer] = creditcard.gateway_customer_profile_id - creditcard = creditcard.gateway_payment_profile_id + convert_to_payment_method!(creditcard) if creditcard.gateway_payment_profile_id.starts_with?('card_') - [money, creditcard, options] + options[:customer] = creditcard.gateway_customer_profile_id + payment_method = creditcard.gateway_payment_profile_id + + [money, payment_method, options] + end + + def convert_to_payment_method!(creditcard) + card_id = creditcard.gateway_payment_profile_id + customer_id = creditcard.gateway_customer_profile_id + new_payment_method = Stripe::PaymentMethod.create({ customer: customer_id, payment_method: card_id }, { stripe_account: stripe_account_id }) + + new_customer = Stripe::Customer.create({ email: creditcard.user.email }, { stripe_account: stripe_account_id }) + Stripe::PaymentMethod.attach(new_payment_method.id, { customer: new_customer.id }, { stripe_account: stripe_account_id }) + + creditcard.update_attributes gateway_customer_profile_id: new_customer.id, gateway_payment_profile_id: new_payment_method.id + creditcard end def failed_activemerchant_billing_response(error_message) From 14c03ead319bec219c6164ad5498e3c273c3086e Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Sun, 26 Jan 2020 18:45:28 +0000 Subject: [PATCH 38/97] Extract CardCloner to separate class --- app/models/spree/gateway/stripe_sca.rb | 15 +-------- lib/stripe/card_cloner.rb | 44 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 lib/stripe/card_cloner.rb diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index 272faff407..0cafe4b6d6 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -70,26 +70,13 @@ module Spree options[:currency] = gateway_options[:currency] options[:stripe_account] = stripe_account_id - convert_to_payment_method!(creditcard) if creditcard.gateway_payment_profile_id.starts_with?('card_') - + Stripe::CardCloner.new.clone!(creditcard, stripe_account_id) options[:customer] = creditcard.gateway_customer_profile_id payment_method = creditcard.gateway_payment_profile_id [money, payment_method, options] end - def convert_to_payment_method!(creditcard) - card_id = creditcard.gateway_payment_profile_id - customer_id = creditcard.gateway_customer_profile_id - new_payment_method = Stripe::PaymentMethod.create({ customer: customer_id, payment_method: card_id }, { stripe_account: stripe_account_id }) - - new_customer = Stripe::Customer.create({ email: creditcard.user.email }, { stripe_account: stripe_account_id }) - Stripe::PaymentMethod.attach(new_payment_method.id, { customer: new_customer.id }, { stripe_account: stripe_account_id }) - - creditcard.update_attributes gateway_customer_profile_id: new_customer.id, gateway_payment_profile_id: new_payment_method.id - creditcard - end - def failed_activemerchant_billing_response(error_message) ActiveMerchant::Billing::Response.new(false, error_message) end diff --git a/lib/stripe/card_cloner.rb b/lib/stripe/card_cloner.rb new file mode 100644 index 0000000000..c0e14f2644 --- /dev/null +++ b/lib/stripe/card_cloner.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Here we clone +# - a card (card_*) stored in a customer in a platform account +# into +# - a payment method (pm_*) in a new customer in a connected account +# +# This process is used in the migration between using the Stripe Charges API (stripe_connect) +# and the Stripe Payment Intents API (stripe_sca) +# +# This process can be deleted once all hubs are running on the new stripe_sca method and all cards in the system have been migrated to the new payment_methods +# Basically, when all DBs have no card_* values in credit_card.gateway_payment_profile_id +module Stripe + class CardCloner + def clone!(credit_card, stripe_account_id) + return unless credit_card.gateway_payment_profile_id.starts_with?('card_') + + new_payment_method = clone_payment_method(credit_card, stripe_account_id) + new_customer = Stripe::Customer.create({ email: credit_card.user.email }, + stripe_account: stripe_account_id) + attach_payment_method_to_customer(new_payment_method.id, new_customer.id, stripe_account_id) + + credit_card.update_attributes gateway_customer_profile_id: new_customer.id, + gateway_payment_profile_id: new_payment_method.id + credit_card + end + + private + + def clone_payment_method(credit_card, stripe_account_id) + card_id = credit_card.gateway_payment_profile_id + customer_id = credit_card.gateway_customer_profile_id + + Stripe::PaymentMethod.create({ customer: customer_id, payment_method: card_id }, + stripe_account: stripe_account_id) + end + + def attach_payment_method_to_customer(payment_method_id, customer_id, stripe_account_id) + Stripe::PaymentMethod.attach(payment_method_id, + { customer: customer_id }, + stripe_account: stripe_account_id) + end + end +end From 3fb1df9bb390f231335765ec454d03e75ae50a02 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Sun, 26 Jan 2020 18:49:16 +0000 Subject: [PATCH 39/97] Rename CardCloner to CreditCardCloner because it's dependent on Spree:CreditCard attributes --- app/models/spree/gateway/stripe_sca.rb | 2 +- lib/stripe/{card_cloner.rb => credit_card_cloner.rb} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename lib/stripe/{card_cloner.rb => credit_card_cloner.rb} (98%) diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index 0cafe4b6d6..e0849d2dbb 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -70,7 +70,7 @@ module Spree options[:currency] = gateway_options[:currency] options[:stripe_account] = stripe_account_id - Stripe::CardCloner.new.clone!(creditcard, stripe_account_id) + Stripe::CreditCardCloner.new.clone!(creditcard, stripe_account_id) options[:customer] = creditcard.gateway_customer_profile_id payment_method = creditcard.gateway_payment_profile_id diff --git a/lib/stripe/card_cloner.rb b/lib/stripe/credit_card_cloner.rb similarity index 98% rename from lib/stripe/card_cloner.rb rename to lib/stripe/credit_card_cloner.rb index c0e14f2644..021029a83e 100644 --- a/lib/stripe/card_cloner.rb +++ b/lib/stripe/credit_card_cloner.rb @@ -11,7 +11,7 @@ # This process can be deleted once all hubs are running on the new stripe_sca method and all cards in the system have been migrated to the new payment_methods # Basically, when all DBs have no card_* values in credit_card.gateway_payment_profile_id module Stripe - class CardCloner + class CreditCardCloner def clone!(credit_card, stripe_account_id) return unless credit_card.gateway_payment_profile_id.starts_with?('card_') From 699110258b2c1c116856dcc0eb512806040099d4 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Sun, 26 Jan 2020 19:14:18 +0000 Subject: [PATCH 40/97] Add spec for credit_card_cloner --- spec/lib/stripe/credit_card_cloner_spec.rb | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 spec/lib/stripe/credit_card_cloner_spec.rb diff --git a/spec/lib/stripe/credit_card_cloner_spec.rb b/spec/lib/stripe/credit_card_cloner_spec.rb new file mode 100644 index 0000000000..771db58c21 --- /dev/null +++ b/spec/lib/stripe/credit_card_cloner_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'stripe/credit_card_cloner' + +module Stripe + describe CreditCardCloner do + describe "#clone!" do + let(:cloner) { Stripe::CreditCardCloner.new } + + let(:customer_id) { "cus_A123" } + let(:card_id) { "card_1234" } + let(:new_customer_id) { "cus_A456" } + let(:new_payment_method_id) { "pm_4567" } + let(:stripe_account_id) { "acct_456" } + let(:customer_response_mock) { { status: 200, body: customer_response_body } } + + let(:credit_card) { create(:credit_card, user: create(:user)) } + + before do + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + + stub_request(:post, "https://api.stripe.com/v1/customers") + .with(basic_auth: ["sk_test_12345", ""], body: { email: credit_card.user.email }) + .to_return(customer_response_mock) + end + + context "when called with a credit_card with valid id (card_*)" do + let(:customer_response_body) { + JSON.generate(id: customer_id, default_card: card_id) + } + + it "clones the card successefully" do + cloner.clone!(credit_card, stripe_account_id) + + expect(credit_card.gateway_customer_profile_id).to eq new_customer_id + expect(credit_card.gateway_payment_profile_id).to eq new_payment_method_id + end + end + end + end +end From 1afd712ff4f01ed7f2cc47eb0e7f6e7987194405 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 27 Jan 2020 16:40:42 +0000 Subject: [PATCH 41/97] Make StripeSCA store cards (and delete them) on the Stripe platform account and not the Stripe Connected account (the sellers accounts) This is important so that cards can be re-used across sellers in OFN --- app/controllers/spree/credit_cards_controller.rb | 10 ++-------- app/models/spree/gateway/stripe_sca.rb | 3 ++- .../payments/source_forms/_stripe_sca.html.haml | 2 +- .../spree/checkout/payment/_stripe_sca.html.haml | 2 +- lib/stripe/profile_storer.rb | 11 ++--------- .../spree/credit_cards_controller_spec.rb | 16 ---------------- spec/lib/stripe/profile_storer_spec.rb | 1 - 7 files changed, 8 insertions(+), 37 deletions(-) diff --git a/app/controllers/spree/credit_cards_controller.rb b/app/controllers/spree/credit_cards_controller.rb index fbad74e943..d2d7939d0f 100644 --- a/app/controllers/spree/credit_cards_controller.rb +++ b/app/controllers/spree/credit_cards_controller.rb @@ -56,15 +56,9 @@ module Spree private - # Currently can only destroy the whole customer object + # It destroys the whole customer object def destroy_at_stripe - if @credit_card.payment_method && - @credit_card.payment_method.type == "Spree::Gateway::StripeSCA" - options = { stripe_account: stripe_account_id } - end - - stripe_customer = Stripe::Customer.retrieve(@credit_card.gateway_customer_profile_id, - options || {}) + stripe_customer = Stripe::Customer.retrieve(@credit_card.gateway_customer_profile_id, {}) stripe_customer.delete if stripe_customer end diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index e0849d2dbb..78fff7d0cd 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'stripe/profile_storer' +require 'stripe/credit_card_cloner' require 'active_merchant/billing/gateways/stripe_payment_intents' require 'active_merchant/billing/gateways/stripe_decorator' @@ -52,7 +53,7 @@ module Spree def create_profile(payment) return unless payment.source.gateway_customer_profile_id.nil? - profile_storer = Stripe::ProfileStorer.new(payment, provider, stripe_account_id) + profile_storer = Stripe::ProfileStorer.new(payment, provider) profile_storer.create_customer_from_token end diff --git a/app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml b/app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml index b8939ea4a7..fbdba84ac3 100644 --- a/app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml +++ b/app/views/spree/admin/payments/source_forms/_stripe_sca.html.haml @@ -2,7 +2,7 @@ %script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"} - if Stripe.publishable_key :javascript - angular.module('admin.payments').value("stripeObject", Stripe("#{Stripe.publishable_key}", { stripeAccount: "#{StripeAccount.find_by_enterprise_id(payment_method.preferred_enterprise_id).andand.stripe_user_id}" })) + angular.module('admin.payments').value("stripeObject", Stripe("#{Stripe.publishable_key}")) .row .three.columns diff --git a/app/views/spree/checkout/payment/_stripe_sca.html.haml b/app/views/spree/checkout/payment/_stripe_sca.html.haml index 00ded42afe..2929b90d3e 100644 --- a/app/views/spree/checkout/payment/_stripe_sca.html.haml +++ b/app/views/spree/checkout/payment/_stripe_sca.html.haml @@ -1,7 +1,7 @@ - content_for :injection_data do - if Stripe.publishable_key :javascript - angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}", { stripeAccount: "#{StripeAccount.find_by_enterprise_id(payment_method.preferred_enterprise_id).andand.stripe_user_id}" })) + angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}")) .row{ "ng-show" => "savedCreditCards.length > 0" } .small-12.columns diff --git a/lib/stripe/profile_storer.rb b/lib/stripe/profile_storer.rb index 1806a48bd6..eb5212a1d2 100644 --- a/lib/stripe/profile_storer.rb +++ b/lib/stripe/profile_storer.rb @@ -4,10 +4,9 @@ module Stripe class ProfileStorer - def initialize(payment, provider, stripe_account_id = nil) + def initialize(payment, provider) @payment = payment @provider = provider - @stripe_account_id = stripe_account_id end def create_customer_from_token @@ -29,13 +28,7 @@ module Stripe email: @payment.order.email, login: Stripe.api_key, address: address_for(@payment) - }.merge(stripe_account_option) - end - - def stripe_account_option - return {} if @stripe_account_id.blank? - - { stripe_account: @stripe_account_id } + } end def address_for(payment) diff --git a/spec/controllers/spree/credit_cards_controller_spec.rb b/spec/controllers/spree/credit_cards_controller_spec.rb index b9ed2ed77b..e8996433f3 100644 --- a/spec/controllers/spree/credit_cards_controller_spec.rb +++ b/spec/controllers/spree/credit_cards_controller_spec.rb @@ -172,22 +172,6 @@ describe Spree::CreditCardsController, type: :controller do expect(response).to redirect_to spree.account_path(anchor: 'cards') end end - - context "where the payment method is StripeSCA" do - let(:stripe_payment_method) { create(:stripe_sca_payment_method) } - let!(:card) { create(:credit_card, gateway_customer_profile_id: 'cus_AZNMJ', payment_method: stripe_payment_method) } - - before do - stub_request(:delete, "https://api.stripe.com/v1/customers/cus_AZNMJ"). - to_return(status: 200, body: JSON.generate(deleted: true, id: "cus_AZNMJ")) - end - - it "the request to destroy the Stripe customer includes the stripe_account_id" do - expect(Stripe::Customer).to receive(:retrieve).with(card.gateway_customer_profile_id, { stripe_account: "abc123" }) - - expect{ delete :destroy, params }.to change(Spree::CreditCard, :count).by(-1) - end - end end end end diff --git a/spec/lib/stripe/profile_storer_spec.rb b/spec/lib/stripe/profile_storer_spec.rb index beec53feec..a5c7cfc7d2 100644 --- a/spec/lib/stripe/profile_storer_spec.rb +++ b/spec/lib/stripe/profile_storer_spec.rb @@ -7,7 +7,6 @@ module Stripe describe "create_customer_from_token" do let(:payment) { create(:payment) } let(:stripe_payment_method) { create(:stripe_payment_method) } - let(:stripe_account_id) { "12312" } let(:profile_storer) { Stripe::ProfileStorer.new(payment, stripe_payment_method.provider) } let(:customer_id) { "cus_A123" } From 5ef1510fc72bb72331499a43320db4eb42d8f8f2 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Mon, 27 Jan 2020 16:43:31 +0000 Subject: [PATCH 42/97] Adapt CreditCard cloner to clone not 'cards of the platform account to payment_methods of the connected accounts' but instead 'cards or payment_methods of the platform account to payment_methods of the connected accounts' This process mimicks the existing process of generating a token on the connected account from a card on the platform account. In the Payment Intents API we need to create a payment method in the connected account, a token is not enough --- app/models/spree/gateway/stripe_sca.rb | 8 ++-- lib/stripe/credit_card_cloner.rb | 45 ++++++++++++---------- spec/lib/stripe/credit_card_cloner_spec.rb | 12 +++++- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index 78fff7d0cd..de383f3b64 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -71,11 +71,9 @@ module Spree options[:currency] = gateway_options[:currency] options[:stripe_account] = stripe_account_id - Stripe::CreditCardCloner.new.clone!(creditcard, stripe_account_id) - options[:customer] = creditcard.gateway_customer_profile_id - payment_method = creditcard.gateway_payment_profile_id - - [money, payment_method, options] + connected_acct_customer_id, connected_acct_payment_method_id = Stripe::CreditCardCloner.new.clone(creditcard, stripe_account_id) + options[:customer] = connected_acct_customer_id + [money, connected_acct_payment_method_id, options] end def failed_activemerchant_billing_response(error_message) diff --git a/lib/stripe/credit_card_cloner.rb b/lib/stripe/credit_card_cloner.rb index 021029a83e..80b094fe84 100644 --- a/lib/stripe/credit_card_cloner.rb +++ b/lib/stripe/credit_card_cloner.rb @@ -1,44 +1,49 @@ # frozen_string_literal: true # Here we clone -# - a card (card_*) stored in a customer in a platform account +# - a card (card_*) or payment_method (pm_*) stored in a customer in a platform account # into -# - a payment method (pm_*) in a new customer in a connected account +# - a payment method (pm_*) in a new customer in a connected account # -# This process is used in the migration between using the Stripe Charges API (stripe_connect) -# and the Stripe Payment Intents API (stripe_sca) +# This is required when using the Stripe Payment Intents API: +# - the customer and payment methods are stored in the platform account +# so that they can be re-used across multiple sellers +# - when a card needs to be charged, we need to create it in the seller's stripe account # -# This process can be deleted once all hubs are running on the new stripe_sca method and all cards in the system have been migrated to the new payment_methods -# Basically, when all DBs have no card_* values in credit_card.gateway_payment_profile_id +# We are doing this process every time the card is charged: +# - this means that, if the customer uses the same card on the same seller multiple times, +# the card will be created multiple times on the seller's account +# - to avoid this, we would have to store the IDs of every card on each seller's stripe account +# in our database (this way we only have to store the platform account ID) module Stripe class CreditCardCloner - def clone!(credit_card, stripe_account_id) - return unless credit_card.gateway_payment_profile_id.starts_with?('card_') + def clone(credit_card, connected_account_id) + new_payment_method = clone_payment_method(credit_card, connected_account_id) - new_payment_method = clone_payment_method(credit_card, stripe_account_id) new_customer = Stripe::Customer.create({ email: credit_card.user.email }, - stripe_account: stripe_account_id) - attach_payment_method_to_customer(new_payment_method.id, new_customer.id, stripe_account_id) + stripe_account: connected_account_id) + attach_payment_method_to_customer(new_payment_method.id, + new_customer.id, + connected_account_id) - credit_card.update_attributes gateway_customer_profile_id: new_customer.id, - gateway_payment_profile_id: new_payment_method.id - credit_card + [new_customer.id, new_payment_method.id] end private - def clone_payment_method(credit_card, stripe_account_id) - card_id = credit_card.gateway_payment_profile_id + def clone_payment_method(credit_card, connected_account_id) + platform_acct_payment_method_id = credit_card.gateway_payment_profile_id customer_id = credit_card.gateway_customer_profile_id - Stripe::PaymentMethod.create({ customer: customer_id, payment_method: card_id }, - stripe_account: stripe_account_id) + Stripe::PaymentMethod.create({ customer: customer_id, + payment_method: platform_acct_payment_method_id }, + stripe_account: connected_account_id) end - def attach_payment_method_to_customer(payment_method_id, customer_id, stripe_account_id) + def attach_payment_method_to_customer(payment_method_id, customer_id, connected_account_id) Stripe::PaymentMethod.attach(payment_method_id, { customer: customer_id }, - stripe_account: stripe_account_id) + stripe_account: connected_account_id) end end end diff --git a/spec/lib/stripe/credit_card_cloner_spec.rb b/spec/lib/stripe/credit_card_cloner_spec.rb index 771db58c21..07872c0259 100644 --- a/spec/lib/stripe/credit_card_cloner_spec.rb +++ b/spec/lib/stripe/credit_card_cloner_spec.rb @@ -5,7 +5,7 @@ require 'stripe/credit_card_cloner' module Stripe describe CreditCardCloner do - describe "#clone!" do + describe "#clone" do let(:cloner) { Stripe::CreditCardCloner.new } let(:customer_id) { "cus_A123" } @@ -14,24 +14,32 @@ module Stripe let(:new_payment_method_id) { "pm_4567" } let(:stripe_account_id) { "acct_456" } let(:customer_response_mock) { { status: 200, body: customer_response_body } } + let(:payment_method_response_mock) { { status: 200, body: payment_method_response_body } } let(:credit_card) { create(:credit_card, user: create(:user)) } before do allow(Stripe).to receive(:api_key) { "sk_test_12345" } + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(basic_auth: ["sk_test_12345", ""]) + .to_return(payment_method_response_mock) + stub_request(:post, "https://api.stripe.com/v1/customers") .with(basic_auth: ["sk_test_12345", ""], body: { email: credit_card.user.email }) .to_return(customer_response_mock) end context "when called with a credit_card with valid id (card_*)" do + let(:payment_method_response_body) { + JSON.generate(id: new_payment_method_id, default_card: card_id) + } let(:customer_response_body) { JSON.generate(id: customer_id, default_card: card_id) } it "clones the card successefully" do - cloner.clone!(credit_card, stripe_account_id) + cloner.clone(credit_card, stripe_account_id) expect(credit_card.gateway_customer_profile_id).to eq new_customer_id expect(credit_card.gateway_payment_profile_id).to eq new_payment_method_id From ccb4c77d1feb1444e9458f33f68d3151059241c6 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 27 Jan 2020 19:21:39 +0000 Subject: [PATCH 43/97] Adapt credit card cloner to not clone card if it's a card to be used only once Adapt stripe_sca specs to new cloner logic --- app/models/spree/gateway/stripe_sca.rb | 7 +-- lib/stripe/credit_card_cloner.rb | 5 +++ spec/requests/checkout/stripe_sca_spec.rb | 52 ++++++++++++++++++----- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index de383f3b64..d5ac331b89 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -71,9 +71,10 @@ module Spree options[:currency] = gateway_options[:currency] options[:stripe_account] = stripe_account_id - connected_acct_customer_id, connected_acct_payment_method_id = Stripe::CreditCardCloner.new.clone(creditcard, stripe_account_id) - options[:customer] = connected_acct_customer_id - [money, connected_acct_payment_method_id, options] + customer_id, payment_method_id = Stripe::CreditCardCloner.new.clone(creditcard, + stripe_account_id) + options[:customer] = customer_id + [money, payment_method_id, options] end def failed_activemerchant_billing_response(error_message) diff --git a/lib/stripe/credit_card_cloner.rb b/lib/stripe/credit_card_cloner.rb index 80b094fe84..d760ee52a0 100644 --- a/lib/stripe/credit_card_cloner.rb +++ b/lib/stripe/credit_card_cloner.rb @@ -18,6 +18,11 @@ module Stripe class CreditCardCloner def clone(credit_card, connected_account_id) + # No need to clone the card if there's no customer, i.e., it's a card for one time usage + if credit_card.gateway_customer_profile_id.blank? + return nil, credit_card.gateway_payment_profile_id + end + new_payment_method = clone_payment_method(credit_card, connected_account_id) new_customer = Stripe::Customer.create({ email: credit_card.user.email }, diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index dcdb0f4291..b665563f0b 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -20,8 +20,9 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques let!(:order) { line_item.order } let(:address) { create(:address) } let(:stripe_payment_method) { "pm_123" } - let(:new_stripe_payment_method) { "new_pm_123" } let(:customer_id) { "cus_A123" } + let(:hubs_stripe_payment_method) { "pm_456" } + let(:hubs_customer_id) { "cus_A456" } let(:payments_attributes) do { payment_method_id: payment_method.id, @@ -118,13 +119,12 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end context "and the customer requests that the card is saved for later" do - let(:payment_method_response_mock) do + let(:payment_method_attach_response_mock) do { status: 200, - body: JSON.generate(id: new_stripe_payment_method, customer: customer_id) + body: JSON.generate(id: stripe_payment_method, customer: customer_id) } end - let(:customer_response_mock) do { status: 200, @@ -132,19 +132,49 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques } end + let(:hubs_customer_response_mock) do + { + status: 200, + body: JSON.generate(id: hubs_customer_id, sources: { data: [{ id: "1" }] }) + } + end + let(:hubs_payment_method_response_mock) do + { + status: 200, + body: JSON.generate(id: hubs_stripe_payment_method, customer: hubs_customer_id) + } + end + before do source_attributes = params[:order][:payments_attributes][0][:source_attributes] source_attributes[:save_requested_by_customer] = '1' - # Saves the card against the user + # Creates a customer stub_request(:post, "https://api.stripe.com/v1/customers") - .with(basic_auth: ["sk_test_12345", ""], body: { email: order.email }) + .with(body: { email: order.email }) .to_return(customer_response_mock) - # Requests a payment method from the newly saved card + # Attaches the payment method to the customer stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{stripe_payment_method}/attach") .with(body: { customer: customer_id }) - .to_return(payment_method_response_mock) + .to_return(payment_method_attach_response_mock) + + # Clones the payment method to the hub's stripe account + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(body: { customer: customer_id, payment_method: stripe_payment_method }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_payment_method_response_mock) + + # Creates a customer on the hub's stripe account to hold the payment meethod + stub_request(:post, "https://api.stripe.com/v1/customers") + .with(body: { email: order.email }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_customer_response_mock) + + # Attaches the payment method to the customer in the hub's stripe account + stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{hubs_stripe_payment_method}/attach") + .with(body: { customer: hubs_customer_id }) + .to_return(hubs_payment_method_response_mock) # Charges the card stub_request(:post, "https://api.stripe.com/v1/payment_intents") @@ -155,7 +185,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end context "and the customer, payment_method and payment_intent requests are successful" do - it "should process the payment, and stores the card/customer details" do + it "should process the payment, and store the card/customer details" do put update_checkout_path, params expect(json_response["path"]).to eq spree.order_path(order) @@ -164,7 +194,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques card = order.payments.completed.first.source expect(card.gateway_customer_profile_id).to eq customer_id - expect(card.gateway_payment_profile_id).to eq new_stripe_payment_method + expect(card.gateway_payment_profile_id).to eq stripe_payment_method expect(card.cc_type).to eq "visa" expect(card.last_digits).to eq "4242" expect(card.first_name).to eq "Jill" @@ -204,7 +234,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end context "when the payment_method request returns an error message" do - let(:payment_method_response_mock) do + let(:hubs_payment_method_response_mock) do { status: 402, body: JSON.generate(error: { message: "payment-method-failure" }) } end From f8ab64d71ef801669d53f75b7acc284ce471ffbc Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 27 Jan 2020 19:49:19 +0000 Subject: [PATCH 44/97] Move specs around in stripe_sca_spec so we can re-use the cloning stubs when storing a new card and when re-using a new card --- spec/requests/checkout/stripe_sca_spec.rb | 244 +++++++++++----------- 1 file changed, 124 insertions(+), 120 deletions(-) diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index b665563f0b..b7d4d3066f 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -75,50 +75,83 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques set_order order end - context "when a new card is submitted" do - context "and the user doesn't request that the card is saved for later" do - before do - # Charges the card - stub_request(:post, "https://api.stripe.com/v1/payment_intents") - .with(basic_auth: ["sk_test_12345", ""], body: /#{stripe_payment_method}.*#{order.number}/) - .to_return(payment_intent_response_mock) - end + context "when the user submits a new card and doesn't request that the card is saved for later" do + before do + # Charges the card + stub_request(:post, "https://api.stripe.com/v1/payment_intents") + .with(basic_auth: ["sk_test_12345", ""], body: /#{stripe_payment_method}.*#{order.number}/) + .to_return(payment_intent_response_mock) + end - context "and the paymeent intent request is successful" do - it "should process the payment without storing card details" do - put update_checkout_path, params + context "and the paymeent intent request is successful" do + it "should process the payment without storing card details" do + put update_checkout_path, params - expect(json_response["path"]).to eq spree.order_path(order) - expect(order.payments.completed.count).to be 1 + expect(json_response["path"]).to eq spree.order_path(order) + expect(order.payments.completed.count).to be 1 - card = order.payments.completed.first.source + card = order.payments.completed.first.source - expect(card.gateway_customer_profile_id).to eq nil - expect(card.gateway_payment_profile_id).to eq stripe_payment_method - expect(card.cc_type).to eq "visa" - expect(card.last_digits).to eq "4242" - expect(card.first_name).to eq "Jill" - expect(card.last_name).to eq "Jeffreys" - end - end - - context "when the payment intent request returns an error message" do - let(:payment_intent_response_mock) do - { status: 402, body: JSON.generate(error: { message: "payment-intent-failure" }) } - end - - it "should not process the payment" do - put update_checkout_path, params - - expect(response.status).to be 400 - - expect(json_response["flash"]["error"]).to eq "payment-intent-failure" - expect(order.payments.completed.count).to be 0 - end + expect(card.gateway_customer_profile_id).to eq nil + expect(card.gateway_payment_profile_id).to eq stripe_payment_method + expect(card.cc_type).to eq "visa" + expect(card.last_digits).to eq "4242" + expect(card.first_name).to eq "Jill" + expect(card.last_name).to eq "Jeffreys" end end - context "and the customer requests that the card is saved for later" do + context "when the payment intent request returns an error message" do + let(:payment_intent_response_mock) do + { status: 402, body: JSON.generate(error: { message: "payment-intent-failure" }) } + end + + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]).to eq "payment-intent-failure" + expect(order.payments.completed.count).to be 0 + end + end + end + + context "when saving a card or using a stored card is involved" do + let(:hubs_customer_response_mock) do + { + status: 200, + body: JSON.generate(id: hubs_customer_id, sources: { data: [{ id: "1" }] }) + } + end + let(:hubs_payment_method_response_mock) do + { + status: 200, + body: JSON.generate(id: hubs_stripe_payment_method, customer: hubs_customer_id) + } + end + + before do + # Clones the payment method to the hub's stripe account + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(body: { customer: customer_id, payment_method: stripe_payment_method }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_payment_method_response_mock) + + # Creates a customer on the hub's stripe account to hold the payment meethod + stub_request(:post, "https://api.stripe.com/v1/customers") + .with(body: { email: order.email }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_customer_response_mock) + + # Attaches the payment method to the customer in the hub's stripe account + stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{hubs_stripe_payment_method}/attach") + .with(body: { customer: hubs_customer_id }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_payment_method_response_mock) + end + + context "when the user submits a new card and requests that the card is saved for later" do let(:payment_method_attach_response_mock) do { status: 200, @@ -132,26 +165,14 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques } end - let(:hubs_customer_response_mock) do - { - status: 200, - body: JSON.generate(id: hubs_customer_id, sources: { data: [{ id: "1" }] }) - } - end - let(:hubs_payment_method_response_mock) do - { - status: 200, - body: JSON.generate(id: hubs_stripe_payment_method, customer: hubs_customer_id) - } - end - before do source_attributes = params[:order][:payments_attributes][0][:source_attributes] source_attributes[:save_requested_by_customer] = '1' # Creates a customer stub_request(:post, "https://api.stripe.com/v1/customers") - .with(body: { email: order.email }) + .with(body: { email: order.email }, + headers: hash_excluding('Stripe-Account')) .to_return(customer_response_mock) # Attaches the payment method to the customer @@ -159,23 +180,6 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques .with(body: { customer: customer_id }) .to_return(payment_method_attach_response_mock) - # Clones the payment method to the hub's stripe account - stub_request(:post, "https://api.stripe.com/v1/payment_methods") - .with(body: { customer: customer_id, payment_method: stripe_payment_method }, - headers: { 'Stripe-Account' => 'abc123' }) - .to_return(hubs_payment_method_response_mock) - - # Creates a customer on the hub's stripe account to hold the payment meethod - stub_request(:post, "https://api.stripe.com/v1/customers") - .with(body: { email: order.email }, - headers: { 'Stripe-Account' => 'abc123' }) - .to_return(hubs_customer_response_mock) - - # Attaches the payment method to the customer in the hub's stripe account - stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{hubs_stripe_payment_method}/attach") - .with(body: { customer: hubs_customer_id }) - .to_return(hubs_payment_method_response_mock) - # Charges the card stub_request(:post, "https://api.stripe.com/v1/payment_intents") .with( @@ -248,63 +252,63 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end end end - end - context "when an existing card is submitted" do - let(:credit_card) do - create( - :credit_card, - user_id: order.user_id, - gateway_payment_profile_id: stripe_payment_method, - gateway_customer_profile_id: customer_id, - last_digits: "4321", - cc_type: "master", - first_name: "Sammy", - last_name: "Signpost", - month: 11, year: 2026 - ) - end - - before do - params[:order][:existing_card_id] = credit_card.id - quick_login_as(order.user) - - # Charges the card - stub_request(:post, "https://api.stripe.com/v1/payment_intents") - .with(basic_auth: ["sk_test_12345", ""], body: %r{#{customer_id}.*#{stripe_payment_method}}) - .to_return(payment_intent_response_mock) - end - - context "and the payment intent and payment method requests are accepted" do - it "should process the payment, and keep the profile ids and other card details" do - put update_checkout_path, params - - expect(json_response["path"]).to eq spree.order_path(order) - expect(order.payments.completed.count).to be 1 - - card = order.payments.completed.first.source - - expect(card.gateway_customer_profile_id).to eq customer_id - expect(card.gateway_payment_profile_id).to eq stripe_payment_method - expect(card.cc_type).to eq "master" - expect(card.last_digits).to eq "4321" - expect(card.first_name).to eq "Sammy" - expect(card.last_name).to eq "Signpost" - end - end - - context "when the payment intent request returns an error message" do - let(:payment_intent_response_mock) do - { status: 402, body: JSON.generate(error: { message: "payment-intent-failure" }) } + context "when the user selects an existing card" do + let(:credit_card) do + create( + :credit_card, + user_id: order.user_id, + gateway_payment_profile_id: stripe_payment_method, + gateway_customer_profile_id: customer_id, + last_digits: "4321", + cc_type: "master", + first_name: "Sammy", + last_name: "Signpost", + month: 11, year: 2026 + ) end - it "should not process the payment" do - put update_checkout_path, params + before do + params[:order][:existing_card_id] = credit_card.id + quick_login_as(order.user) - expect(response.status).to be 400 + # Charges the card + stub_request(:post, "https://api.stripe.com/v1/payment_intents") + .with(basic_auth: ["sk_test_12345", ""], body: %r{#{customer_id}.*#{stripe_payment_method}}) + .to_return(payment_intent_response_mock) + end - expect(json_response["flash"]["error"]).to eq "payment-intent-failure" - expect(order.payments.completed.count).to be 0 + context "and the payment intent and payment method requests are accepted" do + it "should process the payment, and keep the profile ids and other card details" do + put update_checkout_path, params + + expect(json_response["path"]).to eq spree.order_path(order) + expect(order.payments.completed.count).to be 1 + + card = order.payments.completed.first.source + + expect(card.gateway_customer_profile_id).to eq customer_id + expect(card.gateway_payment_profile_id).to eq stripe_payment_method + expect(card.cc_type).to eq "master" + expect(card.last_digits).to eq "4321" + expect(card.first_name).to eq "Sammy" + expect(card.last_name).to eq "Signpost" + end + end + + context "when the payment intent request returns an error message" do + let(:payment_intent_response_mock) do + { status: 402, body: JSON.generate(error: { message: "payment-intent-failure" }) } + end + + it "should not process the payment" do + put update_checkout_path, params + + expect(response.status).to be 400 + + expect(json_response["flash"]["error"]).to eq "payment-intent-failure" + expect(order.payments.completed.count).to be 0 + end end end end From 7584e96759e50e1d6e229d296f10e918a1dd899b Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 27 Jan 2020 19:57:24 +0000 Subject: [PATCH 45/97] Make customer stub always return the same customer id I cant make stripe customers stub return different customer_ids based on the stripe_account header --- spec/requests/checkout/stripe_sca_spec.rb | 55 ++++++++++------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index b7d4d3066f..9b4186ae5b 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -118,37 +118,37 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end context "when saving a card or using a stored card is involved" do - let(:hubs_customer_response_mock) do - { - status: 200, - body: JSON.generate(id: hubs_customer_id, sources: { data: [{ id: "1" }] }) - } - end let(:hubs_payment_method_response_mock) do + { + status: 200, + body: JSON.generate(id: hubs_stripe_payment_method, customer: hubs_customer_id) + } + end + let(:customer_response_mock) do { status: 200, - body: JSON.generate(id: hubs_stripe_payment_method, customer: hubs_customer_id) + body: JSON.generate(id: customer_id, sources: { data: [{ id: "1" }] }) } end - before do - # Clones the payment method to the hub's stripe account - stub_request(:post, "https://api.stripe.com/v1/payment_methods") - .with(body: { customer: customer_id, payment_method: stripe_payment_method }, - headers: { 'Stripe-Account' => 'abc123' }) - .to_return(hubs_payment_method_response_mock) + before do + # Clones the payment method to the hub's stripe account + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(body: { customer: customer_id, payment_method: stripe_payment_method }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_payment_method_response_mock) - # Creates a customer on the hub's stripe account to hold the payment meethod - stub_request(:post, "https://api.stripe.com/v1/customers") - .with(body: { email: order.email }, - headers: { 'Stripe-Account' => 'abc123' }) - .to_return(hubs_customer_response_mock) + # Creates a customer on the hub's stripe account to hold the payment meethod + stub_request(:post, "https://api.stripe.com/v1/customers") + .with(body: { email: order.email }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(customer_response_mock) - # Attaches the payment method to the customer in the hub's stripe account - stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{hubs_stripe_payment_method}/attach") - .with(body: { customer: hubs_customer_id }, - headers: { 'Stripe-Account' => 'abc123' }) - .to_return(hubs_payment_method_response_mock) + # Attaches the payment method to the customer in the hub's stripe account + stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{hubs_stripe_payment_method}/attach") + .with(body: { customer: customer_id }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_payment_method_response_mock) end context "when the user submits a new card and requests that the card is saved for later" do @@ -158,12 +158,6 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques body: JSON.generate(id: stripe_payment_method, customer: customer_id) } end - let(:customer_response_mock) do - { - status: 200, - body: JSON.generate(id: customer_id, sources: { data: [{ id: "1" }] }) - } - end before do source_attributes = params[:order][:payments_attributes][0][:source_attributes] @@ -171,8 +165,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques # Creates a customer stub_request(:post, "https://api.stripe.com/v1/customers") - .with(body: { email: order.email }, - headers: hash_excluding('Stripe-Account')) + .with(body: { email: order.email }) .to_return(customer_response_mock) # Attaches the payment method to the customer From 7fb85092ce6f8162fec0b392790bd1647d0ec2e6 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 27 Jan 2020 20:00:56 +0000 Subject: [PATCH 46/97] Remove duplicate customers stubs --- spec/requests/checkout/stripe_sca_spec.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index 9b4186ae5b..cff72340ea 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -22,7 +22,6 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques let(:stripe_payment_method) { "pm_123" } let(:customer_id) { "cus_A123" } let(:hubs_stripe_payment_method) { "pm_456" } - let(:hubs_customer_id) { "cus_A456" } let(:payments_attributes) do { payment_method_id: payment_method.id, @@ -121,7 +120,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques let(:hubs_payment_method_response_mock) do { status: 200, - body: JSON.generate(id: hubs_stripe_payment_method, customer: hubs_customer_id) + body: JSON.generate(id: hubs_stripe_payment_method, customer: customer_id) } end let(:customer_response_mock) do @@ -138,10 +137,9 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques headers: { 'Stripe-Account' => 'abc123' }) .to_return(hubs_payment_method_response_mock) - # Creates a customer on the hub's stripe account to hold the payment meethod + # Creates a customer (this stubs the customers call to the main stripe account and also the call to the connected account) stub_request(:post, "https://api.stripe.com/v1/customers") - .with(body: { email: order.email }, - headers: { 'Stripe-Account' => 'abc123' }) + .with(body: { email: order.email }) .to_return(customer_response_mock) # Attaches the payment method to the customer in the hub's stripe account @@ -163,11 +161,6 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques source_attributes = params[:order][:payments_attributes][0][:source_attributes] source_attributes[:save_requested_by_customer] = '1' - # Creates a customer - stub_request(:post, "https://api.stripe.com/v1/customers") - .with(body: { email: order.email }) - .to_return(customer_response_mock) - # Attaches the payment method to the customer stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{stripe_payment_method}/attach") .with(body: { customer: customer_id }) From 10fff31dcac55fc48040a560f17ca8fe0ebd6e38 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 27 Jan 2020 20:04:07 +0000 Subject: [PATCH 47/97] Fix stripe_sca spec --- spec/requests/checkout/stripe_sca_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index cff72340ea..4dcccf5c03 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -260,7 +260,7 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques # Charges the card stub_request(:post, "https://api.stripe.com/v1/payment_intents") - .with(basic_auth: ["sk_test_12345", ""], body: %r{#{customer_id}.*#{stripe_payment_method}}) + .with(basic_auth: ["sk_test_12345", ""], body: %r{#{customer_id}.*#{hubs_stripe_payment_method}}) .to_return(payment_intent_response_mock) end From ab4add1954067edb9af4f3ccd0b9dd3c37d51d90 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 27 Jan 2020 20:15:21 +0000 Subject: [PATCH 48/97] Fix CreditCardCloner basic spec --- spec/lib/stripe/credit_card_cloner_spec.rb | 32 +++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/spec/lib/stripe/credit_card_cloner_spec.rb b/spec/lib/stripe/credit_card_cloner_spec.rb index 07872c0259..e88c865fd4 100644 --- a/spec/lib/stripe/credit_card_cloner_spec.rb +++ b/spec/lib/stripe/credit_card_cloner_spec.rb @@ -9,9 +9,9 @@ module Stripe let(:cloner) { Stripe::CreditCardCloner.new } let(:customer_id) { "cus_A123" } - let(:card_id) { "card_1234" } + let(:payment_method_id) { "pm_1234" } let(:new_customer_id) { "cus_A456" } - let(:new_payment_method_id) { "pm_4567" } + let(:new_payment_method_id) { "pm_456" } let(:stripe_account_id) { "acct_456" } let(:customer_response_mock) { { status: 200, body: customer_response_body } } let(:payment_method_response_mock) { { status: 200, body: payment_method_response_body } } @@ -22,27 +22,39 @@ module Stripe allow(Stripe).to receive(:api_key) { "sk_test_12345" } stub_request(:post, "https://api.stripe.com/v1/payment_methods") - .with(basic_auth: ["sk_test_12345", ""]) + .with(body: { customer: customer_id, payment_method: payment_method_id}, + headers: { 'Stripe-Account' => stripe_account_id }) .to_return(payment_method_response_mock) stub_request(:post, "https://api.stripe.com/v1/customers") - .with(basic_auth: ["sk_test_12345", ""], body: { email: credit_card.user.email }) + .with(body: { email: credit_card.user.email }, + headers: { 'Stripe-Account' => stripe_account_id }) .to_return(customer_response_mock) + + stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{new_payment_method_id}/attach") + .with(body: { customer: new_customer_id }, + headers: { 'Stripe-Account' => stripe_account_id }) + .to_return(payment_method_response_mock) end context "when called with a credit_card with valid id (card_*)" do let(:payment_method_response_body) { - JSON.generate(id: new_payment_method_id, default_card: card_id) + JSON.generate(id: new_payment_method_id) } let(:customer_response_body) { - JSON.generate(id: customer_id, default_card: card_id) + JSON.generate(id: new_customer_id) } - it "clones the card successefully" do - cloner.clone(credit_card, stripe_account_id) + before do + credit_card.update_attributes gateway_customer_profile_id: customer_id, + gateway_payment_profile_id: payment_method_id + end - expect(credit_card.gateway_customer_profile_id).to eq new_customer_id - expect(credit_card.gateway_payment_profile_id).to eq new_payment_method_id + it "clones the card successefully" do + customer_id, payment_method_id = cloner.clone(credit_card, stripe_account_id) + + expect(customer_id).to eq new_customer_id + expect(payment_method_id).to eq new_payment_method_id end end end From 47916f823f6e84c091215257c381c4416f6bca85 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 27 Jan 2020 20:21:19 +0000 Subject: [PATCH 49/97] Add spec to credit card cloner. No customer given. --- spec/lib/stripe/credit_card_cloner_spec.rb | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/spec/lib/stripe/credit_card_cloner_spec.rb b/spec/lib/stripe/credit_card_cloner_spec.rb index e88c865fd4..4158a78192 100644 --- a/spec/lib/stripe/credit_card_cloner_spec.rb +++ b/spec/lib/stripe/credit_card_cloner_spec.rb @@ -18,6 +18,13 @@ module Stripe let(:credit_card) { create(:credit_card, user: create(:user)) } + let(:payment_method_response_body) { + JSON.generate(id: new_payment_method_id) + } + let(:customer_response_body) { + JSON.generate(id: new_customer_id) + } + before do allow(Stripe).to receive(:api_key) { "sk_test_12345" } @@ -35,19 +42,22 @@ module Stripe .with(body: { customer: new_customer_id }, headers: { 'Stripe-Account' => stripe_account_id }) .to_return(payment_method_response_mock) + + credit_card.update_attribute :gateway_payment_profile_id, payment_method_id end - context "when called with a credit_card with valid id (card_*)" do - let(:payment_method_response_body) { - JSON.generate(id: new_payment_method_id) - } - let(:customer_response_body) { - JSON.generate(id: new_customer_id) - } + context "when called with a card without a customer (one time usage card)" do + it "returns a nil customer and the given payment id" do + customer_id, payment_method_id = cloner.clone(credit_card, stripe_account_id) + expect(customer_id).to eq nil + expect(payment_method_id).to eq payment_method_id + end + end + + context "when called with a valid customer and payment_method" do before do - credit_card.update_attributes gateway_customer_profile_id: customer_id, - gateway_payment_profile_id: payment_method_id + credit_card.update_attribute :gateway_customer_profile_id, customer_id end it "clones the card successefully" do From 404e7c1f37127070f5cbf151cc6694c54e316ae1 Mon Sep 17 00:00:00 2001 From: luisramos0 Date: Tue, 28 Jan 2020 12:10:03 +0000 Subject: [PATCH 50/97] Make credit card cloner clone the payment method even if the customer is not given This makes the payments without saving card work again in the frontoffice as well as the payments taken by the seller in the backoffice --- lib/stripe/credit_card_cloner.rb | 12 ++++------ spec/lib/stripe/credit_card_cloner_spec.rb | 28 ++++++++++++++-------- spec/requests/checkout/stripe_sca_spec.rb | 12 +++++++++- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/lib/stripe/credit_card_cloner.rb b/lib/stripe/credit_card_cloner.rb index d760ee52a0..64b67b5345 100644 --- a/lib/stripe/credit_card_cloner.rb +++ b/lib/stripe/credit_card_cloner.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true # Here we clone -# - a card (card_*) or payment_method (pm_*) stored in a customer in a platform account +# - a card (card_*) or payment_method (pm_*) stored (in a customer) in a platform account # into -# - a payment method (pm_*) in a new customer in a connected account +# - a payment method (pm_*) (in a new customer) in a connected account # # This is required when using the Stripe Payment Intents API: # - the customer and payment methods are stored in the platform account @@ -18,13 +18,11 @@ module Stripe class CreditCardCloner def clone(credit_card, connected_account_id) - # No need to clone the card if there's no customer, i.e., it's a card for one time usage - if credit_card.gateway_customer_profile_id.blank? - return nil, credit_card.gateway_payment_profile_id - end - new_payment_method = clone_payment_method(credit_card, connected_account_id) + # If no customer is given, it will clone the payment method only + return nil, new_payment_method.id if credit_card.gateway_customer_profile_id.blank? + new_customer = Stripe::Customer.create({ email: credit_card.user.email }, stripe_account: connected_account_id) attach_payment_method_to_customer(new_payment_method.id, diff --git a/spec/lib/stripe/credit_card_cloner_spec.rb b/spec/lib/stripe/credit_card_cloner_spec.rb index 4158a78192..40854560d6 100644 --- a/spec/lib/stripe/credit_card_cloner_spec.rb +++ b/spec/lib/stripe/credit_card_cloner_spec.rb @@ -28,17 +28,13 @@ module Stripe before do allow(Stripe).to receive(:api_key) { "sk_test_12345" } - stub_request(:post, "https://api.stripe.com/v1/payment_methods") - .with(body: { customer: customer_id, payment_method: payment_method_id}, - headers: { 'Stripe-Account' => stripe_account_id }) - .to_return(payment_method_response_mock) - stub_request(:post, "https://api.stripe.com/v1/customers") .with(body: { email: credit_card.user.email }, headers: { 'Stripe-Account' => stripe_account_id }) .to_return(customer_response_mock) - stub_request(:post, "https://api.stripe.com/v1/payment_methods/#{new_payment_method_id}/attach") + stub_request(:post, + "https://api.stripe.com/v1/payment_methods/#{new_payment_method_id}/attach") .with(body: { customer: new_customer_id }, headers: { 'Stripe-Account' => stripe_account_id }) .to_return(payment_method_response_mock) @@ -47,24 +43,36 @@ module Stripe end context "when called with a card without a customer (one time usage card)" do - it "returns a nil customer and the given payment id" do + before do + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(body: { payment_method: payment_method_id }, + headers: { 'Stripe-Account' => stripe_account_id }) + .to_return(payment_method_response_mock) + end + + it "clones the payment method only" do customer_id, payment_method_id = cloner.clone(credit_card, stripe_account_id) + expect(payment_method_id).to eq new_payment_method_id expect(customer_id).to eq nil - expect(payment_method_id).to eq payment_method_id end end context "when called with a valid customer and payment_method" do before do + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(body: { customer: customer_id, payment_method: payment_method_id }, + headers: { 'Stripe-Account' => stripe_account_id }) + .to_return(payment_method_response_mock) + credit_card.update_attribute :gateway_customer_profile_id, customer_id end - it "clones the card successefully" do + it "clones both the payment method and the customer" do customer_id, payment_method_id = cloner.clone(credit_card, stripe_account_id) - expect(customer_id).to eq new_customer_id expect(payment_method_id).to eq new_payment_method_id + expect(customer_id).to eq new_customer_id end end end diff --git a/spec/requests/checkout/stripe_sca_spec.rb b/spec/requests/checkout/stripe_sca_spec.rb index 4dcccf5c03..9c8f3d1b96 100644 --- a/spec/requests/checkout/stripe_sca_spec.rb +++ b/spec/requests/checkout/stripe_sca_spec.rb @@ -75,10 +75,20 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques end context "when the user submits a new card and doesn't request that the card is saved for later" do + let(:hubs_payment_method_response_mock) do + { status: 200, body: JSON.generate(id: hubs_stripe_payment_method) } + end + before do + # Clones the payment method to the hub's stripe account + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(body: { payment_method: stripe_payment_method }, + headers: { 'Stripe-Account' => 'abc123' }) + .to_return(hubs_payment_method_response_mock) + # Charges the card stub_request(:post, "https://api.stripe.com/v1/payment_intents") - .with(basic_auth: ["sk_test_12345", ""], body: /#{stripe_payment_method}.*#{order.number}/) + .with(basic_auth: ["sk_test_12345", ""], body: /#{hubs_stripe_payment_method}.*#{order.number}/) .to_return(payment_intent_response_mock) end From 03fac6f2859999ac54454d5eb5e4d257e9e0c8f0 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Sun, 23 Feb 2020 20:02:20 +0000 Subject: [PATCH 51/97] Avoid subquery with too many columns error by specifying the selected column --- app/jobs/subscription_placement_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/subscription_placement_job.rb b/app/jobs/subscription_placement_job.rb index 26c67997ba..2ef5cc9716 100644 --- a/app/jobs/subscription_placement_job.rb +++ b/app/jobs/subscription_placement_job.rb @@ -63,7 +63,7 @@ class SubscriptionPlacementJob end def unavailable_stock_lines_for(order) - order.line_items.where('variant_id NOT IN (?)', available_variants_for(order)) + order.line_items.where('variant_id NOT IN (?)', available_variants_for(order).select(&:id)) end def available_variants_for(order) From 5848a46149506991a83fb74516684dd6e1b3df29 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 26 Feb 2020 11:30:08 +0000 Subject: [PATCH 52/97] Add missing template to render stripeSCA payment and add spec to verify it's presence --- .../payments/source_views/_stripe_sca.html.haml | 1 + spec/features/admin/payments_spec.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 app/views/spree/admin/payments/source_views/_stripe_sca.html.haml diff --git a/app/views/spree/admin/payments/source_views/_stripe_sca.html.haml b/app/views/spree/admin/payments/source_views/_stripe_sca.html.haml new file mode 100644 index 0000000000..64af07bd3b --- /dev/null +++ b/app/views/spree/admin/payments/source_views/_stripe_sca.html.haml @@ -0,0 +1 @@ += render "spree/admin/payments/source_views/gateway", payment: payment diff --git a/spec/features/admin/payments_spec.rb b/spec/features/admin/payments_spec.rb index 006fd09b93..d6f402147e 100644 --- a/spec/features/admin/payments_spec.rb +++ b/spec/features/admin/payments_spec.rb @@ -34,4 +34,20 @@ feature ' expect(page).to have_content "New Payment" end end + + context "with a StripeSCA payment method" do + before do + stripe_payment_method = create(:stripe_sca_payment_method, distributors: [order.distributor]) + order.payments << create(:payment, payment_method: stripe_payment_method, order: order) + end + + it "renders the payment details" do + quick_login_as_admin + + visit spree.admin_order_payments_path order + + page.click_link("StripeSCA") + expect(page).to have_content order.payments.last.source.last_digits + end + end end From 677f31ffa838169aa2ea48df748b7d705b1880f8 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 26 Feb 2020 11:55:17 +0000 Subject: [PATCH 53/97] Make payment source_views/gateway work with nil credit card This will happen if user deletes a saved credit card used previously. In this case, the admin payment details page will render empty details and the payment amount --- .../payments/source_views/_gateway.html.haml | 14 +++++++------- spec/features/admin/payments_spec.rb | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/views/spree/admin/payments/source_views/_gateway.html.haml b/app/views/spree/admin/payments/source_views/_gateway.html.haml index bd366492ba..ebd63febc0 100644 --- a/app/views/spree/admin/payments/source_views/_gateway.html.haml +++ b/app/views/spree/admin/payments/source_views/_gateway.html.haml @@ -6,28 +6,28 @@ %dt = Spree.t(:card_number) \: - %dd= payment.source.display_number + %dd= payment.source&.display_number %dt = Spree.t(:expiration) \: %dd - = payment.source.month + = payment.source&.month \/ - = payment.source.year + = payment.source&.year %dt = Spree.t(:card_code) \: - %dd= payment.source.verification_value + %dd= payment.source&.verification_value .omega.six.columns %dl %dt = t(:maestro_or_solo_cards) \: - %dd= payment.source.issue_number + %dd= payment.source&.issue_number %dt = Spree.t(:start_date) \: %dd - = payment.source.start_month + = payment.source&.start_month \/ - = payment.source.start_year + = payment.source&.start_year diff --git a/spec/features/admin/payments_spec.rb b/spec/features/admin/payments_spec.rb index d6f402147e..2ea666f121 100644 --- a/spec/features/admin/payments_spec.rb +++ b/spec/features/admin/payments_spec.rb @@ -10,7 +10,6 @@ feature ' scenario "visiting the payment form" do quick_login_as_admin - visit spree.new_admin_order_payment_path order expect(page).to have_content "New Payment" @@ -28,7 +27,6 @@ feature ' scenario "visiting the payment form" do quick_login_as_admin - visit spree.new_admin_order_payment_path order expect(page).to have_content "New Payment" @@ -43,11 +41,24 @@ feature ' it "renders the payment details" do quick_login_as_admin - visit spree.admin_order_payments_path order page.click_link("StripeSCA") expect(page).to have_content order.payments.last.source.last_digits end + + context "with a deleted credit card" do + before do + order.payments.last.update_attribute(:source, nil) + end + + it "renders the payment details" do + quick_login_as_admin + visit spree.admin_order_payments_path order + + page.click_link("StripeSCA") + expect(page).to have_content order.payments.last.amount + end + end end end From 4c3916a93ddedeb610539463618fba41b85481b5 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Wed, 19 Feb 2020 21:22:21 -0300 Subject: [PATCH 54/97] redirect to shops list when an enterprise is not found --- app/controllers/enterprises_controller.rb | 10 ++++++++-- config/locales/en.yml | 1 + spec/controllers/enterprises_controller_spec.rb | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index e5c30438d0..c1b5b5104f 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -63,8 +63,6 @@ class EnterprisesController < BaseController end def reset_order - distributor = Enterprise.is_distributor.find_by_permalink(params[:id]) || - Enterprise.is_distributor.find(params[:id]) order = current_order(true) reset_distributor(order, distributor) @@ -74,6 +72,14 @@ class EnterprisesController < BaseController reset_order_cycle(order, distributor) order.save! + rescue ActiveRecord::RecordNotFound + flash[:error] = I18n.t(:enterprise_shop_show_error) + redirect_to shops_path + end + + def distributor + @distributor ||= Enterprise.is_distributor.find_by_permalink(params[:id]) || + Enterprise.is_distributor.find(params[:id]) end def reset_distributor(order, distributor) diff --git a/config/locales/en.yml b/config/locales/en.yml index d9ef79b65f..f483972140 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2412,6 +2412,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using enterprise_register_success_notice: "Congratulations! Registration for %{enterprise} is complete!" enterprise_bulk_update_success_notice: "Enterprises updated successfully" enterprise_bulk_update_error: 'Update failed' + enterprise_shop_show_error: "The shop you are looking for doesn't exist or is inactive on OFN. Please check other shops." order_cycles_create_notice: 'Your order cycle has been created.' order_cycles_update_notice: 'Your order cycle has been updated.' order_cycles_bulk_update_notice: 'Order cycles have been updated.' diff --git a/spec/controllers/enterprises_controller_spec.rb b/spec/controllers/enterprises_controller_spec.rb index 084beec9bf..cf6f01cca8 100644 --- a/spec/controllers/enterprises_controller_spec.rb +++ b/spec/controllers/enterprises_controller_spec.rb @@ -139,4 +139,18 @@ describe EnterprisesController, type: :controller do expect(response.status).to be 409 end end + + context "checking access on nonexistent enterprise" do + before do + spree_get :shop, id: "some_nonexistent_enterprise" + end + + it "redirects to shops_path" do + expect(response).to redirect_to shops_path + end + + it "shows a flash message with the error" do + expect(request.flash[:error]).to eq(I18n.t(:enterprise_shop_show_error)) + end + end end From 38215c2a88ab897310457f3cf9c56d1b04e05515 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Thu, 27 Feb 2020 15:23:01 +0100 Subject: [PATCH 55/97] Delete some dead code This feature for assigning defaults via the UI was previously removed --- app/models/product_import/entry_processor.rb | 35 ------------ spec/models/product_importer_spec.rb | 60 -------------------- 2 files changed, 95 deletions(-) diff --git a/app/models/product_import/entry_processor.rb b/app/models/product_import/entry_processor.rb index eab1ed4cc3..df2765d932 100644 --- a/app/models/product_import/entry_processor.rb +++ b/app/models/product_import/entry_processor.rb @@ -122,7 +122,6 @@ module ProductImport def save_new_inventory_item(entry) new_item = entry.product_object - assign_defaults(new_item, entry) new_item.import_date = @import_time if new_item.valid? && new_item.save @@ -136,7 +135,6 @@ module ProductImport def save_existing_inventory_item(entry) existing_item = entry.product_object - assign_defaults(existing_item, entry) existing_item.import_date = @import_time if existing_item.valid? && existing_item.save @@ -164,7 +162,6 @@ module ProductImport product = Spree::Product.new product.assign_attributes(entry.attributes.except('id', 'on_hand', 'on_demand')) product.supplier_id = entry.producer_id - assign_defaults(product, entry) if product.save ensure_variant_updated(product, entry) @@ -179,7 +176,6 @@ module ProductImport def save_variant(entry) variant = entry.product_object - assign_defaults(variant, entry) variant.import_date = @import_time if variant.valid? && variant.save @@ -199,37 +195,6 @@ module ProductImport ) end - def assign_defaults(object, entry) - # Assigns a default value for a specified field e.g. category='Vegetables', setting this value - # either for all entries (overwrite_all), or only for those entries where the field was blank - # in the spreadsheet (overwrite_empty), depending on selected import settings - return unless settings.defaults(entry) - - settings.defaults(entry).each do |attribute, setting| - next unless setting['active'] - - case setting['mode'] - when 'overwrite_all' - object.assign_attributes(attribute => setting['value']) - # In case of new products, some attributes are saved on the variant. - # We write them to the entry here to be copied to the variant later. - if entry.respond_to? "#{attribute}=" - entry.public_send("#{attribute}=", setting['value']) - end - when 'overwrite_empty' - if object.public_send(attribute).blank? || - ((attribute == 'on_hand') && - entry.on_hand_nil) - - object.assign_attributes(attribute => setting['value']) - if entry.respond_to? "#{attribute}=" - entry.public_send("#{attribute}=", setting['value']) - end - end - end - end - end - def display_in_inventory(variant_override, is_new = false) unless is_new existing_item = InventoryItem.where( diff --git a/spec/models/product_importer_spec.rb b/spec/models/product_importer_spec.rb index 032ee8c14f..705f99d41e 100644 --- a/spec/models/product_importer_spec.rb +++ b/spec/models/product_importer_spec.rb @@ -714,66 +714,6 @@ describe ProductImport::ProductImporter do expect(cabbage.count_on_hand).to eq 0 # In enterprise, not in file (reset) expect(lettuce.count_on_hand).to eq 96 # In different enterprise; unchanged end - - it "can overwrite fields with selected defaults when importing to product list" do - csv_data = CSV.generate do |csv| - csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "tax_category_id", "available_on", "shipping_category"] - csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", tax_category.id, "", shipping_category.name] - csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1", "kg", "", "", shipping_category.name] - end - settings = { enterprise.id.to_s => { - 'import_into' => 'product_list', - 'defaults' => { - 'on_hand' => { - 'active' => true, - 'mode' => 'overwrite_all', - 'value' => '9000' - }, - 'tax_category_id' => { - 'active' => true, - 'mode' => 'overwrite_empty', - 'value' => tax_category2.id - }, - 'shipping_category_id' => { - 'active' => true, - 'mode' => 'overwrite_all', - 'value' => shipping_category.id - }, - 'available_on' => { - 'active' => true, - 'mode' => 'overwrite_all', - 'value' => '2020-01-01' - } - } - } } - - importer = import_data csv_data, settings: settings - - importer.validate_entries - entries = JSON.parse(importer.entries_json) - - expect(filter('valid', entries)).to eq 2 - expect(filter('invalid', entries)).to eq 0 - expect(filter('create_product', entries)).to eq 2 - - importer.save_entries - - expect(importer.products_created_count).to eq 2 - expect(importer.updated_ids).to be_a(Array) - expect(importer.updated_ids.count).to eq 2 - - carrots = Spree::Product.find_by_name('Carrots') - expect(carrots.on_hand).to eq 9000 - expect(carrots.tax_category_id).to eq tax_category.id - expect(carrots.shipping_category_id).to eq shipping_category.id - expect(carrots.available_on).to be_within(1.day).of(Time.zone.local(2020, 1, 1)) - - potatoes = Spree::Product.find_by_name('Potatoes') - expect(potatoes.on_hand).to eq 9000 - expect(potatoes.tax_category_id).to eq tax_category2.id - expect(potatoes.shipping_category_id).to eq shipping_category.id - expect(potatoes.available_on).to be_within(1.day).of(Time.zone.local(2020, 1, 1)) - end end end From b528903aa80efad95684b1cae9e69c2a78d3147e Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Thu, 27 Feb 2020 19:05:36 +0000 Subject: [PATCH 56/97] Remove spec covering html format in SchedulesController#index, this is not used anywhere --- spec/controllers/admin/schedules_controller_spec.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/spec/controllers/admin/schedules_controller_spec.rb b/spec/controllers/admin/schedules_controller_spec.rb index db974df917..19f2e4b032 100644 --- a/spec/controllers/admin/schedules_controller_spec.rb +++ b/spec/controllers/admin/schedules_controller_spec.rb @@ -10,19 +10,6 @@ describe Admin::SchedulesController, type: :controller do let!(:coordinated_schedule) { create(:schedule, order_cycles: [coordinated_order_cycle] ) } let!(:uncoordinated_schedule) { create(:schedule, order_cycles: [other_order_cycle] ) } - context "html" do - context "where I manage an order cycle coordinator" do - before do - allow(controller).to receive_messages spree_current_user: managed_coordinator.owner - end - - it "returns an empty @collection" do - spree_get :index, format: :html - expect(assigns(:collection)).to eq [] - end - end - end - context "json" do context "where I manage an order cycle coordinator" do before do From 64d83bfc4dcd139d3c92097d9db802e8c9cbb1a2 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Sun, 1 Mar 2020 17:30:43 -0300 Subject: [PATCH 57/97] fix ampersand problem using ng-bind-html --- app/views/admin/order_cycles/_row.html.haml | 3 +-- app/views/admin/variant_overrides/_new_products.html.haml | 2 +- app/views/admin/variant_overrides/_products_product.html.haml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/views/admin/order_cycles/_row.html.haml b/app/views/admin/order_cycles/_row.html.haml index a9a7ba0685..4636e07e5a 100644 --- a/app/views/admin/order_cycles/_row.html.haml +++ b/app/views/admin/order_cycles/_row.html.haml @@ -19,8 +19,7 @@ {{ orderCycle.producers.length }} = t('.suppliers') %span{ ng: { hide: 'orderCycle.producers.length > 3', bind: 'orderCycle.producerNames' } } - %td.coordinator{ ng: { show: 'columns.coordinator.visible' } } - {{ orderCycle.coordinator.name }} + %td.coordinator{ ng: { show: 'columns.coordinator.visible', bind: { html: 'orderCycle.coordinator.name'} } } %td.shops{ ng: { show: 'columns.shops.visible' } } %span{'ofn-with-tip' => '{{ orderCycle.shopNames }}', ng: { show: 'orderCycle.shops.length > 3' } } {{ orderCycle.shops.length }} diff --git a/app/views/admin/variant_overrides/_new_products.html.haml b/app/views/admin/variant_overrides/_new_products.html.haml index cf23c22039..77eda7ae37 100644 --- a/app/views/admin/variant_overrides/_new_products.html.haml +++ b/app/views/admin/variant_overrides/_new_products.html.haml @@ -13,7 +13,7 @@ %th.hide=t('admin.variant_overrides.index.hide') %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } %tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } } - %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} } + %td.producer{ ng: { bind: { html: '::producersByID[product.producer_id].name'} } } %td.product{ ng: { bind: '::product.name'} } %td.variant %span{ ng: { bind: '::variant.display_name || ""'} } diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml index 5f101e91a6..312273d4d2 100644 --- a/app/views/admin/variant_overrides/_products_product.html.haml +++ b/app/views/admin/variant_overrides/_products_product.html.haml @@ -1,5 +1,5 @@ %tr.product.even - %td.producer{ ng: { show: 'columns.producer.visible', bind: '::producersByID[product.producer_id].name'} } + %td.producer{ ng: { show: 'columns.producer.visible', bind: { html: '::producersByID[product.producer_id].name'} } } %td.product{ ng: { show: 'columns.product.visible', bind: '::product.name'} } %td.sku{ ng: { show: 'columns.sku.visible' } } %td.price{ ng: { show: 'columns.price.visible' } } From fd534bf629da79cbb5dd5059a97c67f2827743d4 Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Mon, 2 Mar 2020 09:37:10 +1100 Subject: [PATCH 58/97] Updating translations for config/locales/pt_BR.yml --- config/locales/pt_BR.yml | 73 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index 0ca3d063fe..74af2f91b9 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -2593,6 +2593,79 @@ pt_BR: signup_or_login: "Faça seu cadastro ou login para começar" have_an_account: "Já possui um conta?" action_login: "Entrar agora" + inflections: + each: + one: "cada" + other: "cada" + bunch: + one: "maço" + other: "maços" + pack: + one: "pacote" + other: "pacotes" + box: + one: "caixa" + other: "caixas" + bottle: + one: "garrafa" + other: "garrafas" + jar: + one: "jarro" + other: "jarros" + head: + one: "cabeça" + other: "cabeças" + bag: + one: "sacolas" + other: "sacolas" + loaf: + one: "pão" + other: "pães" + single: + one: "simples" + other: "simples" + tub: + one: "vaso" + other: "vasos" + punnet: + one: "vasilha" + other: "vasilhas" + packet: + one: "pacote" + other: "pacotes" + item: + one: "item" + other: "items" + dozen: + one: "dúzia" + other: "dúzias" + unit: + one: "unidade" + other: "unidades" + serve: + one: "servido" + other: "servidos" + tray: + one: "bandeja" + other: "bandejas" + piece: + one: "pedaço" + other: "pedaços" + pot: + one: "pote" + other: "potes" + bundle: + one: "pacote" + other: "pacotes" + flask: + one: "frasco" + other: "frascos" + basket: + one: "cesta" + other: "cestas" + sack: + one: "saco" + other: "sacos" producers: signup: start_free_profile: "Comece com um perfil gratuito e expanda quando estiver pronto!" From 67aeae4a6d63092cda38f2d6298fb63443280f3e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 19:34:09 +0000 Subject: [PATCH 60/97] Bump oj from 3.10.2 to 3.10.3 Bumps [oj](https://github.com/ohler55/oj) from 3.10.2 to 3.10.3. - [Release notes](https://github.com/ohler55/oj/releases) - [Changelog](https://github.com/ohler55/oj/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/oj/compare/v3.10.2...v3.10.3) Signed-off-by: dependabot-preview[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3ba189ec48..a56160eff3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -480,7 +480,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.10.2) + oj (3.10.3) orm_adapter (0.5.0) paper_trail (5.2.3) activerecord (>= 3.0, < 6.0) From e4c5893c1e5c01c71e75c56a4e0969ba1ec7f075 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 19:36:43 +0000 Subject: [PATCH 61/97] Bump rubocop from 0.80.0 to 0.80.1 Bumps [rubocop](https://github.com/rubocop-hq/rubocop) from 0.80.0 to 0.80.1. - [Release notes](https://github.com/rubocop-hq/rubocop/releases) - [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md) - [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.80.0...v0.80.1) Signed-off-by: dependabot-preview[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3ba189ec48..ed1c480540 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -494,7 +494,7 @@ GEM parallel (1.19.1) paranoia (1.3.4) activerecord (~> 3.1) - parser (2.7.0.2) + parser (2.7.0.4) ast (~> 2.4.0) paypal-sdk-core (0.2.10) multi_json (~> 1.0) @@ -599,7 +599,7 @@ GEM rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.9.0) - rubocop (0.80.0) + rubocop (0.80.1) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.7.0.1) From 690474c01aba1ac6c34b0235e68b3c6099d152c4 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Mon, 2 Mar 2020 21:55:21 +0000 Subject: [PATCH 62/97] Adapt address finder spec to work with spree 2.1 code Order ship address is required to get have an order with shipping rates --- spec/lib/open_food_network/address_finder_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/open_food_network/address_finder_spec.rb b/spec/lib/open_food_network/address_finder_spec.rb index b2d3293866..e192aa1dab 100644 --- a/spec/lib/open_food_network/address_finder_spec.rb +++ b/spec/lib/open_food_network/address_finder_spec.rb @@ -137,7 +137,6 @@ module OpenFoodNetwork describe "last_used_ship_address" do let(:address) { create(:address) } let(:distributor) { create(:distributor_enterprise) } - let(:order) { create(:shipped_order, user: nil, email: email, distributor: distributor, shipments: [], ship_address: nil) } let(:finder) { AddressFinder.new(email) } context "when searching by email is not allowed" do @@ -146,8 +145,9 @@ module OpenFoodNetwork end context "and an order with a required ship address exists" do + let(:order) { create(:shipped_order, user: nil, email: email, distributor: distributor, shipments: [], ship_address: address) } + before do - order.update_attribute(:ship_address, address) order.shipping_method.update_attribute(:require_ship_address, true) end @@ -163,7 +163,7 @@ module OpenFoodNetwork end context "and an order with a ship address exists" do - before { order.update_attribute(:ship_address, address) } + let(:order) { create(:shipped_order, user: nil, email: email, distributor: distributor, shipments: [], ship_address: address) } context "and the shipping method requires an address" do before { order.shipping_method.update_attribute(:require_ship_address, true) } @@ -183,7 +183,7 @@ module OpenFoodNetwork end context "and an order without a ship address exists" do - before { order } + let!(:order) { create(:shipped_order, user: nil, email: email, distributor: distributor, shipments: [], ship_address: nil) } it "return nil" do expect(finder.send(:last_used_ship_address)).to eq nil From de752b05a7290c33be8a658890e178039e8495ce Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Tue, 3 Mar 2020 14:08:17 +1100 Subject: [PATCH 63/97] Updating translations for config/locales/fr_CA.yml --- config/locales/fr_CA.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/locales/fr_CA.yml b/config/locales/fr_CA.yml index bf3cda687e..d8d045d2d9 100644 --- a/config/locales/fr_CA.yml +++ b/config/locales/fr_CA.yml @@ -3226,6 +3226,12 @@ fr_CA: used_saved_card: "Utiliser une carte sauvegardée :" or_enter_new_card: "Ou entrez les informations pour utiliser une nouvelle carte :" remember_this_card: Se souvenir de cette carte ? + stripe_sca: + choose_one: En choisir un + enter_new_card: Entrer les informations pour la nouvelle carte + used_saved_card: "Utiliser une carte sauvegardée :" + or_enter_new_card: "Ou entrez les informations pour utiliser une nouvelle carte :" + remember_this_card: Se souvenir de cette carte ? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' From 7ea96f88e8afd05a27c5fdde4a5637e22014ac74 Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Tue, 3 Mar 2020 14:08:26 +1100 Subject: [PATCH 64/97] Updating translations for config/locales/en_CA.yml --- config/locales/en_CA.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/locales/en_CA.yml b/config/locales/en_CA.yml index 53dd86f2cd..2981d7a4f8 100644 --- a/config/locales/en_CA.yml +++ b/config/locales/en_CA.yml @@ -3212,6 +3212,12 @@ en_CA: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' From 484326561fbc4515e3ac5e1819df0e86f58f6665 Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Tue, 3 Mar 2020 14:11:28 +1100 Subject: [PATCH 65/97] Updating translations for config/locales/en_GB.yml --- config/locales/en_GB.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index 81654ea6be..9fff8f8f20 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -294,7 +294,7 @@ en_GB: shipping_method: Shipping Method shop: Shop sku: SKU - status_state: County + status_state: State tags: Tags variant: Variant weight: Weight @@ -2039,7 +2039,7 @@ en_GB: admin_share_city: "City" admin_share_zipcode: "Postcode" admin_share_country: "Country" - admin_share_state: "County" + admin_share_state: "State" hub_sidebar_hubs: "Hubs" hub_sidebar_none_available: "None Available" hub_sidebar_manage: "Manage" @@ -2549,7 +2549,7 @@ en_GB: no_changes_to_save: No changes to save.' no_authorisation: "I couldn't get authorisation to save those changes, so they remain unsaved." some_trouble: "I had some trouble saving: %{errors}" - changing_on_hand_stock: Changing on hand stock levels... + changing_on_hand_stock: Changing 'in stock' stock levels... stock_reset: Stocks reset to defaults. tag_rules: show_hide_variants: 'Show or Hide variants in my shopfront' @@ -2748,7 +2748,7 @@ en_GB: cannot_create_returns: Cannot create returns as this order has no shipped units. select_stock: "Select stock" location: "Location" - count_on_hand: "Count On Hand" + count_on_hand: "Count In Stock" quantity: "Quantity" package_from: "package from" item_description: "Item Description" @@ -3219,6 +3219,12 @@ en_GB: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' From 2712be3fa46a6025f97a4c0de8818585ef9b01aa Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Tue, 3 Mar 2020 14:14:38 +1100 Subject: [PATCH 66/97] Updating translations for config/locales/fr.yml --- config/locales/fr.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 434aa1bc4b..a261ee007d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -3243,6 +3243,12 @@ fr: used_saved_card: "Utiliser une carte sauvegardée :" or_enter_new_card: "Ou entrez les informations pour utiliser une nouvelle carte :" remember_this_card: Se souvenir de cette carte ? + stripe_sca: + choose_one: En choisir un + enter_new_card: Entrer les informations pour la nouvelle carte + used_saved_card: "Utiliser une carte sauvegardée :" + or_enter_new_card: "Ou entrez les informations pour utiliser une nouvelle carte :" + remember_this_card: Se souvenir de cette carte ? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' From a508c5570048419f7f6ec7795baaf66b497fced3 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 3 Mar 2020 09:27:04 +0000 Subject: [PATCH 67/97] Bring TaxRate.match to OFN The version of this method in spree 2.1 will break our build This way we simply bypass this fix in spree: https://github.com/spree/spree/pull/3669 We can get back to this in the future if we ever experience the mentioned bug --- app/models/spree/tax_rate_decorator.rb | 7 +++++++ spec/models/spree/tax_rate_spec.rb | 2 ++ 2 files changed, 9 insertions(+) diff --git a/app/models/spree/tax_rate_decorator.rb b/app/models/spree/tax_rate_decorator.rb index 4e256a2cce..79a7da9679 100644 --- a/app/models/spree/tax_rate_decorator.rb +++ b/app/models/spree/tax_rate_decorator.rb @@ -1,6 +1,13 @@ module Spree TaxRate.class_eval do class << self + def match(order) + return [] unless order.tax_zone + all.select do |rate| + rate.zone == order.tax_zone || rate.zone.contains?(order.tax_zone) || rate.zone.default_tax + end + end + def match_with_sales_tax_registration(order) return [] if order.distributor && !order.distributor.charges_sales_tax diff --git a/spec/models/spree/tax_rate_spec.rb b/spec/models/spree/tax_rate_spec.rb index a51b0fa34f..e2f7e7eb65 100644 --- a/spec/models/spree/tax_rate_spec.rb +++ b/spec/models/spree/tax_rate_spec.rb @@ -1,3 +1,5 @@ +require 'spec_helper' + module Spree describe TaxRate do describe "selecting tax rates to apply to an order" do From 6c8b1753449a17bc1c919c09ff1e6d2e72b06f3b Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 3 Mar 2020 09:29:02 +0000 Subject: [PATCH 68/97] Merge match and its alias method --- app/models/spree/tax_rate_decorator.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/models/spree/tax_rate_decorator.rb b/app/models/spree/tax_rate_decorator.rb index 79a7da9679..f15ded6de8 100644 --- a/app/models/spree/tax_rate_decorator.rb +++ b/app/models/spree/tax_rate_decorator.rb @@ -2,18 +2,13 @@ module Spree TaxRate.class_eval do class << self def match(order) + return [] if order.distributor && !order.distributor.charges_sales_tax return [] unless order.tax_zone + all.select do |rate| rate.zone == order.tax_zone || rate.zone.contains?(order.tax_zone) || rate.zone.default_tax end end - - def match_with_sales_tax_registration(order) - return [] if order.distributor && !order.distributor.charges_sales_tax - - match_without_sales_tax_registration(order) - end - alias_method_chain :match, :sales_tax_registration end def adjust_with_included_tax(order) From af8369ae1b91406a8fcf4210d983d86f1b69c13c Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 3 Mar 2020 10:56:57 +0000 Subject: [PATCH 69/97] Remove 5 years old debug code This reverts ab9bc7b1dcd0c3f0027ecb788ed43f91984cb744, it can be added if the issue happens again --- app/controllers/admin/enterprises_controller.rb | 10 +--------- app/views/admin/enterprises/_admin_index.html.haml | 4 ---- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 970c09a7ee..8369c5283f 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -23,7 +23,6 @@ module Admin before_filter :setup_property, only: [:edit] helper 'spree/products' - include ActionView::Helpers::TextHelper include OrderCyclesHelper def index @@ -77,19 +76,12 @@ module Admin def bulk_update @enterprise_set = EnterpriseSet.new(collection, params[:enterprise_set]) - touched_enterprises = @enterprise_set.collection.select(&:changed?) if @enterprise_set.save flash[:success] = I18n.t(:enterprise_bulk_update_success_notice) - # 18-3-2015: It seems that the form for this action sometimes loads bogus values for - # the 'sells' field, and submitting that form results in a bunch of enterprises with - # values that have mysteriously changed. This statement is here to help debug that - # issue, and should be removed (along with its display in index.html.haml) when the - # issue has been resolved. - flash[:action] = "#{I18n.t(:updated)} #{pluralize(touched_enterprises.count, 'enterprise')}: #{touched_enterprises.map(&:name).join(', ')}" - redirect_to main_app.admin_enterprises_path else + touched_enterprises = @enterprise_set.collection.select(&:changed?) @enterprise_set.collection.select! { |e| touched_enterprises.include? e } flash[:error] = I18n.t(:enterprise_bulk_update_error) render :index diff --git a/app/views/admin/enterprises/_admin_index.html.haml b/app/views/admin/enterprises/_admin_index.html.haml index 3376601aaa..cb38f57722 100644 --- a/app/views/admin/enterprises/_admin_index.html.haml +++ b/app/views/admin/enterprises/_admin_index.html.haml @@ -1,7 +1,3 @@ --# For purposes of debugging bulk_update. See Admin/Enterprises#bulk_update. -- if flash[:action] - %p= flash[:action] - = form_for @enterprise_set, url: main_app.bulk_update_admin_enterprises_path do |f| %table#listing_enterprises.index %colgroup From ba750547a268b1ea50eaa4018a8be6d9765857ea Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 3 Mar 2020 12:26:18 +0000 Subject: [PATCH 70/97] The touch process in 'belongs_to :supplier, class_name: 'Enterprise', touch: true' must have changed in rails 4 and now we need to reload the enterprise to get the new updated_at value --- spec/models/enterprise_caching_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/enterprise_caching_spec.rb b/spec/models/enterprise_caching_spec.rb index 047fc6ab02..2fe4bd3f7a 100644 --- a/spec/models/enterprise_caching_spec.rb +++ b/spec/models/enterprise_caching_spec.rb @@ -33,7 +33,7 @@ describe Enterprise do it "touches enterprise when the supplier of a product changes" do expect { product.update_attributes!(supplier: supplier2) - }.to change { enterprise.updated_at } + }.to change { enterprise.reload.updated_at } end end From 7fdaa0f0c71169611775f82eac0359449cc30138 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 3 Mar 2020 14:46:12 +0000 Subject: [PATCH 71/97] Make package spec work in rails 4 by persisting the test enterprises so that the copnnection between shipping methods and enterprises works --- spec/models/stock/package_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/stock/package_spec.rb b/spec/models/stock/package_spec.rb index 726eed6c37..6a3ac355cc 100644 --- a/spec/models/stock/package_spec.rb +++ b/spec/models/stock/package_spec.rb @@ -6,8 +6,8 @@ module Stock subject(:package) { Package.new(stock_location, order, contents) } - let(:enterprise) { build(:enterprise) } - let(:other_enterprise) { build(:enterprise) } + let(:enterprise) { create(:enterprise) } + let(:other_enterprise) { create(:enterprise) } let(:order) { build(:order, distributor: enterprise) } From a180576c0a477348ad42b4ef3dacf6c5dd54104b Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 3 Mar 2020 17:29:38 +0000 Subject: [PATCH 72/97] Make cart_service spec green in rails 4 branch --- spec/services/cart_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/cart_service_spec.rb b/spec/services/cart_service_spec.rb index 8a01a8cdb0..1f6b03949d 100644 --- a/spec/services/cart_service_spec.rb +++ b/spec/services/cart_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe CartService do let(:order) { double(:order, id: 123) } - let(:currency) { double(:currency) } + let(:currency) { "EUR" } let(:params) { {} } let(:distributor) { double(:distributor) } let(:order_cycle) { double(:order_cycle) } From d969190ca57130533192632163b84a5e81cdae4d Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 4 Mar 2020 14:29:11 +0000 Subject: [PATCH 73/97] Bypass problem with quick_login_as_admin in rails 4 and just user simple factory --- spec/controllers/admin/bulk_line_items_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/admin/bulk_line_items_controller_spec.rb b/spec/controllers/admin/bulk_line_items_controller_spec.rb index e3d5536022..6b7fe5bff4 100644 --- a/spec/controllers/admin/bulk_line_items_controller_spec.rb +++ b/spec/controllers/admin/bulk_line_items_controller_spec.rb @@ -27,7 +27,7 @@ describe Admin::BulkLineItemsController, type: :controller do context "as an administrator" do before do - allow(controller).to receive_messages spree_current_user: quick_login_as_admin + allow(controller).to receive_messages spree_current_user: create(:admin_user) end context "when no ransack params are passed in" do From 415415273cdaebe93bfdc926f58fceaf492ec1b4 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 4 Mar 2020 15:50:52 +0000 Subject: [PATCH 74/97] In rails 4 we need to update the stub after we update the order.user otherwise the stub will return the previous value --- spec/controllers/line_items_controller_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/controllers/line_items_controller_spec.rb b/spec/controllers/line_items_controller_spec.rb index 2a4dbd8835..31d890a80a 100644 --- a/spec/controllers/line_items_controller_spec.rb +++ b/spec/controllers/line_items_controller_spec.rb @@ -59,7 +59,10 @@ describe LineItemsController, type: :controller do end context "where the item's order is associated with the current user" do - before { order.update_attributes!(user_id: user.id) } + before do + order.update_attributes!(user_id: user.id) + allow(controller).to receive_messages spree_current_user: item.order.user + end context "without an order cycle or distributor" do it "denies deletion" do From e4d09b5404178ed05af8502e6f42aae6208fde4d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2020 19:16:40 +0000 Subject: [PATCH 75/97] Bump oj from 3.10.3 to 3.10.5 Bumps [oj](https://github.com/ohler55/oj) from 3.10.3 to 3.10.5. - [Release notes](https://github.com/ohler55/oj/releases) - [Changelog](https://github.com/ohler55/oj/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/oj/compare/v3.10.3...v3.10.5) Signed-off-by: dependabot-preview[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5ff0ac4af4..2bf97f7891 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -480,7 +480,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.10.3) + oj (3.10.5) orm_adapter (0.5.0) paper_trail (5.2.3) activerecord (>= 3.0, < 6.0) From f13d7d6845acdcc27507eeba1bb147702d01b507 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Thu, 5 Mar 2020 15:20:45 +0000 Subject: [PATCH 76/97] Fix products api spec in rails 4 --- spec/controllers/api/products_controller_spec.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/spec/controllers/api/products_controller_spec.rb b/spec/controllers/api/products_controller_spec.rb index 5da8d89e32..6bf2cba312 100644 --- a/spec/controllers/api/products_controller_spec.rb +++ b/spec/controllers/api/products_controller_spec.rb @@ -253,13 +253,9 @@ describe Api::ProductsController, type: :controller do end it "filters results by import_date" do - product.variants.first.import_date = 1.day.ago - product2.variants.first.import_date = 2.days.ago - product3.variants.first.import_date = 1.day.ago - - product.save - product2.save - product3.save + product.variants.first.update_attribute :import_date, 1.day.ago + product2.variants.first.update_attribute :import_date, 2.days.ago + product3.variants.first.update_attribute :import_date, 1.day.ago api_get :bulk_products, { page: 1, per_page: 15, import_date: 1.day.ago.to_date.to_s }, format: :json expect(returned_product_ids).to eq [product3.id, product.id] From 461b1b26f3434c539af537de99944ccd141b1e34 Mon Sep 17 00:00:00 2001 From: Pau Perez Date: Tue, 18 Feb 2020 19:00:25 +0100 Subject: [PATCH 77/97] Add controller tests to cover totals by supplier --- .../spree/admin/reports_controller.rb | 6 +- .../distributor_totals_by_supplier_spec.rb | 249 ++++++++++++++++++ 2 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 spec/controllers/spree/admin/reports/distributor_totals_by_supplier_spec.rb diff --git a/app/controllers/spree/admin/reports_controller.rb b/app/controllers/spree/admin/reports_controller.rb index ec08bca29c..cdc97fbf39 100644 --- a/app/controllers/spree/admin/reports_controller.rb +++ b/app/controllers/spree/admin/reports_controller.rb @@ -217,7 +217,11 @@ module Spree end def render_report(header, table, create_csv, csv_file_name) - send_data csv_report(header, table), filename: csv_file_name if create_csv + if create_csv + @csv_report = csv_report(header, table) + send_data @csv_report, filename: csv_file_name + end + @header = header @table = table # Rendering HTML is the default. diff --git a/spec/controllers/spree/admin/reports/distributor_totals_by_supplier_spec.rb b/spec/controllers/spree/admin/reports/distributor_totals_by_supplier_spec.rb new file mode 100644 index 0000000000..357c0a8670 --- /dev/null +++ b/spec/controllers/spree/admin/reports/distributor_totals_by_supplier_spec.rb @@ -0,0 +1,249 @@ +require 'spec_helper' + +describe Spree::Admin::ReportsController, type: :controller do + let(:csv) do + <<-CSV.strip_heredoc + Hub,Producer,Product,Variant,Amount,Curr. Cost per Unit,Total Cost,Total Shipping Cost,Shipping Method + Mary's Online Shop,Freddy's Farm Shop,Beef - 5kg Trays,1g,1,12.0,12.0,"",Shipping Method + Mary's Online Shop,Freddy's Farm Shop,Fuji Apple,2g,1,4.0,4.0,"",Shipping Method + Mary's Online Shop,Freddy's Farm Shop,Fuji Apple,5g,5,12.0,60.0,"",Shipping Method + Mary's Online Shop,Freddy's Farm Shop,Fuji Apple,8g,3,15.0,45.0,"",Shipping Method + "",TOTAL,"","","","",121.0,2.0,"" + CSV + end + + before do + DefaultStockLocation.create! + + delivery = marys_online_shop.shipping_methods.new( + name: "Home delivery", + require_ship_address: true, + calculator_type: "Spree::Calculator::FlatRate", + distributor_ids: [marys_online_shop.id] + ) + delivery.shipping_categories << DefaultShippingCategory.find_or_create + delivery.calculator.preferred_amount = 2 + delivery.save! + end + + let(:taxonomy) { Spree::Taxonomy.create!(name: 'Products') } + let(:meat) do + Spree::Taxon.create!(name: 'Meat and Fish', parent_id: taxonomy.root.id, taxonomy_id: taxonomy.id) + end + let(:fruit) do + Spree::Taxon.create!(name: 'Fruit', parent_id: taxonomy.root.id, taxonomy_id: taxonomy.id) + end + + let(:calculator) { Calculator::FlatPercentPerItem.new(preferred_flat_percent: 10) } + + let(:mary) do + password = Spree::User.friendly_token + Spree::User.create!( + email: 'mary_retailer@example.org', + password: password, + password_confirmation: password, + confirmation_sent_at: Time.zone.now, + confirmed_at: Time.zone.now + ) + end + let(:marys_online_shop) do + Enterprise.create!( + name: "Mary's Online Shop", + owner: mary, + is_primary_producer: false, + sells: "any", + address: create(:address) + ) + end + before do + fee = marys_online_shop.enterprise_fees.new( + fee_type: "sales", name: "markup", inherits_tax_category: true + ) + fee.calculator = calculator + fee.save! + end + + let(:freddy) do + password = Spree::User.friendly_token + Spree::User.create!( + email: 'freddy_shop_farmer@example.org', + password: password, + password_confirmation: password, + confirmation_sent_at: Time.zone.now, + confirmed_at: Time.zone.now + ) + end + let(:freddys_farm_shop) do + Enterprise.create!( + name: "Freddy's Farm Shop", + owner: freddy, + is_primary_producer: true, + sells: "own", + address: create(:address) + ) + end + before do + fee = freddys_farm_shop.enterprise_fees.new( + fee_type: "sales", name: "markup", inherits_tax_category: true, + ) + fee.calculator = calculator + fee.save! + end + + let!(:beef) do + product = Spree::Product.new( + name: 'Beef - 5kg Trays', + price: 50.00, + supplier_id: freddys_farm_shop.id, + primary_taxon_id: meat.id, + variant_unit: "weight", + variant_unit_scale: 1, + unit_value: 1, + ) + product.shipping_category = DefaultShippingCategory.find_or_create + product.save! + product.variants.first.update_attribute(:on_demand, true) + + InventoryItem.create!( + enterprise: marys_online_shop, + variant: product.variants.first, + visible: true + ) + VariantOverride.create!( + variant: product.variants.first, + hub: marys_online_shop, + price: 12, + on_demand: false, + count_on_hand: 5 + ) + + product + end + + let!(:apple) do + product = Spree::Product.new( + name: 'Fuji Apple', + price: 5.00, + supplier_id: freddys_farm_shop.id, + primary_taxon_id: fruit.id, + variant_unit: "weight", + variant_unit_scale: 1, + unit_value: 1, + shipping_category: DefaultShippingCategory.find_or_create + ) + product.shipping_category = DefaultShippingCategory.find_or_create + product.save! + product.variants.first.update_attribute :on_demand, true + + VariantOverride.create!( + variant: product.variants.first, + hub: marys_online_shop, + price: 12, + on_demand: false, + count_on_hand: 5 + ) + + product + end + let!(:apple_variant_2) do + variant = apple.variants.create!(weight: 0.0, unit_value: 2.0, price: 4.0) + VariantOverride.create!( + variant: variant, hub: marys_online_shop, on_demand: false, count_on_hand: 4 + ) + variant + end + let!(:apple_variant_5) do + variant = apple.variants.create!(weight: 0.0, unit_value: 5.0, price: 12.0) + VariantOverride.create!( + variant: variant, hub: marys_online_shop, on_demand: false, count_on_hand: 5 + ) + variant.update_attribute :on_demand, true + variant + end + let!(:apple_variant_8) do + variant = apple.variants.create!(weight: 0.0, unit_value: 8.0, price: 15.0) + VariantOverride.create!( + variant: variant, hub: marys_online_shop, on_demand: false, count_on_hand: 3 + ) + variant.update_attribute :on_demand, true + variant + end + + let!(:beef_variant) do + variant = beef.variants.first + OpenFoodNetwork::ScopeVariantToHub.new(marys_online_shop).scope(variant) + variant + end + + let!(:order_cycle) do + cycle = OrderCycle.create!( + name: "Mary's Online Shop OC", + orders_open_at: 1.day.ago, + orders_close_at: 1.month.from_now, + coordinator: marys_online_shop + ) + cycle.coordinator_fees << marys_online_shop.enterprise_fees.first + + incoming = Exchange.create!( + order_cycle: cycle, sender: freddys_farm_shop, receiver: cycle.coordinator, incoming: true + ) + outgoing = Exchange.create!( + order_cycle: cycle, sender: cycle.coordinator, receiver: marys_online_shop, incoming: false + ) + + freddys_farm_shop.supplied_products.each do |product| + incoming.variants << product.variants.first + outgoing.variants << product.variants.first + end + + cycle + end + + let(:order) do + create( + :order, + distributor: marys_online_shop, + order_cycle: order_cycle, + ship_address: create(:address) + ) + end + + before do + order.add_variant(beef_variant, 1, nil, order.currency) + order.add_variant(apple_variant_2, 1, nil, order.currency) + order.add_variant(apple_variant_5, 5, nil, order.currency) + order.add_variant(apple_variant_8, 3, nil, order.currency) + + order.create_proposed_shipments + order.finalize! + + order.completed_at = Time.zone.parse("2020-02-05 00:00:00 +1100") + order.save + + allow(controller).to receive(:spree_current_user).and_return(mary) + end + + it 'returns the right CSV' do + spree_post :orders_and_fulfillment, { + q: { + completed_at_gt: "2020-01-11 00:00:00 +1100", + completed_at_lt: "2020-02-12 00:00:00 +1100", + distributor_id_in: [marys_online_shop.id], + order_cycle_id_in: [""] + }, + report_type: "order_cycle_distributor_totals_by_supplier", + csv: true + } + + csv_report = assigns(:csv_report) + report_lines = csv_report.split("\n") + csv_fixture_lines = csv.split("\n") + + expect(report_lines[0]).to eq(csv_fixture_lines[0]) + expect(report_lines[1]).to eq(csv_fixture_lines[1]) + expect(report_lines[2]).to eq(csv_fixture_lines[2]) + expect(report_lines[3]).to eq(csv_fixture_lines[3]) + expect(report_lines[4]).to eq(csv_fixture_lines[4]) + expect(report_lines[5]).to eq(csv_fixture_lines[5]) + end +end From 53a63775fe2b79cb60a477afa8f03413e1171040 Mon Sep 17 00:00:00 2001 From: Pau Perez Date: Fri, 14 Feb 2020 18:38:26 +0100 Subject: [PATCH 78/97] Replace LEFT JOIN with INNER JOIN I see no reason why a LEFT might be needed and its the root cause of the awful performance. --- app/models/spree/order_decorator.rb | 6 ++++++ app/services/permissions/order.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 28d487d6fd..688f1b3b16 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -75,6 +75,12 @@ Spree::Order.class_eval do joins('LEFT OUTER JOIN spree_products ON (spree_products.id = spree_variants.product_id)') } + scope :with_line_items_variants_and_products, lambda { + joins('INNER JOIN spree_line_items ON (spree_line_items.order_id = spree_orders.id)'). + joins('INNER JOIN spree_variants ON (spree_variants.id = spree_line_items.variant_id)'). + joins('INNER JOIN spree_products ON (spree_products.id = spree_variants.product_id)') + } + scope :not_state, lambda { |state| where("state != ?", state) } diff --git a/app/services/permissions/order.rb b/app/services/permissions/order.rb index 5e95ba92c8..cf44b1e0ae 100644 --- a/app/services/permissions/order.rb +++ b/app/services/permissions/order.rb @@ -10,7 +10,7 @@ module Permissions # Find orders that the user can see def visible_orders Spree::Order. - with_line_items_variants_and_products_outer. + with_line_items_variants_and_products. where(visible_orders_where_values) end From 0042ab2f28ef2a7e992313f73910c142f0ea2106 Mon Sep 17 00:00:00 2001 From: Pau Perez Date: Mon, 2 Mar 2020 16:40:27 +0100 Subject: [PATCH 79/97] Rewrite INNER JOIN in ActiveRecord's DSL --- app/models/spree/order_decorator.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 688f1b3b16..34398bbe7d 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -76,9 +76,7 @@ Spree::Order.class_eval do } scope :with_line_items_variants_and_products, lambda { - joins('INNER JOIN spree_line_items ON (spree_line_items.order_id = spree_orders.id)'). - joins('INNER JOIN spree_variants ON (spree_variants.id = spree_line_items.variant_id)'). - joins('INNER JOIN spree_products ON (spree_products.id = spree_variants.product_id)') + joins(line_items: { variant: :product }) } scope :not_state, lambda { |state| From f23575302bc9083e11146b5c4074ffce922b151d Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Thu, 5 Mar 2020 17:07:08 +0000 Subject: [PATCH 80/97] In rails 4 variant.destroy is removing the variants from the exchanges as needed and variant.exchange_variants becomes immediatly empty but variant.exchanges is not automatically updated anymore and needs a refresh to become empty --- spec/controllers/spree/admin/variants_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/spree/admin/variants_controller_spec.rb b/spec/controllers/spree/admin/variants_controller_spec.rb index 2f8ed849b1..d88eaf12a4 100644 --- a/spec/controllers/spree/admin/variants_controller_spec.rb +++ b/spec/controllers/spree/admin/variants_controller_spec.rb @@ -69,7 +69,7 @@ module Spree variant.exchanges << exchange spree_delete :destroy, id: variant.id, product_id: variant.product.permalink, format: 'html' - expect(variant.exchanges).to be_empty + expect(variant.exchanges.reload).to be_empty end end end From 464717dec50ee66e639becf23e315ed608142de5 Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Fri, 6 Mar 2020 07:38:36 +1100 Subject: [PATCH 81/97] Updating translations for config/locales/pt_BR.yml --- config/locales/pt_BR.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index 74af2f91b9..abe07ed6b8 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -3217,6 +3217,12 @@ pt_BR: used_saved_card: "Use um cartão salvo:" or_enter_new_card: "Ou insira os detalhes de um novo cartão:" remember_this_card: Lembra-se deste cartão? + stripe_sca: + choose_one: Escolha um + enter_new_card: Digite os detalhes do novo cartão + used_saved_card: "Use um cartão já registrado" + or_enter_new_card: "Ou, adicione os dados para um novo cartão:" + remember_this_card: Lembrar deste cartão? date_picker: format: '%A-%m-' js_format: 'aa-mm-dd' From 5d51e5d39338fbefc35c72b50e2b76b1477092e4 Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Fri, 6 Mar 2020 07:41:46 +1100 Subject: [PATCH 82/97] Updating translations for config/locales/pt_BR.yml --- config/locales/pt_BR.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index abe07ed6b8..f048a4d822 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -1572,7 +1572,7 @@ pt_BR: login_invalid: "E-mail ou senha inválidos" modal_hubs: "Central de alimentos" modal_hubs_abstract: Nossas centrais são o ponto de contato entre você e as pessoas que produzem sua comida! - modal_hubs_content1: 'Você pode procurar por uma central conveniente à você por localização ou nome. Alguns possuem múltiplos pontos de entrega, onde você pode retirar suas compras, e outros ainda entregam na sua casa. Cada um é um ponto de venda independente, e por isso as ofertas e maneira de operar podem variar de um para outro. ' + modal_hubs_content1: 'Você pode procurar a central mais próxima por localização ou nome. Alguns possuem múltiplos pontos de entrega, onde você pode retirar suas compras, e outros ainda entregam na sua casa. Cada um é um ponto de venda independente, e por isso as ofertas e maneira de operar podem variar de um para outro. ' modal_hubs_content2: Você só pode comprar de uma central por vez. modal_groups: "Grupos / Regiões" modal_groups_content1: Estas são as organizações e as relações entre as centrais que compõem a Open Food Brasil. From 6304a085c0c8214a135042fb31b0d90de6ce6ef6 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Fri, 6 Mar 2020 08:30:04 +0000 Subject: [PATCH 83/97] Update all locales with the latest Transifex translations --- config/locales/ar.yml | 6 ++ config/locales/ca.yml | 6 ++ config/locales/en_AU.yml | 124 ++++++++++++++++++++------------------- config/locales/en_BE.yml | 6 ++ config/locales/en_DE.yml | 6 ++ config/locales/en_FR.yml | 6 ++ config/locales/en_GB.yml | 2 +- config/locales/en_NZ.yml | 6 ++ config/locales/en_US.yml | 6 ++ config/locales/en_ZA.yml | 6 ++ config/locales/es.yml | 6 ++ config/locales/fr_BE.yml | 6 ++ config/locales/it.yml | 6 ++ config/locales/nb.yml | 6 ++ config/locales/nl_BE.yml | 6 ++ config/locales/pt.yml | 6 ++ config/locales/tr.yml | 8 ++- 17 files changed, 157 insertions(+), 61 deletions(-) diff --git a/config/locales/ar.yml b/config/locales/ar.yml index b03afe72e0..42dcbf52ba 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -3155,6 +3155,12 @@ ar: used_saved_card: "استخدم بطاقة المحفوظة:" or_enter_new_card: "أو أدخل تفاصيل البطاقة الجديدة:" remember_this_card: تذكر هذه البطاقة؟ + stripe_sca: + choose_one: اختيار واحد + enter_new_card: أدخل تفاصيل البطاقة الجديدة + used_saved_card: "استخدم بطاقة المحفوظة:" + or_enter_new_card: "أو أدخل تفاصيل البطاقة الجديدة:" + remember_this_card: تذكر هذه البطاقة؟ date_picker: format: '٪ س-٪ م-%d' js_format: 'يوم-شهر-سنة' diff --git a/config/locales/ca.yml b/config/locales/ca.yml index b834f84a2f..25df51631d 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -3139,6 +3139,12 @@ ca: used_saved_card: "Utilitza una targeta desada:" or_enter_new_card: "O bé introdueix els detalls d'una nova targeta:" remember_this_card: Recordar aquesta targeta? + stripe_sca: + choose_one: Escull-ne un + enter_new_card: Introdueix els detalls d'una targeta nova + used_saved_card: "Utilitza una targeta desada:" + or_enter_new_card: "O bé introdueix els detalls d'una nova targeta:" + remember_this_card: Recordar aquesta targeta? date_picker: format: '%d-% m-% Y' js_format: 'dd-mm-yy' diff --git a/config/locales/en_AU.yml b/config/locales/en_AU.yml index 16d8f4d18c..cbfc05a738 100644 --- a/config/locales/en_AU.yml +++ b/config/locales/en_AU.yml @@ -119,7 +119,7 @@ en_AU: userguide: "Open Food Network User Guide" email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration." admin_panel: "Admin Panel" - email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}" + email_community_html: "Open Food Network is community supported software. If you have less than $500 per month turnover on OFN it's free to use. Read more about our membership options and make your selection: https://about.openfoodnetwork.org.au/software-pricing/. If you haven't yet chosen your membership option, you will be assigned to our basic community membership level, which is 1% of turnover through the platform. We'll invoice you monthly once you reach that level. " join_community: "Join the community" invite_manager: subject: "%{enterprise} has invited you to be a manager" @@ -1737,46 +1737,46 @@ en_AU: registration: steps: introduction: - registration_greeting: "Hi there!" - registration_intro: "You can now create a profile for your Producer or Hub" - registration_checklist: "What do I need?" - registration_time: "5-10 minutes" - registration_enterprise_address: "Enterprise address" - registration_contact_details: "Primary contact details" - registration_logo: "Your logo image" - registration_promo_image: "Landscape image for your profile" - registration_about_us: "'About Us' text" - registration_outcome_headline: "What do I get?" - registration_outcome1_html: "Your profile helps people find and contact you on the Open Food Network." - registration_outcome2: "Use this space to tell the story of your enterprise, to help drive connections to your social and online presence." - registration_outcome3: "It's also the first step towards trading on the Open Food Network, or opening an online store." + registration_greeting: "Let’s get you set up" + registration_intro: "Share your story to connect with the community (and eaters!)" + registration_checklist: "Set up your profile" + registration_time: "Tell us about yourself " + registration_enterprise_address: "And all about your enterprise" + registration_contact_details: "Then share a bit about what you do..." + registration_logo: "...or even what you sell" + registration_promo_image: "Add pictures, and stories, and ways to get in touch" + registration_about_us: "Or manage your profile whenever you need" + registration_outcome_headline: "Connect with the community and eaters" + registration_outcome1_html: "Your profile helps people find and connect with you directly on the Open Food Network." + registration_outcome2: "Use this space to tell your story, and to help drive connections with your enterprise. " + registration_outcome3: "It’s also the first step to opening your own store on the Open Food Network. \nIt’s totally free to get set up. If your shop starts to earn more than $500 a month, a 1% membership fee will apply." registration_action: "Let's get started!" details: title: "Details" - headline: "Let's Get Started" - enterprise: "Woot! First need to know a little bit about your enterprise:" - producer: "Woot! First we need to know a little bit about your farm:" - enterprise_name_field: "Enterprise Name:" - producer_name_field: "Farm Name:" + headline: "Tell us about your enterprise" + enterprise: "This will put you on our map, and be shared on your profile." + producer: "This will put you on our map, and be shared on your profile." + enterprise_name_field: "Enterprise Name" + producer_name_field: "Farm Name" producer_name_field_placeholder: "e.g. Charlie's Awesome Farm" producer_name_field_error: "Please choose a unique name for your enterprise" - address1_field: "Address line 1:" + address1_field: "Address line 1" address1_field_placeholder: "e.g. 123 Cranberry Drive" address1_field_error: "Please enter an address" - address2_field: "Address line 2:" - suburb_field: "Suburb:" + address2_field: "Address line 2" + suburb_field: "Suburb" suburb_field_placeholder: "e.g. Northcote" suburb_field_error: "Please enter a suburb" - postcode_field: "Postcode:" + postcode_field: "Postcode" postcode_field_placeholder: "e.g. 3070" postcode_field_error: "Postcode required" - state_field: "State:" + state_field: "State" state_field_error: "State required" - country_field: "Country:" + country_field: "Country" country_field_error: "Please select a country" contact: title: "Contact" - who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" + who_is_managing_enterprise: "Who is the main contact for %{enterprise}? We’ll add these details to your profile so people can get in touch." contact_field: "Primary Contact" contact_field_placeholder: "Contact Name" contact_field_required: "You need to enter a primary contact." @@ -1784,24 +1784,24 @@ en_AU: phone_field_placeholder: "eg. (03) 1234 5678" type: title: "Type" - headline: "Last step to add %{enterprise}!" - question: "Are you a producer?" - yes_producer: "Yes, I'm a producer" - no_producer: "No, I'm not a producer" + headline: "How will you use the Open Food Network?" + question: "This helps us work out what sort of package would best suit you." + yes_producer: "I’ll share my own produce" + no_producer: "I’ll share other people’s produce (and mine!)" producer_field_error: "Please choose one. Are you are producer?" - yes_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." - no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." + yes_producer_help: "What sort of produce? All the yummy things you can eat and drink. If you grow it, raise it, brew it, bake it, ferment it, milk it or mould it – you’re a producer. " + no_producer_help: "Maybe you’re someone who sells or distributes food that you or others have produced. You might be a hub, co-op, buying group, retailer, wholesaler, or someone else." create_profile: "Create Profile" about: title: "About" - headline: "Nice one!" - message: "Now let's flesh out the details about" - success: "Success! %{enterprise} added to the Open Food Network" - registration_exit_message: "If you exit this wizard at any stage, you can continue to create your profile by going to the admin interface." - enterprise_description: "Short Description" + headline: "Add to your profile" + message: "Your profile is set up! You can complete the details later, or start adding details for" + success: "Welcome to the Open Food Network, %{enterprise}!" + registration_exit_message: "If you need a bit more time, you can upload an image and add to your story in the Admin section of your profile another time." + enterprise_description: "What do you do?" enterprise_description_placeholder: "A short sentence describing your enterprise" - enterprise_long_desc: "Long Description" - enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words." + enterprise_long_desc: "Tell us more" + enterprise_long_desc_placeholder: "This is your opportunity to tell your story – what makes your enterprise different and wonderful? Try to keep it around 150 words so it doesn’t get cut off." enterprise_long_desc_length: "%{num} characters / up to 600 recommended" enterprise_abn: "ABN" enterprise_abn_placeholder: "eg. 99 123 456 789" @@ -1809,31 +1809,31 @@ en_AU: enterprise_acn_placeholder: "eg. 123 456 789" enterprise_tax_required: "You need to make a selection." images: - title: "Images" - headline: "Thanks!" - description: "Let's upload some pretty pictures so your profile looks great! :)" + title: "Logo" + headline: "Upload your logo" + description: "Help people recognise you (and make your profile stand out!)" uploading: "Uploading..." continue: "Continue" back: "Back" logo: - select_logo: "Step 1. Select Logo Image" - logo_tip: "Tip: Square images will work best, preferably at least 300×300px" - logo_label: "Choose a logo image" - logo_drag: "Drag and drop your logo here" - review_logo: "Step 2. Review Your Logo" - review_logo_tip: "Tip: for best results, your logo should fill the available space" - logo_placeholder: "Your logo will appear here for review once uploaded" + select_logo: "Select file to upload" + logo_tip: "Try using a square image, around 300x300px." + logo_label: "Select a file" + logo_drag: "Drag and drop here" + review_logo: "How does it look?" + review_logo_tip: "For best results, the image should fill the available space" + logo_placeholder: "Your logo will appear here once it's uploaded" promo: - select_promo_image: "Step 3. Select Promo Image" - promo_image_tip: "Tip: Shown as a banner, preferred size is 1200×260px" - promo_image_label: "Choose a promo image" - promo_image_drag: "Drag and drop your promo here" - review_promo_image: "Step 4. Review Your Promo Banner" - review_promo_image_tip: "Tip: for best results, your promo image should fill the available space" - promo_image_placeholder: "Your logo will appear here for review once uploaded" + select_promo_image: "Choose a shop header image" + promo_image_tip: "This displays as a banner. For best results try an image that's around 1200×260px" + promo_image_label: "Choose a header image" + promo_image_drag: "Drag and drop your header image here" + review_promo_image: "How does it look?" + review_promo_image_tip: "For best results, your banner image should fill the available space" + promo_image_placeholder: "Your logo will appear here once it's uploaded" social: title: "Social" - enterprise_final_step: "Final step!" + enterprise_final_step: "One last thing..." enterprise_social_text: "How can people find %{enterprise} online?" website: "Website" website_placeholder: "eg. openfoodnetwork.org.au" @@ -1851,9 +1851,9 @@ en_AU: text: "You have reached the limit for the number of enterprises you are allowed to own on the" action: "Return to the homepage" finished: - headline: "Finished!" + headline: "You’re all set up! " thanks: "Thanks for filling out the details for %{enterprise}." - login: "To manage your new Enterprise, go to openfoodnetwork.org.au/admin" + login: "To manage your new Enterprise, go to openfoodnetwork.org.au/admin\n\nYou can also get to your Admin page in the top righthand corner of the Open Food Network homepage, just to the left of the shopping cart symbol." action: "Open Food Network home" back: "Back" continue: "Continue" @@ -3126,6 +3126,12 @@ en_AU: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/en_BE.yml b/config/locales/en_BE.yml index 7757ac6641..eebd4d0c53 100644 --- a/config/locales/en_BE.yml +++ b/config/locales/en_BE.yml @@ -3044,6 +3044,12 @@ en_BE: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/en_DE.yml b/config/locales/en_DE.yml index 4ade7e753b..b4045f6b91 100644 --- a/config/locales/en_DE.yml +++ b/config/locales/en_DE.yml @@ -3060,6 +3060,12 @@ en_DE: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index c498d5da68..a19db5ae4a 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -3213,6 +3213,12 @@ en_FR: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index 9fff8f8f20..fe89f73505 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -57,7 +57,7 @@ en_GB: attributes: subscription_line_items: at_least_one_product: "^Please add at least one product" - not_available: "^%{name} is not available from the selected schedule" + not_available: "^%{name} is not available from the selected schedule. Your changes have not been saved." ends_at: after_begins_at: "must be after begins at" customer: diff --git a/config/locales/en_NZ.yml b/config/locales/en_NZ.yml index 1c0d06883c..3a051e71c6 100644 --- a/config/locales/en_NZ.yml +++ b/config/locales/en_NZ.yml @@ -3213,6 +3213,12 @@ en_NZ: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml index 9375fa03b4..8b955e8a3b 100644 --- a/config/locales/en_US.yml +++ b/config/locales/en_US.yml @@ -3057,6 +3057,12 @@ en_US: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/en_ZA.yml b/config/locales/en_ZA.yml index 0516828725..f60d79d734 100644 --- a/config/locales/en_ZA.yml +++ b/config/locales/en_ZA.yml @@ -3058,6 +3058,12 @@ en_ZA: used_saved_card: "Use a saved card:" or_enter_new_card: "Or, enter details for a new card:" remember_this_card: Remember this card? + stripe_sca: + choose_one: Choose one + enter_new_card: Enter details for a new card + used_saved_card: "Use a saved card:" + or_enter_new_card: "Or, enter details for a new card:" + remember_this_card: Remember this card? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/es.yml b/config/locales/es.yml index 0520566763..9acc65d841 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -3065,6 +3065,12 @@ es: used_saved_card: "Usa una tarjeta guardada:" or_enter_new_card: "O introduce los detalles de una nueva tarjeta:" remember_this_card: ¿Recordar esta tarjeta? + stripe_sca: + choose_one: Elige uno + enter_new_card: Introduce los detalles para una nueva tarjeta + used_saved_card: "Usa una tarjeta guardada:" + or_enter_new_card: "O introduce los detalles de una nueva tarjeta:" + remember_this_card: ¿Recordar esta tarjeta? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/fr_BE.yml b/config/locales/fr_BE.yml index 5635ffe8f3..2bf0ba3f0e 100644 --- a/config/locales/fr_BE.yml +++ b/config/locales/fr_BE.yml @@ -3148,6 +3148,12 @@ fr_BE: used_saved_card: "Utiliser une carte sauvegardée :" or_enter_new_card: "Ou entrez les informations pour utiliser une nouvelle carte :" remember_this_card: Se souvenir de cette carte ? + stripe_sca: + choose_one: En choisir un + enter_new_card: Entrer les informations pour la nouvelle carte + used_saved_card: "Utiliser une carte sauvegardée :" + or_enter_new_card: "Ou entrez les informations pour utiliser une nouvelle carte :" + remember_this_card: Se souvenir de cette carte ? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/it.yml b/config/locales/it.yml index 06aa5e78dc..937e299de2 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -3123,6 +3123,12 @@ it: used_saved_card: "usare la carta salvata" or_enter_new_card: "Oppure, inserire dettagli di una nuova carta" remember_this_card: Ricordare questa Carta? + stripe_sca: + choose_one: Scegli uno + enter_new_card: Inserire dettagli per una nuova carta + used_saved_card: "usare la carta salvata" + or_enter_new_card: "Oppure, inserire dettagli di una nuova carta" + remember_this_card: Ricordare questa Carta? date_picker: format: '%Y-%m-%d' js_format: 'aa-mm-gg' diff --git a/config/locales/nb.yml b/config/locales/nb.yml index 25bfc2674a..7ce4f8d1ff 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -3212,6 +3212,12 @@ nb: used_saved_card: "Bruk et lagret kort:" or_enter_new_card: "Eller skriv inn detaljer for et nytt kort:" remember_this_card: Husk dette kortet? + stripe_sca: + choose_one: Velg en + enter_new_card: Skriv inn detaljer for et nytt kort + used_saved_card: "Bruk et lagret kort:" + or_enter_new_card: "Eller skriv inn detaljer for et nytt kort:" + remember_this_card: Husk dette kortet? date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/nl_BE.yml b/config/locales/nl_BE.yml index a587612b83..21955a3b4b 100644 --- a/config/locales/nl_BE.yml +++ b/config/locales/nl_BE.yml @@ -3053,6 +3053,12 @@ nl_BE: used_saved_card: "Gebruik een opgeslaan kaart : " or_enter_new_card: "Of, voer de gegevens in voor een nieuwe kaart : " remember_this_card: 'Deze kaart onthouden? ' + stripe_sca: + choose_one: 'Kies één ' + enter_new_card: Voer de gegevens in voor een nieuwe kaart + used_saved_card: "Gebruik een opgeslaan kaart : " + or_enter_new_card: "Of, voer de gegevens in voor een nieuwe kaart : " + remember_this_card: 'Deze kaart onthouden? ' date_picker: format: '%Y-%m-%d' js_format: 'yy-mm-dd' diff --git a/config/locales/pt.yml b/config/locales/pt.yml index ec4deb12f5..de852adb84 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -2984,6 +2984,12 @@ pt: used_saved_card: "Usar um cartão guardado:" or_enter_new_card: "Ou, introduza detalhes para um novo cartão:" remember_this_card: Lembra-se deste cartão? + stripe_sca: + choose_one: Escolha um + enter_new_card: Inserir detalhes de novo cartão + used_saved_card: "Usar um cartão guardado:" + or_enter_new_card: "Ou, introduza detalhes para um novo cartão:" + remember_this_card: Lembra-se deste cartão? date_picker: format: '%Y-%m-%d' js_format: 'aa-mm-dd' diff --git a/config/locales/tr.yml b/config/locales/tr.yml index f07152f282..4c39795e6d 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1305,7 +1305,7 @@ tr: cookie_stripe_desc: "Sahtekarlık tespiti için ödeme işlemcimiz Stripe tarafından toplanan veriler https://stripe.com/cookies-policy/legal. Tüm mağazalar Stripe'ı ödeme yöntemi olarak kullanmaz, ancak dolandırıcılığın tüm sayfalara uygulanmasını önlemek iyi bir yöntemdir. Stripe muhtemelen sayfalarımızdan hangilerinin genellikle API'leriyle etkileşime girdiğini gösteren bir resim oluşturur ve daha sonra olağandışı bir şeyi işaretler. Dolayısıyla, Stripe çerezinin ayarlanması, kullanıcıya ödeme yönteminin sağlanmasından daha geniş bir işleve sahiptir. Kaldırılması hizmetin güvenliğini etkileyebilir. Stripe hakkında daha fazla bilgi edinebilir ve gizlilik politikasını https://stripe.com/privacy adresinde okuyabilirsiniz." statistics_cookies: "İstatistik Çerezleri" statistics_cookies_desc: "Aşağıdakiler mecburi değildir, ancak kullanıcı davranışını analiz etmemize, en çok kullandığınız veya kullanmadığınız özellikleri belirlememize veya kullanıcı deneyimi sorunlarını anlamamıza vb. izin vererek size en iyi kullanıcı deneyimini sunmaya yardımcı olur." - statistics_cookies_analytics_desc_html: "Platform kullanım verilerini toplamak ve analiz etmek için, Google Analytics'i Spree ( temelde oluşturduğumuz e-ticaret açık kaynak yazılımı) ile bağlantılı varsayılan hizmet olduğu için kullanıyoruz, ancak vizyonumuz Matomo'ya (eski Piwik, açık kaynak analitiği) geçmek GDPR uyumlu ve gizliliğinizi koruyan bir araç)." + statistics_cookies_analytics_desc_html: "Platform kullanım verilerini toplamak ve analiz etmek için, Google Analytics'i Spree ( temelde oluşturduğumuz e-ticaret açık kaynak yazılımı) ile bağlantılı varsayılan hizmet olduğu için kullanıyoruz, ancak vizyonumuz Matomoya (eski Piwik, açık kaynak analitiği) geçmek GDPR uyumlu ve gizliliğinizi koruyan bir araç)." statistics_cookies_matomo_desc_html: "Platform kullanım verilerini toplamak ve analiz etmek için, GDPR uyumlu ve gizliliğinizi koruyan açık kaynaklı bir analiz aracı olan Matomo (eski Piwik) kullanıyoruz." statistics_cookies_matomo_optout: "Matomo analitiğinden çıkmak istiyor musunuz? Herhangi bir kişisel veri toplamıyoruz ve Matomo hizmetimizi geliştirmemize yardımcı oluyor, ancak seçiminize saygı duyuyoruz :-)" cookie_analytics_utma_desc: "Kullanıcıları ve oturumları ayırt etmek için kullanılır. Çerez, javascript kütüphanesi yürütüldüğünde ve mevcut __utma çerezleri olmadığında oluşturulur. Çerez, Google Analytics'e her veri gönderildiğinde güncellenir." @@ -3209,6 +3209,12 @@ tr: used_saved_card: "Kayıtlı bir kart kullanın:" or_enter_new_card: "Veya yeni bir kart için bilgileri girin:" remember_this_card: Bu kartı hatırlıyor musunuz? + stripe_sca: + choose_one: Birini seçin + enter_new_card: Yeni bir kart için bilgileri girin + used_saved_card: "Kayıtlı bir kart kullanın:" + or_enter_new_card: "Veya yeni bir kart için bilgileri girin:" + remember_this_card: Bu kartı hatırlıyor musunuz? date_picker: format: '% Y-% A-%d' js_format: 'yy-aa-gg' From 7c3a0a292f88b0496a29100e20e5c5d4b5581181 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2020 19:31:47 +0000 Subject: [PATCH 84/97] Bump ddtrace from 0.32.0 to 0.33.0 Bumps [ddtrace](https://github.com/DataDog/dd-trace-rb) from 0.32.0 to 0.33.0. - [Release notes](https://github.com/DataDog/dd-trace-rb/releases) - [Changelog](https://github.com/DataDog/dd-trace-rb/blob/master/CHANGELOG.md) - [Commits](https://github.com/DataDog/dd-trace-rb/compare/v0.32.0...v0.33.0) Signed-off-by: dependabot-preview[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2bf97f7891..70933c50f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -211,7 +211,7 @@ GEM activerecord (>= 3.2.0, < 5.0) fog (~> 1.0) rails (>= 3.2.0, < 5.0) - ddtrace (0.32.0) + ddtrace (0.33.0) msgpack debugger-linecache (1.2.0) deface (1.0.2) @@ -467,7 +467,7 @@ GEM railties (>= 3.1) money (5.1.1) i18n (~> 0.6.0) - msgpack (1.3.1) + msgpack (1.3.3) multi_json (1.14.1) multi_xml (0.6.0) multipart-post (2.1.1) From 1e76f3f744190f30af29f705cba5fddd03ba548a Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Sun, 8 Mar 2020 01:20:57 +1100 Subject: [PATCH 85/97] Updating translations for config/locales/fr.yml --- config/locales/fr.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index a261ee007d..6385ec2c37 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -2221,7 +2221,7 @@ fr: no_change_to_save: "Pas de changement à sauvegarder" user_invited: "%{email}a été invité à gérer cette entreprise" add_manager: "Ajouter un utilisateur existant" - users: "Utilisateurs" + users: "Gestionnaires" about: "A propos" images: "Images" web: "Web" @@ -2278,6 +2278,7 @@ fr: enterprise_register_success_notice: "Bravo ! L'entreprise %{enterprise} est maintenant inscrite sur Open Food France :-)" enterprise_bulk_update_success_notice: "Entreprises mises à jour avec succès" enterprise_bulk_update_error: 'Echec dans la mise à jour' + enterprise_shop_show_error: "La boutique que vous recherchez n'existe pas ou est inactive. Veuillez sélectionner une boutique depuis la liste ci-dessous." order_cycles_create_notice: 'Votre cycle de vente a été créé.' order_cycles_update_notice: 'Votre cycle de vente a été mis à jour.' order_cycles_bulk_update_notice: 'Des cycles de vente ont été mis à jour.' From 0d02b2afcfc780fc62ed6efed5c1f20984f475be Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Sun, 8 Mar 2020 01:21:12 +1100 Subject: [PATCH 86/97] Updating translations for config/locales/en_FR.yml --- config/locales/en_FR.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index a19db5ae4a..b37822f390 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -2274,6 +2274,7 @@ en_FR: enterprise_register_success_notice: "Congratulations! Registration for %{enterprise} is complete!" enterprise_bulk_update_success_notice: "Enterprises updated successfully" enterprise_bulk_update_error: 'Update failed' + enterprise_shop_show_error: "The shop you are looking for doesn't exist or is inactive on OFN. Please check other shops." order_cycles_create_notice: 'Your order cycle has been created.' order_cycles_update_notice: 'Your order cycle has been updated.' order_cycles_bulk_update_notice: 'Order cycles have been updated.' From 7585e3d1d6e7e1648b05abfc54e6b8367563e2ee Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 10 Mar 2020 18:40:46 +0000 Subject: [PATCH 88/97] The variants_to_a method was dead but actually we can use it to make the code simpler --- lib/open_food_network/order_cycle_form_applicator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/open_food_network/order_cycle_form_applicator.rb b/lib/open_food_network/order_cycle_form_applicator.rb index 611f120f62..8f9c4308d8 100644 --- a/lib/open_food_network/order_cycle_form_applicator.rb +++ b/lib/open_food_network/order_cycle_form_applicator.rb @@ -149,7 +149,7 @@ module OpenFoodNetwork receiver = @order_cycle.coordinator exchange = find_exchange(sender.id, receiver.id, true) - requested_ids = attrs[:variants].select{ |_k, v| v }.keys.map(&:to_i) # Only the ids the user has requested + requested_ids = variants_to_a(attrs[:variants]) # Only the ids the user has requested existing_ids = exchange.present? ? exchange.variants.pluck(:id) : [] # The ids that already exist editable_ids = editable_variant_ids_for_incoming_exchange_between(sender, receiver) # The ids we are allowed to add/remove @@ -166,7 +166,7 @@ module OpenFoodNetwork receiver = Enterprise.find(attrs[:enterprise_id]) exchange = find_exchange(sender.id, receiver.id, false) - requested_ids = attrs[:variants].select{ |_k, v| v }.keys.map(&:to_i) # Only the ids the user has requested + requested_ids = variants_to_a(attrs[:variants]) # Only the ids the user has requested existing_ids = exchange.present? ? exchange.variants.pluck(:id) : [] # The ids that already exist editable_ids = editable_variant_ids_for_outgoing_exchange_between(sender, receiver) # The ids we are allowed to add/remove @@ -184,7 +184,7 @@ module OpenFoodNetwork end def variants_to_a(variants) - variants.select { |_k, v| v }.keys.map(&:to_i).sort + variants.select { |_k, v| v }.keys.map(&:to_i) end end end From 03246d425d02b4fa843aac24ca91c1feab70c6a3 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Tue, 10 Mar 2020 18:41:26 +0000 Subject: [PATCH 89/97] Make this method handle the case where the variants hash passed is nil This fixes a spec in the rails 4 branch --- lib/open_food_network/order_cycle_form_applicator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/open_food_network/order_cycle_form_applicator.rb b/lib/open_food_network/order_cycle_form_applicator.rb index 8f9c4308d8..1abf16f89c 100644 --- a/lib/open_food_network/order_cycle_form_applicator.rb +++ b/lib/open_food_network/order_cycle_form_applicator.rb @@ -184,6 +184,8 @@ module OpenFoodNetwork end def variants_to_a(variants) + return [] unless variants + variants.select { |_k, v| v }.keys.map(&:to_i) end end From c83bded763d1a846599de89d092d5827856ddad1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2020 19:19:31 +0000 Subject: [PATCH 90/97] Bump ddtrace from 0.33.0 to 0.33.1 Bumps [ddtrace](https://github.com/DataDog/dd-trace-rb) from 0.33.0 to 0.33.1. - [Release notes](https://github.com/DataDog/dd-trace-rb/releases) - [Changelog](https://github.com/DataDog/dd-trace-rb/blob/master/CHANGELOG.md) - [Commits](https://github.com/DataDog/dd-trace-rb/compare/v0.33.0...v0.33.1) Signed-off-by: dependabot-preview[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 70933c50f5..fb8f877dd9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -211,7 +211,7 @@ GEM activerecord (>= 3.2.0, < 5.0) fog (~> 1.0) rails (>= 3.2.0, < 5.0) - ddtrace (0.33.0) + ddtrace (0.33.1) msgpack debugger-linecache (1.2.0) deface (1.0.2) From 4c7b8209b9a9b7784051e6a3f845fb29f5255203 Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Wed, 11 Mar 2020 19:49:14 +1100 Subject: [PATCH 91/97] Updating translations for config/locales/nb.yml --- config/locales/nb.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/nb.yml b/config/locales/nb.yml index 7ce4f8d1ff..1207b53957 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -2274,6 +2274,7 @@ nb: enterprise_register_success_notice: "Gratulerer! Registrering for %{enterprise} er fullført!" enterprise_bulk_update_success_notice: "Bedrifter oppdatert" enterprise_bulk_update_error: 'Oppdatering mislyktes' + enterprise_shop_show_error: "Butikken du leter etter eksisterer ikke eller er inaktiv på OFN. Sjekk gjerne ut andre butikker." order_cycles_create_notice: 'Din bestillingsrunde er opprettet.' order_cycles_update_notice: 'Din bestillingsrunde har blitt oppdatert.' order_cycles_bulk_update_notice: 'Bestillingsrundene er oppdatert.' From 933b5f16069923f5d2a24a36d5dc63e8acb4229d Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Wed, 11 Mar 2020 15:31:25 +0100 Subject: [PATCH 92/97] Fix reloading issue in dev environment I constantly get `NameError: uninitialized constant Spree::AuthenticationHelpers` when touching local files and then reloading a page, and have to restart my rails server every time (in development). I read the other day that this is the best way to fix the issue, and it seems to work... --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5b67897fab..1a841eae27 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,5 @@ require 'open_food_network/referer_parser' -require 'spree/authentication_helpers' +require_dependency 'spree/authentication_helpers' class ApplicationController < ActionController::Base protect_from_forgery From 633f1bd7cf20a380ae0483e55605c04b0b6bfa80 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2020 19:15:51 +0000 Subject: [PATCH 93/97] Bump rspec-rails from 3.9.0 to 3.9.1 Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 3.9.0 to 3.9.1. - [Release notes](https://github.com/rspec/rspec-rails/releases) - [Changelog](https://github.com/rspec/rspec-rails/blob/master/Changelog.md) - [Commits](https://github.com/rspec/rspec-rails/compare/v3.9.0...v3.9.1) Signed-off-by: dependabot-preview[bot] --- Gemfile.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 70933c50f5..a76e77889b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -516,7 +516,7 @@ GEM rabl (0.8.4) activesupport (>= 2.3.14) rack (1.4.7) - rack-cache (1.9.0) + rack-cache (1.11.0) rack (>= 0.4) rack-mini-profiler (1.1.6) rack (>= 1.2.0) @@ -548,7 +548,7 @@ GEM thor (>= 0.14.6, < 2.0) rainbow (3.0.0) raindrops (0.19.1) - rake (13.0.0) + rake (13.0.1) ransack (0.7.2) actionpack (~> 3.0) activerecord (~> 3.0) @@ -580,15 +580,15 @@ GEM rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) rspec-mocks (~> 3.9.0) - rspec-core (3.9.0) - rspec-support (~> 3.9.0) + rspec-core (3.9.1) + rspec-support (~> 3.9.1) rspec-expectations (3.9.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) - rspec-mocks (3.9.0) + rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) - rspec-rails (3.9.0) + rspec-rails (3.9.1) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) @@ -598,7 +598,7 @@ GEM rspec-support (~> 3.9.0) rspec-retry (0.6.2) rspec-core (> 3.3) - rspec-support (3.9.0) + rspec-support (3.9.2) rubocop (0.80.1) jaro_winkler (~> 1.5.1) parallel (~> 1.10) From 8eb60388fd38f1a940f259b626347c2a14008e62 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2020 19:19:24 +0000 Subject: [PATCH 94/97] Bump rack-mini-profiler from 1.1.6 to 2.0.0 Bumps [rack-mini-profiler](https://github.com/MiniProfiler/rack-mini-profiler) from 1.1.6 to 2.0.0. - [Release notes](https://github.com/MiniProfiler/rack-mini-profiler/releases) - [Changelog](https://github.com/MiniProfiler/rack-mini-profiler/blob/master/CHANGELOG.md) - [Commits](https://github.com/MiniProfiler/rack-mini-profiler/compare/v1.1.6...v2.0.0) Signed-off-by: dependabot-preview[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 2318f4d7ca..a07f9c26ce 100644 --- a/Gemfile +++ b/Gemfile @@ -166,5 +166,5 @@ group :development do # greater than 1.0.9, so we just required the latest available version here. gem 'eventmachine', '>= 1.2.3' - gem 'rack-mini-profiler', '< 2.0.0' + gem 'rack-mini-profiler', '< 3.0.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 70933c50f5..6be680b1f4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -518,7 +518,7 @@ GEM rack (1.4.7) rack-cache (1.9.0) rack (>= 0.4) - rack-mini-profiler (1.1.6) + rack-mini-profiler (2.0.0) rack (>= 1.2.0) rack-protection (1.5.5) rack @@ -762,7 +762,7 @@ DEPENDENCIES pg (~> 0.21.0) pry-byebug (>= 3.4.3) rabl - rack-mini-profiler (< 2.0.0) + rack-mini-profiler (< 3.0.0) rack-rewrite rack-ssl rails (~> 3.2.22) From d14b5eb46ba2980b9a4f6b4ec43ba5b48f43f61b Mon Sep 17 00:00:00 2001 From: Transifex-Openfoodnetwork Date: Thu, 12 Mar 2020 09:22:44 +1100 Subject: [PATCH 95/97] Updating translations for config/locales/pt_BR.yml --- config/locales/pt_BR.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index f048a4d822..a48684cb62 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -2273,6 +2273,7 @@ pt_BR: enterprise_register_success_notice: "Parabéns! O registro para %{enterprise} está completo!" enterprise_bulk_update_success_notice: "Iniciativas atualizadas com sucesso" enterprise_bulk_update_error: 'Atualização falhou' + enterprise_shop_show_error: "A loja que você está procurando não existe na OFN ou está inativa. Por favor, busque por outras lojas. " order_cycles_create_notice: 'Seu ciclo de pedidos foi criado.' order_cycles_update_notice: 'Seu ciclo de pedidos foi atualizado.' order_cycles_bulk_update_notice: 'Ciclos de pedidos foram atualizados.' From 8ccc8dfaf6920547a012ce6c1b3f96bfedbe7b0b Mon Sep 17 00:00:00 2001 From: Pau Perez Date: Thu, 12 Mar 2020 13:33:42 +0100 Subject: [PATCH 97/97] Update all locales with the latest Transifex translations --- config/locales/ca.yml | 75 +++++++++++++++++++++++++++++++++++++++++++ config/locales/fr.yml | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 25df51631d..7ea493176c 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1285,6 +1285,7 @@ ca: saving_credit_card: Desant la targeta de crèdit... card_has_been_removed: "S'ha eliminat la teva targeta (número: %{number})" card_could_not_be_removed: Ho sentim, no s'ha pogut eliminar la targeta + invalid_credit_card: "Targeta de crèdit no vàlida" ie_warning_headline: "El vostre navegador no està actualitzat :-(" ie_warning_text: "Per obtenir la millor experiència a Open Food Network et recomanem que actualitzis el teu navegador:" ie_warning_chrome: Descarrega Chrome @@ -2276,6 +2277,7 @@ ca: enterprise_register_success_notice: "Enhorabona! El registre de %{enterprise} s'ha completat!" enterprise_bulk_update_success_notice: "Les organitzacions s'han actualitzat correctament" enterprise_bulk_update_error: 'No s''ha pogut actualitzar' + enterprise_shop_show_error: "La botiga que busqueu no existeix o està inactiva a OFN. Consulteu altres botigues." order_cycles_create_notice: 'S''ha creat el cicle de comanda.' order_cycles_update_notice: 'S''ha actualitzat el cicle de comanda.' order_cycles_bulk_update_notice: 'S''han actualitzat els cicles de comanda.' @@ -2430,6 +2432,12 @@ ca: severity: Severitat description: Descripció resolve: Resoldre + exchange_products: + load_more_variants: "Carregueu més variants" + load_all_variants: "Carregueu totes les variants" + select_all_variants: "Seleccioneu totes les %{total_number_of_variants} variants" + variants_loaded: "%{num_of_variants_loaded} de %{total_number_of_variants} variants carregades" + loading_variants: "Carregant variants" tag_rules: shipping_method_tagged_top: "Els mètodes d'enviament etiquetats" shipping_method_tagged_bottom: "son:" @@ -2588,6 +2596,73 @@ ca: signup_or_login: "Comenceu registrant-vos (o iniciant sessió)" have_an_account: "Ja tens un compte?" action_login: "Inicia la sessió ara." + inflections: + each: + one: "cadascun" + other: "cadascun" + bunch: + one: "munt" + other: "grapats" + pack: + one: "paquet" + other: "paquets" + box: + one: "Caixa" + other: "caixes" + bottle: + one: "ampolla" + other: "ampolles" + jar: + one: "gerro" + other: "pots" + head: + one: "cap" + other: "caps" + bag: + one: "bossa" + other: "bosses" + loaf: + one: "pa" + other: "barres" + single: + one: "solter" + other: "únics" + tub: + one: "tina" + other: "cubells" + item: + one: "article" + other: "articles" + dozen: + one: "dotzena" + other: "dotzenes" + unit: + one: "unitat" + other: "unitats" + serve: + one: "servir" + other: "porcions" + tray: + one: "safata" + other: "safates" + piece: + one: "peça" + other: "peces" + pot: + one: "pot" + other: "pots" + bundle: + one: "paquet" + other: "paquets" + flask: + one: "matràs" + other: "ampolleta" + basket: + one: "cistella" + other: "cistelles" + sack: + one: "sac" + other: "sacs" producers: signup: start_free_profile: "Comença amb un perfil gratuït i amplia'l quan estiguis preparada." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6385ec2c37..afb17379ed 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -701,7 +701,7 @@ fr: enable_subscriptions_tip: "Activer la fonction abonnements?" enable_subscriptions_false: "Désactivé" enable_subscriptions_true: "Activé" - shopfront_message: "Message d'accueil boutique ouverte" + shopfront_message: "Message d'accueil" shopfront_message_placeholder: > Vous pouvez indiquer ici un message de bienvenue ou un message expliquant les particularités de votre boutique. Ce message s'affiche dans l'onglet