Upgrade old StripeConnect payment methods to SCA

StripeConnect has been replaced by StripeSCA. But we still have some
StripeConnect payment methods in our production databases. We need to
convert them to StripeSCA methods before we can remove the related
code and clean up. Otherwise our code would raise errors when
encountering an old StripeConnect method, not knowing what that is.
This commit is contained in:
Maikel Linke
2022-01-20 17:04:26 +11:00
parent 205fb630cb
commit 9abacf9047
3 changed files with 153 additions and 1 deletions

View File

@@ -0,0 +1,57 @@
class ConvertStripeConnectToStripeSca < ActiveRecord::Migration[6.1]
class SpreePreference < ActiveRecord::Base
scope :leftover_from_payment_type, ->(class_name) {
joins <<~SQL
JOIN spree_payment_methods
ON spree_preferences.key =
CONCAT('/#{class_name.underscore}/enterprise_id/', spree_payment_methods.id)
AND spree_payment_methods.type != '#{class_name}'
SQL
}
end
def up
delete_outdated_spree_preferences
upgrade_stripe_payment_methods
update_payment_method_preferences
end
private
# When changing the type of a payment method, we leave orphaned records in
# the spree_preferences table. The key of the preference contains the type
# of the payment method and therefore changing the type disconnects the
# preference.
#
# Here we delete orphaned preferences first so that we don't have any
# conflicts later when updating preferences alongside the connected
# payment methods.
def delete_outdated_spree_preferences
outdated_keys =
SpreePreference.leftover_from_payment_type("Spree::Gateway::StripeConnect").pluck(:key) +
SpreePreference.leftover_from_payment_type("Spree::Gateway::StripeSCA").pluck(:key)
SpreePreference.where(key: outdated_keys).delete_all
# Spree preferences are cached and we want to avoid reading old values.
# Danger: The cache may alter the given array in place. Make sure to
# not use the `outdated_keys` variable after this call.
Rails.cache.delete_multi(outdated_keys)
end
def upgrade_stripe_payment_methods
execute <<~SQL
UPDATE spree_payment_methods
SET type = 'Spree::Gateway::StripeSCA'
WHERE type = 'Spree::Gateway::StripeConnect'
SQL
end
def update_payment_method_preferences
execute <<~SQL
UPDATE spree_preferences
SET key = replace(key, 'stripe_connect', 'stripe_sca')
WHERE key LIKE '/spree/gateway/stripe_connect/%'
SQL
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_01_14_110920) do
ActiveRecord::Schema.define(version: 2022_01_18_053107) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

View File

@@ -0,0 +1,95 @@
# frozen_string_literal: true
require 'spec_helper'
require_relative '../../db/migrate/20220118053107_convert_stripe_connect_to_stripe_sca'
describe ConvertStripeConnectToStripeSca do
let(:owner) { create(:distributor_enterprise) }
let(:new_owner) { create(:distributor_enterprise) }
let(:old_stripe_connect) {
Spree::Gateway::StripeConnect.create!(
name: "Stripe",
environment: "test",
preferred_enterprise_id: owner.id,
distributor_ids: [owner.id]
)
}
let(:result) { Spree::PaymentMethod.find(old_stripe_connect.id) }
before do
# Activate the cache because it's deactivated in test environment.
allow(Spree::Preferences::Store.instance).to receive(:should_persist?).and_return(true)
# Create the payment method after cache activation to store the owner.
old_stripe_connect
end
it "converts payment methods" do
subject.up
expect(result.class).to eq Spree::Gateway::StripeSCA
end
it "keeps attributes" do
subject.up
expect(result.name).to eq "Stripe"
expect(result.environment).to eq "test"
expect(result.distributor_ids).to eq [owner.id]
end
it "keeps Spree preferences" do
subject.up
expect(result.preferred_enterprise_id).to eq owner.id
end
it "doesn't move outdated StripeConnect preferences to StripeSCA methods" do
# When you change the type of a payment method in the admin screen
# it leaves old entries in the spree_preferences table.
# Here is a simulation of such a change:
old_stripe_connect.update_columns(type: "Spree::Gateway::StripeSCA")
changed_method = Spree::PaymentMethod.find(old_stripe_connect.id)
changed_method.preferred_enterprise_id = new_owner.id
subject.up
expect(result.preferred_enterprise_id).to eq new_owner.id
end
it "keeps Spree preferences despite conflicting preference keys" do
# We change the payment method to StripeSCA and then back to StripeConnect
# to generate a conflicting preference. We want to keep the preference
# of the current payment method, not the intermediately changed one.
old_stripe_connect.update_columns(type: "Spree::Gateway::StripeSCA")
changed_method = Spree::PaymentMethod.find(old_stripe_connect.id)
changed_method.preferred_enterprise_id = owner.id
old_stripe_connect.update_columns(type: "Spree::Gateway::StripeConnect")
old_stripe_connect.preferred_enterprise_id = new_owner.id
subject.up
expect(result.preferred_enterprise_id).to eq new_owner.id
end
it "doesn't mess with new Stripe payment methods" do
stripe = Spree::Gateway::StripeSCA.create!(
name: "Modern Stripe",
environment: "test",
preferred_enterprise_id: owner.id,
distributor_ids: [owner.id]
)
expect { subject.up }.to_not change { stripe.reload.attributes }
end
it "doesn't mess with other payment methods" do
cash = Spree::PaymentMethod::Check.create!(
name: "Cash on delivery",
environment: "test",
distributor_ids: [owner.id]
)
expect { subject.up }.to_not change { cash.reload.attributes }
end
end