mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-11 18:26:50 +00:00
Merge pull request #6050 from Matt-Yorkley/stripe-checkout-spec
Stripe checkout spec
This commit is contained in:
@@ -40,7 +40,8 @@
|
||||
= render "layouts/bugsnag_js"
|
||||
|
||||
- if Spree::Config.stripe_connect_enabled
|
||||
%script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"}
|
||||
= render "shared/stripe_js"
|
||||
|
||||
- if !ContentConfig.open_street_map_enabled
|
||||
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry#{ ENV['GOOGLE_MAPS_API_KEY'] ? '&key=' + ENV['GOOGLE_MAPS_API_KEY'] : ''} "}
|
||||
|
||||
|
||||
5
app/views/shared/_stripe_js.html.haml
Normal file
5
app/views/shared/_stripe_js.html.haml
Normal file
@@ -0,0 +1,5 @@
|
||||
- if Rails.env.test?
|
||||
%script{type: "text/javascript"}
|
||||
= render file: "spec/support/fixtures/stripejs-mock.js"
|
||||
- else
|
||||
%script{src: "https://js.stripe.com/v3/", type: "text/javascript"}
|
||||
@@ -1,6 +1,7 @@
|
||||
-# = render "spree/admin/payments/source_forms/gateway", payment_method: payment_method
|
||||
.stripe
|
||||
%script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"}
|
||||
= render "shared/stripe_js"
|
||||
|
||||
- if Stripe.publishable_key
|
||||
:javascript
|
||||
angular.module('admin.payments').value("stripeObject", Stripe("#{Stripe.publishable_key}"))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.stripe
|
||||
%script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"}
|
||||
= render "shared/stripe_js"
|
||||
|
||||
- if Stripe.publishable_key
|
||||
:javascript
|
||||
angular.module('admin.payments').value("stripeObject", Stripe("#{Stripe.publishable_key}"))
|
||||
|
||||
@@ -34,7 +34,7 @@ module OrderManagement
|
||||
end
|
||||
|
||||
context "when the payment method is a stripe payment method" do
|
||||
let(:payment_method) { create(:stripe_payment_method) }
|
||||
let(:payment_method) { create(:stripe_connect_payment_method) }
|
||||
|
||||
context "and the card is already set (the payment source is a credit card)" do
|
||||
it "returns the pending payment with no change" do
|
||||
|
||||
@@ -704,7 +704,7 @@ describe Admin::SubscriptionsController, type: :controller do
|
||||
end
|
||||
|
||||
context "when other payment methods exist" do
|
||||
let!(:stripe) { create(:stripe_payment_method, distributors: [shop]) }
|
||||
let!(:stripe) { create(:stripe_connect_payment_method, distributors: [shop]) }
|
||||
let!(:paypal) { Spree::Gateway::PayPalExpress.create!(name: "PayPalExpress", distributor_ids: [shop.id]) }
|
||||
let!(:bogus) { create(:bogus_payment_method, distributors: [shop]) }
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ describe Spree::Admin::PaymentsController, type: :controller do
|
||||
before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) }
|
||||
|
||||
context "that was processed by stripe" do
|
||||
let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) }
|
||||
let!(:payment_method) { create(:stripe_connect_payment_method, distributors: [shop]) }
|
||||
let!(:payment) do
|
||||
create(:payment, order: order, state: 'completed', payment_method: payment_method,
|
||||
response_code: 'ch_1a2b3c', amount: order.total)
|
||||
@@ -80,7 +80,7 @@ describe Spree::Admin::PaymentsController, type: :controller do
|
||||
before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) }
|
||||
|
||||
context "that was processed by stripe" do
|
||||
let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) }
|
||||
let!(:payment_method) { create(:stripe_connect_payment_method, distributors: [shop]) }
|
||||
let!(:payment) do
|
||||
create(:payment, order: order, state: 'completed', payment_method: payment_method,
|
||||
response_code: 'ch_1a2b3c', amount: order.total + 5)
|
||||
|
||||
@@ -45,7 +45,7 @@ describe Spree::Admin::PaymentsController, type: :controller do
|
||||
end
|
||||
|
||||
context "with Stripe payment where payment.process! errors out" do
|
||||
let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) }
|
||||
let!(:payment_method) { create(:stripe_connect_payment_method, distributors: [shop]) }
|
||||
before do
|
||||
allow_any_instance_of(Spree::Payment).
|
||||
to receive(:process!).
|
||||
|
||||
@@ -68,7 +68,7 @@ module Spree
|
||||
let!(:user) { create(:user, enterprise_limit: 2) }
|
||||
let!(:enterprise1) { create(:distributor_enterprise, owner: user) }
|
||||
let!(:enterprise2) { create(:distributor_enterprise, owner: create(:user)) }
|
||||
let!(:payment_method) { create(:stripe_payment_method, distributor_ids: [enterprise1.id, enterprise2.id], preferred_enterprise_id: enterprise2.id) }
|
||||
let!(:payment_method) { create(:stripe_connect_payment_method, distributor_ids: [enterprise1.id, enterprise2.id], preferred_enterprise_id: enterprise2.id) }
|
||||
|
||||
before { allow(controller).to receive(:spree_current_user) { user } }
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ FactoryBot.define do
|
||||
environment 'test'
|
||||
end
|
||||
|
||||
factory :stripe_payment_method, class: Spree::Gateway::StripeConnect do
|
||||
name 'Stripe'
|
||||
factory :stripe_connect_payment_method, class: Spree::Gateway::StripeConnect do
|
||||
name 'StripeConnect'
|
||||
environment 'test'
|
||||
distributors { [FactoryBot.create(:enterprise)] }
|
||||
preferred_enterprise_id { distributors.first.id }
|
||||
|
||||
@@ -172,7 +172,7 @@ feature 'Subscriptions' do
|
||||
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
|
||||
let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [test_variant, shop_variant], enterprise_fees: [enterprise_fee]) }
|
||||
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
|
||||
let!(:payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop]) }
|
||||
let!(:payment_method) { create(:stripe_connect_payment_method, name: 'Credit Card', distributors: [shop]) }
|
||||
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
|
||||
|
||||
before do
|
||||
@@ -319,7 +319,7 @@ feature 'Subscriptions' do
|
||||
let!(:variant3_oc) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) }
|
||||
let!(:variant3_ex) { variant3_oc.exchanges.create(sender: shop, receiver: shop, variants: [variant3]) }
|
||||
let!(:payment_method) { create(:payment_method, distributors: [shop]) }
|
||||
let!(:stripe_payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop]) }
|
||||
let!(:stripe_payment_method) { create(:stripe_connect_payment_method, name: 'Credit Card', distributors: [shop]) }
|
||||
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
|
||||
let!(:subscription) {
|
||||
create(:subscription,
|
||||
@@ -457,7 +457,7 @@ feature 'Subscriptions' do
|
||||
let!(:enterprise_fee) { create(:enterprise_fee, amount: 1.75) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop) }
|
||||
let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
|
||||
let!(:payment_method) { create(:stripe_payment_method, distributors: [shop]) }
|
||||
let!(:payment_method) { create(:stripe_connect_payment_method, distributors: [shop]) }
|
||||
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
|
||||
|
||||
before do
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
feature "Check out with Stripe", js: true do
|
||||
include AuthenticationHelper
|
||||
include ShopWorkflow
|
||||
include CheckoutHelper
|
||||
include StripeHelper
|
||||
|
||||
let(:distributor) { create(:distributor_enterprise) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], variants: [variant]) }
|
||||
@@ -12,12 +15,14 @@ feature "Check out with Stripe", js: true do
|
||||
let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor, bill_address_id: nil, ship_address_id: nil) }
|
||||
|
||||
let(:shipping_with_fee) { create(:shipping_method, require_ship_address: false, name: "Donkeys", calculator: Calculator::FlatRate.new(preferred_amount: 4.56)) }
|
||||
let(:free_shipping) { create(:shipping_method) }
|
||||
let!(:check_with_fee) { create(:payment_method, distributors: [distributor], calculator: Calculator::FlatRate.new(preferred_amount: 5.67)) }
|
||||
|
||||
before do
|
||||
setup_stripe
|
||||
set_order order
|
||||
add_product_to_cart order, product
|
||||
distributor.shipping_methods << shipping_with_fee
|
||||
distributor.shipping_methods << [shipping_with_fee, free_shipping]
|
||||
end
|
||||
|
||||
context 'login in as user' do
|
||||
@@ -27,9 +32,9 @@ feature "Check out with Stripe", js: true do
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
context "with Stripe" do
|
||||
context "with Stripe Connect" do
|
||||
let!(:stripe_pm) do
|
||||
create(:stripe_payment_method, distributors: [distributor])
|
||||
create(:stripe_connect_payment_method, distributors: [distributor])
|
||||
end
|
||||
|
||||
let!(:saved_card) do
|
||||
@@ -54,9 +59,6 @@ feature "Check out with Stripe", js: true do
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Stripe).to receive(:api_key) { "sk_test_12345" }
|
||||
allow(Stripe).to receive(:publishable_key) { "some_key" }
|
||||
Spree::Config.set(stripe_connect_enabled: true)
|
||||
stub_request(:post, "https://api.stripe.com/v1/charges")
|
||||
.with(basic_auth: ["sk_test_12345", ""])
|
||||
.to_return(status: 200, body: JSON.generate(response_mock))
|
||||
@@ -79,4 +81,76 @@ feature "Check out with Stripe", js: true do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "using Stripe SCA" do
|
||||
let!(:stripe_account) { create(:stripe_account, enterprise: distributor) }
|
||||
let!(:stripe_sca_payment_method) {
|
||||
create(:stripe_sca_payment_method, distributors: [distributor])
|
||||
}
|
||||
let!(:shipping_method) { create(:shipping_method) }
|
||||
|
||||
context "with guest checkout" do
|
||||
context "when the card is accepted" do
|
||||
before do
|
||||
stub_payment_intents_post_request order: order
|
||||
stub_payment_intent_get_request
|
||||
stub_hub_payment_methods_request
|
||||
stub_successful_capture_request order: order
|
||||
end
|
||||
|
||||
it "completes checkout successfully" do
|
||||
visit checkout_path
|
||||
|
||||
checkout_as_guest
|
||||
|
||||
fill_out_form(
|
||||
free_shipping.name,
|
||||
stripe_sca_payment_method.name,
|
||||
save_default_addresses: false
|
||||
)
|
||||
|
||||
fill_out_card_details
|
||||
|
||||
place_order
|
||||
|
||||
expect(page).to have_content "Confirmed"
|
||||
|
||||
expect(order.reload.completed?).to eq true
|
||||
expect(order.payments.first.state).to eq "completed"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the card is rejected" do
|
||||
let(:error_message) { "Card was declined: insufficient funds." }
|
||||
|
||||
before do
|
||||
stub_payment_intents_post_request order: order
|
||||
stub_payment_intent_get_request
|
||||
stub_hub_payment_methods_request
|
||||
stub_failed_capture_request order: order, response: { message: error_message }
|
||||
end
|
||||
|
||||
it "shows an error message from the Stripe response" do
|
||||
visit checkout_path
|
||||
|
||||
checkout_as_guest
|
||||
|
||||
fill_out_form(
|
||||
free_shipping.name,
|
||||
stripe_sca_payment_method.name,
|
||||
save_default_addresses: false
|
||||
)
|
||||
|
||||
fill_out_card_details
|
||||
|
||||
place_order
|
||||
|
||||
expect(page).to have_content error_message
|
||||
|
||||
expect(order.reload.state).to eq "cart"
|
||||
expect(order.payments.first.state).to eq "failed"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -237,8 +237,8 @@ describe EnterprisesHelper, type: :helper do
|
||||
end
|
||||
|
||||
context "when StripeConnect payment methods are present" do
|
||||
let!(:pm3) { create(:stripe_payment_method, distributors: [distributor], preferred_enterprise_id: distributor.id) }
|
||||
let!(:pm4) { create(:stripe_payment_method, distributors: [distributor], preferred_enterprise_id: some_other_distributor.id) }
|
||||
let!(:pm3) { create(:stripe_connect_payment_method, distributors: [distributor], preferred_enterprise_id: distributor.id) }
|
||||
let!(:pm4) { create(:stripe_connect_payment_method, distributors: [distributor], preferred_enterprise_id: some_other_distributor.id) }
|
||||
let(:available_payment_methods) { helper.available_payment_methods }
|
||||
|
||||
around do |example|
|
||||
|
||||
@@ -6,7 +6,7 @@ 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_payment_method) { create(:stripe_connect_payment_method) }
|
||||
let(:profile_storer) { Stripe::ProfileStorer.new(payment, stripe_payment_method.provider) }
|
||||
|
||||
let(:customer_id) { "cus_A123" }
|
||||
|
||||
@@ -730,7 +730,7 @@ describe Spree::Order do
|
||||
describe "determining checkout steps for an order" do
|
||||
let!(:enterprise) { create(:enterprise) }
|
||||
let!(:order) { create(:order, distributor: enterprise) }
|
||||
let!(:payment_method) { create(:stripe_payment_method, distributor_ids: [enterprise.id]) }
|
||||
let!(:payment_method) { create(:stripe_connect_payment_method, distributor_ids: [enterprise.id]) }
|
||||
let!(:payment) { create(:payment, order: order, payment_method: payment_method) }
|
||||
|
||||
it "does not include the :confirm step" do
|
||||
|
||||
@@ -847,7 +847,7 @@ describe Spree::Payment do
|
||||
|
||||
context "to Stripe payments" do
|
||||
let(:shop) { create(:enterprise) }
|
||||
let(:payment_method) { create(:stripe_payment_method, distributor_ids: [create(:distributor_enterprise).id], preferred_enterprise_id: shop.id) }
|
||||
let(:payment_method) { create(:stripe_connect_payment_method, distributor_ids: [create(:distributor_enterprise).id], preferred_enterprise_id: shop.id) }
|
||||
let(:payment) { create(:payment, order: order, payment_method: payment_method, amount: order.total) }
|
||||
let(:calculator) { ::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ describe "checking out an order with a Stripe Connect payment method", type: :re
|
||||
distributors: [enterprise]
|
||||
)
|
||||
end
|
||||
let!(:payment_method) { create(:stripe_payment_method, distributors: [enterprise]) }
|
||||
let!(:payment_method) { create(:stripe_connect_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 }
|
||||
|
||||
@@ -130,6 +130,15 @@ RSpec.configure do |config|
|
||||
ActionController::Base.perform_caching = caching
|
||||
end
|
||||
|
||||
# Show javascript errors in test output with `js_debug: true`
|
||||
config.after(:each, :js_debug) do
|
||||
errors = page.driver.browser.manage.logs.get(:browser)
|
||||
if errors.present?
|
||||
message = errors.map(&:message).join("\n")
|
||||
puts message
|
||||
end
|
||||
end
|
||||
|
||||
config.before(:all) { restart_driver }
|
||||
|
||||
# Geocoding
|
||||
|
||||
59
spec/support/fixtures/stripejs-mock.js
Normal file
59
spec/support/fixtures/stripejs-mock.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// StripeJS fixture for using Stripe in feature specs. Mimics credit card form and Element objects.
|
||||
// Based on: https://github.com/thoughtbot/fake_stripe/blob/v0.3.0/lib/fake_stripe/assets/v3.js
|
||||
// The original has been adapted to work with OFN (see commit history for details).
|
||||
|
||||
class Element {
|
||||
mount(el) {
|
||||
if (typeof el === "string") {
|
||||
el = document.querySelector(el);
|
||||
}
|
||||
|
||||
el.classList.add('StripeElement');
|
||||
|
||||
el.innerHTML = `
|
||||
<input id="stripe-cardnumber" name="cardnumber" placeholder="Card number" size="16" type="text">
|
||||
<input name="exp-date" placeholder="MM / YY" size="6" type="text">
|
||||
<input name="cvc" placeholder="CVC" size="3" type="text">
|
||||
`;
|
||||
}
|
||||
|
||||
addEventListener(event) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
window.Stripe = () => {
|
||||
const fetchLastFour = () => {
|
||||
return document.getElementById("stripe-cardnumber").value.substr(-4, 4);
|
||||
};
|
||||
|
||||
return {
|
||||
createPaymentMethod: () => {
|
||||
return new Promise(resolve => {
|
||||
resolve({
|
||||
paymentMethod: {
|
||||
id: "pm_123",
|
||||
card: {
|
||||
brand: 'visa',
|
||||
last4: fetchLastFour(),
|
||||
exp_month: "10",
|
||||
exp_year: "2050"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
elements: () => {
|
||||
return {
|
||||
create: (type, options) => new Element()
|
||||
};
|
||||
},
|
||||
|
||||
createToken: card => {
|
||||
return new Promise(resolve => {
|
||||
resolve({ token: { id: "tok_123", card: { last4: fetchLastFour() } } });
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
81
spec/support/request/stripe_helper.rb
Normal file
81
spec/support/request/stripe_helper.rb
Normal file
@@ -0,0 +1,81 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module StripeHelper
|
||||
def fill_out_card_details
|
||||
expect(page).to have_css("input[name='cardnumber']")
|
||||
fill_in 'Card number', with: '4242424242424242'
|
||||
fill_in 'MM / YY', with: "01/#{DateTime.now.year + 1}"
|
||||
fill_in 'CVC', with: '123'
|
||||
end
|
||||
|
||||
def setup_stripe
|
||||
allow(Stripe).to receive(:api_key) { "sk_test_12345" }
|
||||
allow(Stripe).to receive(:publishable_key) { "pk_test_12345" }
|
||||
Spree::Config.set(stripe_connect_enabled: true)
|
||||
end
|
||||
|
||||
def stub_payment_intents_post_request(order:, response: {})
|
||||
stub_request(:post, "https://api.stripe.com/v1/payment_intents")
|
||||
.with(basic_auth: ["sk_test_12345", ""], body: /.*#{order.number}/)
|
||||
.to_return(payment_intent_authorize_response_mock(response))
|
||||
end
|
||||
|
||||
def stub_payment_intent_get_request(response: {})
|
||||
stub_request(:get, "https://api.stripe.com/v1/payment_intents/pi_123")
|
||||
.with(headers: { 'Stripe-Account' => 'abc123' })
|
||||
.to_return(payment_intent_authorize_response_mock(response))
|
||||
end
|
||||
|
||||
def stub_hub_payment_methods_request(response: {})
|
||||
stub_request(:post, "https://api.stripe.com/v1/payment_methods")
|
||||
.with(body: { payment_method: "pm_123" },
|
||||
headers: { 'Stripe-Account' => 'abc123' })
|
||||
.to_return(hub_payment_method_response_mock(response))
|
||||
end
|
||||
|
||||
def stub_successful_capture_request(order:, response: {})
|
||||
stub_capture_request(order, payment_successful_capture_mock(response))
|
||||
end
|
||||
|
||||
def stub_failed_capture_request(order:, response: {})
|
||||
stub_capture_request(order, payment_failed_capture_mock(response))
|
||||
end
|
||||
|
||||
def stub_capture_request(order, response_mock)
|
||||
stub_request(:post, "https://api.stripe.com/v1/payment_intents/pi_123/capture")
|
||||
.with(body: { amount_to_capture: Spree::Money.new(order.total).cents },
|
||||
headers: { 'Stripe-Account' => 'abc123' })
|
||||
.to_return(response_mock)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def payment_intent_authorize_response_mock(options)
|
||||
{ status: options[:code] || 200,
|
||||
body: JSON.generate(id: "pi_123",
|
||||
object: "payment_intent",
|
||||
amount: 2000,
|
||||
status: options[:intent_status] || "requires_capture",
|
||||
last_payment_error: nil,
|
||||
charges: { data: [{ id: "ch_1234", amount: 2000 }] }) }
|
||||
end
|
||||
|
||||
def payment_successful_capture_mock(options)
|
||||
{ status: options[:code] || 200,
|
||||
body: JSON.generate(object: "payment_intent",
|
||||
amount: 2000,
|
||||
charges: { data: [{ id: "ch_1234", amount: 2000 }] }) }
|
||||
end
|
||||
|
||||
def payment_failed_capture_mock(options)
|
||||
{ status: options[:code] || 402,
|
||||
body: JSON.generate(error: { message:
|
||||
options[:message] || "payment-method-failure" }) }
|
||||
end
|
||||
|
||||
def hub_payment_method_response_mock(options)
|
||||
{ status: options[:code] || 200,
|
||||
body: JSON.generate(id: "pm_456", customer: "cus_A123") }
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user