From 3870668a5967654c2e70612215a4d9d7d58a0507 Mon Sep 17 00:00:00 2001 From: Brian Christian Date: Fri, 7 Aug 2020 12:27:37 -0700 Subject: [PATCH 1/3] Add specs for Stripe customer accounts These specs address the need in #26 for maintaining a stable Stripe customer object across multiple credit cards. Currently V2 and V3 Elements are supported, with V3 Intents functionality remaining as a TODO for a later PR. --- Gemfile | 4 + spec/features/stripe_customer_spec.rb | 176 ++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 spec/features/stripe_customer_spec.rb diff --git a/Gemfile b/Gemfile index 24c83d8f..022ace4f 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,10 @@ else gem 'sqlite3' end +group :development, :test do + gem 'stripe' +end + gemspec # Use a local Gemfile to include development dependencies that might not be diff --git a/spec/features/stripe_customer_spec.rb b/spec/features/stripe_customer_spec.rb new file mode 100644 index 00000000..08645fc1 --- /dev/null +++ b/spec/features/stripe_customer_spec.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'stripe' +Stripe.api_key = "sk_test_VCZnDv3GLU15TRvn8i2EsaAN" + +RSpec.describe "Stripe checkout", type: :feature do + let(:zone) { FactoryBot.create(:zone) } + let(:country) { FactoryBot.create(:country) } + + before do + FactoryBot.create(:store) + zone.members << Spree::ZoneMember.create!(zoneable: country) + FactoryBot.create(:free_shipping_method) + + Spree::PaymentMethod::StripeCreditCard.create!( + name: "Stripe", + preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN", + preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg", + preferred_v3_elements: preferred_v3_elements, + preferred_v3_intents: preferred_v3_intents + ) + + FactoryBot.create(:product, name: "DL-44") + + visit spree.root_path + click_link "DL-44" + click_button "Add To Cart" + + expect(page).to have_current_path("/cart") + click_button "Checkout" + + expect(page).to have_current_path("/checkout/registration") + click_link "Create a new account" + within("#new_spree_user") do + fill_in "Email", with: "mary@example.com" + fill_in "Password", with: "superStrongPassword" + fill_in "Password Confirmation", with: "superStrongPassword" + end + click_button "Create" + + # Address + expect(page).to have_current_path("/checkout/address") + + within("#billing") do + fill_in_name + fill_in "Street Address", with: "YT-1300" + fill_in "City", with: "Mos Eisley" + select "United States of America", from: "Country" + select country.states.first.name, from: "order_bill_address_attributes_state_id" + fill_in "Zip", with: "12010" + fill_in "Phone", with: "(555) 555-5555" + end + click_on "Save and Continue" + + # Delivery + expect(page).to have_current_path("/checkout/delivery") + expect(page).to have_content("UPS Ground") + click_on "Save and Continue" + + # Payment + expect(page).to have_current_path("/checkout/payment") + fill_in_card + click_button "Save and Continue" + + # Confirmation + expect(page).to have_current_path("/checkout/confirm") + click_button "Place Order" + expect(page).to have_content("Your order has been processed successfully") + + # Begin Second Purchase + visit spree.root_path + click_link "DL-44" + click_button "Add To Cart" + + expect(page).to have_current_path("/cart") + click_button "Checkout" + + # Address + expect(page).to have_current_path("/checkout/address") + + within("#billing") do + fill_in_name + fill_in "Street Address", with: "YT-1300" + fill_in "City", with: "Mos Eisley" + select "United States of America", from: "Country" + select country.states.first.name, from: "order_bill_address_attributes_state_id" + fill_in "Zip", with: "12010" + fill_in "Phone", with: "(555) 555-5555" + end + click_on "Save and Continue" + + # Delivery + expect(page).to have_current_path("/checkout/delivery") + expect(page).to have_content("UPS Ground") + click_on "Save and Continue" + + # Payment + expect(page).to have_current_path("/checkout/payment") + end + + shared_examples "Maintain Consistent Stripe Customer Across Purchases" do + it "can re-use saved cards and maintain the same Stripe payment ID and customer ID", js: true do + + choose "Use an existing card on file" + click_button "Save and Continue" + + # Confirm + expect(page).to have_current_path("/checkout/confirm") + click_button "Place Order" + expect(page).to have_content("Your order has been processed successfully") + + user = Spree::User.find_by_email("mary@example.com") + user_sources = user.wallet.wallet_payment_sources + expect(user_sources.size).to eq(1) + + user_card = user_sources.first.payment_source + expect(user_card.gateway_customer_profile_id).to start_with 'cus_' + expect(user_card.gateway_payment_profile_id).to start_with 'card_' + + stripe_customer = Stripe::Customer.retrieve(user_card.gateway_customer_profile_id) + expect(stripe_customer[:email]).to eq(user.email) + expect(stripe_customer[:sources][:total_count]).to eq(1) + expect(stripe_customer[:sources][:data].first[:customer]).to eq(user_card.gateway_customer_profile_id) + expect(stripe_customer[:sources][:data].first[:id]).to eq(user_card.gateway_payment_profile_id) + + expect(user.orders.map { |o| o.payments.valid.first.source.gateway_payment_profile_id }.uniq.size).to eq(1) + expect(user.orders.map { |o| o.payments.valid.first.source.gateway_customer_profile_id }.uniq.size).to eq(1) + end + + it "can use a new card and maintain the same Stripe customer ID", js: true do + + choose "Use a new card / payment method" + fill_in_card({ number: '5555 5555 5555 4444' }) + click_button "Save and Continue" + + # Confirm + expect(page).to have_current_path("/checkout/confirm") + + user = Spree::User.find_by_email("mary@example.com") + user_cards = user.credit_cards + expect(user_cards.size).to eq(2) + expect(user_cards.pluck(:gateway_customer_profile_id)).to all( start_with 'cus_' ) + expect(user_cards.pluck(:gateway_payment_profile_id)).to all( start_with 'card_' ) + expect(user_cards.last.gateway_customer_profile_id).to eq(user_cards.first.gateway_customer_profile_id) + expect(user_cards.pluck(:gateway_customer_profile_id).uniq.size).to eq(1) + + click_button "Place Order" + expect(page).to have_content("Your order has been processed successfully") + + expect(user.wallet.wallet_payment_sources.size).to eq(2) + expect(user.orders.map { |o| o.payments.valid.first.source.gateway_payment_profile_id }.uniq.size).to eq(2) + expect(user.orders.map { |o| o.payments.valid.first.source.gateway_customer_profile_id }.uniq.size).to eq(1) + + stripe_customer = Stripe::Customer.retrieve(user_cards.last.gateway_customer_profile_id) + stripe_customer_cards = Stripe::PaymentMethod.list({ customer: stripe_customer.id, type: 'card' }) + expect(stripe_customer_cards.count).to eq(2) + expect(stripe_customer_cards.data.map { |card| card.id }).to match_array(user.orders.map { |o| o.payments.valid.first.source.gateway_payment_profile_id }.uniq) + expect(stripe_customer_cards.data.map { |card| card.id }).to match_array(user_cards.pluck(:gateway_payment_profile_id)) + end + end + + context 'when using Stripe V2 API library' do + let(:preferred_v3_elements) { false } + let(:preferred_v3_intents) { false } + + it_behaves_like "Maintain Consistent Stripe Customer Across Purchases" + end + + context 'when using Stripe V3 API library with Elements' do + let(:preferred_v3_elements) { true } + let(:preferred_v3_intents) { false } + + it_behaves_like "Maintain Consistent Stripe Customer Across Purchases" + end +end From 41a285248002ac3381ca70baa403ba8f7f0906e2 Mon Sep 17 00:00:00 2001 From: Brian Christian Date: Fri, 7 Aug 2020 12:31:32 -0700 Subject: [PATCH 2/3] Keep consistent Stripe customer in V2 and V3 Elements Avoid creating a new Stripe customer during checkout in both V2 and V3 Elements by checking to see whether this Solidus user has a previous Stripe payment source. Support for V3 Intents will require further work, and will be included in a subsequent PR. --- .../spree/payment_method/stripe_credit_card.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/models/spree/payment_method/stripe_credit_card.rb b/app/models/spree/payment_method/stripe_credit_card.rb index fb707580..787c10ee 100644 --- a/app/models/spree/payment_method/stripe_credit_card.rb +++ b/app/models/spree/payment_method/stripe_credit_card.rb @@ -122,6 +122,14 @@ def create_profile(payment) creditcard = source end + user_stripe_payment_sources = payment.source.user&.wallet&.wallet_payment_sources&.select do |wps| + wps.payment_source.payment_method.type == self.class.name + end + if user_stripe_payment_sources.present? + customer_id = user_stripe_payment_sources.map {|ps| ps.payment_source&.gateway_customer_profile_id }.compact.last + options[:customer] = customer_id + end + response = gateway.store(creditcard, options) if response.success? if v3_intents? @@ -133,8 +141,8 @@ def create_profile(payment) else payment.source.update!( 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'] + gateway_customer_profile_id: options[:customer] ? response.params['customer'] : response.params['id'], + gateway_payment_profile_id: options[:customer] ? response.params['id'] : response.params['default_source'] || response.params['default_card'] ) end else From be15193f11b58dcb25844bc6b99ce519005f7954 Mon Sep 17 00:00:00 2001 From: Brian Christian Date: Mon, 26 Oct 2020 21:10:05 -0700 Subject: [PATCH 3/3] Dedupe shared code in checkout specs --- .../testing_support/stripe_checkout_helper.rb | 57 +++++++++++++++++++ spec/features/stripe_checkout_spec.rb | 52 +---------------- spec/features/stripe_customer_spec.rb | 51 +---------------- spec/spec_helper.rb | 4 +- 4 files changed, 62 insertions(+), 102 deletions(-) create mode 100644 lib/solidus_stripe/testing_support/stripe_checkout_helper.rb diff --git a/lib/solidus_stripe/testing_support/stripe_checkout_helper.rb b/lib/solidus_stripe/testing_support/stripe_checkout_helper.rb new file mode 100644 index 00000000..c48707ae --- /dev/null +++ b/lib/solidus_stripe/testing_support/stripe_checkout_helper.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module StripeCheckoutHelper + def initialize_checkout + FactoryBot.create(:store) + zone.members << Spree::ZoneMember.create!(zoneable: country) + FactoryBot.create(:free_shipping_method) + + Spree::PaymentMethod::StripeCreditCard.create!( + name: "Stripe", + preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN", + preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg", + preferred_v3_elements: preferred_v3_elements, + preferred_v3_intents: preferred_v3_intents + ) + + FactoryBot.create(:product, name: "DL-44") + + visit spree.root_path + click_link "DL-44" + click_button "Add To Cart" + + expect(page).to have_current_path("/cart") + click_button "Checkout" + + expect(page).to have_current_path("/checkout/registration") + click_link "Create a new account" + within("#new_spree_user") do + fill_in "Email", with: "mary@example.com" + fill_in "Password", with: "superStrongPassword" + fill_in "Password Confirmation", with: "superStrongPassword" + end + click_button "Create" + + # Address + expect(page).to have_current_path("/checkout/address") + + within("#billing") do + fill_in_name + fill_in "Street Address", with: "YT-1300" + fill_in "City", with: "Mos Eisley" + select "United States of America", from: "Country" + select country.states.first.name, from: "order_bill_address_attributes_state_id" + fill_in "Zip", with: "12010" + fill_in "Phone", with: "(555) 555-5555" + end + click_on "Save and Continue" + + # Delivery + expect(page).to have_current_path("/checkout/delivery") + expect(page).to have_content("UPS Ground") + click_on "Save and Continue" + + # Payment + expect(page).to have_current_path("/checkout/payment") + end +end diff --git a/spec/features/stripe_checkout_spec.rb b/spec/features/stripe_checkout_spec.rb index c43f02b0..f39dc20c 100644 --- a/spec/features/stripe_checkout_spec.rb +++ b/spec/features/stripe_checkout_spec.rb @@ -9,57 +9,7 @@ let(:card_3d_secure) { "4000 0025 0000 3155" } before do - FactoryBot.create(:store) - zone.members << Spree::ZoneMember.create!(zoneable: country) - FactoryBot.create(:free_shipping_method) - - Spree::PaymentMethod::StripeCreditCard.create!( - name: "Stripe", - preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN", - preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg", - preferred_v3_elements: preferred_v3_elements, - preferred_v3_intents: preferred_v3_intents - ) - - FactoryBot.create(:product, name: "DL-44") - - visit spree.root_path - click_link "DL-44" - click_button "Add To Cart" - - expect(page).to have_current_path("/cart") - click_button "Checkout" - - expect(page).to have_current_path("/checkout/registration") - click_link "Create a new account" - within("#new_spree_user") do - fill_in "Email", with: "mary@example.com" - fill_in "Password", with: "superStrongPassword" - fill_in "Password Confirmation", with: "superStrongPassword" - end - click_button "Create" - - # Address - expect(page).to have_current_path("/checkout/address") - - within("#billing") do - fill_in_name - fill_in "Street Address", with: "YT-1300" - fill_in "City", with: "Mos Eisley" - select "United States of America", from: "Country" - select country.states.first.name, from: "order_bill_address_attributes_state_id" - fill_in "Zip", with: "12010" - fill_in "Phone", with: "(555) 555-5555" - end - click_on "Save and Continue" - - # Delivery - expect(page).to have_current_path("/checkout/delivery") - expect(page).to have_content("UPS Ground") - click_on "Save and Continue" - - # Payment - expect(page).to have_current_path("/checkout/payment") + initialize_checkout end # This will fetch a token from Stripe.com and then pass that to the webserver. diff --git a/spec/features/stripe_customer_spec.rb b/spec/features/stripe_customer_spec.rb index 08645fc1..1e3ef484 100644 --- a/spec/features/stripe_customer_spec.rb +++ b/spec/features/stripe_customer_spec.rb @@ -9,57 +9,8 @@ let(:country) { FactoryBot.create(:country) } before do - FactoryBot.create(:store) - zone.members << Spree::ZoneMember.create!(zoneable: country) - FactoryBot.create(:free_shipping_method) + initialize_checkout - Spree::PaymentMethod::StripeCreditCard.create!( - name: "Stripe", - preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN", - preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg", - preferred_v3_elements: preferred_v3_elements, - preferred_v3_intents: preferred_v3_intents - ) - - FactoryBot.create(:product, name: "DL-44") - - visit spree.root_path - click_link "DL-44" - click_button "Add To Cart" - - expect(page).to have_current_path("/cart") - click_button "Checkout" - - expect(page).to have_current_path("/checkout/registration") - click_link "Create a new account" - within("#new_spree_user") do - fill_in "Email", with: "mary@example.com" - fill_in "Password", with: "superStrongPassword" - fill_in "Password Confirmation", with: "superStrongPassword" - end - click_button "Create" - - # Address - expect(page).to have_current_path("/checkout/address") - - within("#billing") do - fill_in_name - fill_in "Street Address", with: "YT-1300" - fill_in "City", with: "Mos Eisley" - select "United States of America", from: "Country" - select country.states.first.name, from: "order_bill_address_attributes_state_id" - fill_in "Zip", with: "12010" - fill_in "Phone", with: "(555) 555-5555" - end - click_on "Save and Continue" - - # Delivery - expect(page).to have_current_path("/checkout/delivery") - expect(page).to have_content("UPS Ground") - click_on "Save and Continue" - - # Payment - expect(page).to have_current_path("/checkout/payment") fill_in_card click_button "Save and Continue" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a466b60c..7a738fef 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -21,8 +21,9 @@ # Requires factories defined in lib/solidus_stripe/testing_support/factories.rb SolidusDevSupport::TestingSupport::Factories.load_for(SolidusStripe::Engine) -# Requires card input helper defined in lib/solidus_stripe/testing_support/card_input_helper.rb +# Requires helpers defined in lib/solidus_stripe/testing_support require 'solidus_stripe/testing_support/card_input_helper' +require 'solidus_stripe/testing_support/stripe_checkout_helper' RSpec.configure do |config| config.infer_spec_type_from_file_location! @@ -30,6 +31,7 @@ config.include SolidusAddressNameHelper, type: :feature config.include SolidusCardInputHelper, type: :feature + config.include StripeCheckoutHelper, type: :feature if Spree.solidus_gem_version < Gem::Version.new('2.11') config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system