Merge pull request #6128 from luisramos0/stripe_bo

Add feature specs to stripe payments in the BackOffice
This commit is contained in:
Matt-Yorkley
2020-10-16 02:53:42 +02:00
committed by GitHub
10 changed files with 215 additions and 254 deletions

View File

@@ -553,10 +553,6 @@ module Spree
line_item_adjustments.destroy_all
end
def has_step?(step)
checkout_steps.include?(step)
end
def state_changed(name)
state = "#{name}_state"
return unless persisted?
@@ -803,7 +799,6 @@ module Spree
end
def has_available_shipment
return unless has_step?("delivery")
return unless address?
return unless ship_address&.valid?
# errors.add(:base, :no_shipping_methods_available) if available_shipping_methods.empty?

View File

@@ -9,7 +9,6 @@ module Spree
class_attribute :previous_states
class_attribute :checkout_flow
class_attribute :checkout_steps
class_attribute :removed_transitions
def self.checkout_flow(&block)
if block_given?
@@ -24,7 +23,6 @@ module Spree
self.checkout_steps = {}
self.next_event_transitions = []
self.previous_states = [:cart]
self.removed_transitions = []
# Build the checkout flow using the checkout_flow defined either
# within the Order class, or a decorator for that class.
@@ -95,43 +93,6 @@ module Spree
end
end
def self.insert_checkout_step(name, options = {})
before = options.delete(:before)
after = options.delete(:after) unless before
after = checkout_steps.keys.last unless before || after
cloned_steps = checkout_steps.clone
cloned_removed_transitions = removed_transitions.clone
checkout_flow do
cloned_steps.each_pair do |key, value|
go_to_state(name, options) if key == before
go_to_state(key, value)
go_to_state(name, options) if key == after
end
cloned_removed_transitions.each do |transition|
remove_transition(transition)
end
end
end
def self.remove_checkout_step(name)
cloned_steps = checkout_steps.clone
cloned_removed_transitions = removed_transitions.clone
checkout_flow do
cloned_steps.each_pair do |key, value|
go_to_state(key, value) unless key == name
end
cloned_removed_transitions.each do |transition|
remove_transition(transition)
end
end
end
def self.remove_transition(options = {})
removed_transitions << options
next_event_transitions.delete(find_transition(options))
end
def self.find_transition(options = {})
return nil if options.nil? || !options.include?(:from) || !options.include?(:to)
@@ -173,10 +134,6 @@ module Spree
checkout_steps.index(step)
end
def self.removed_transitions
@removed_transitions ||= []
end
def can_go_to_state?(state)
return false unless self.state.present? &&
checkout_step?(state) &&

View File

@@ -30,31 +30,4 @@ 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
login_as_admin_and_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
login_as_admin_and_visit spree.admin_order_payments_path order
page.click_link("StripeSCA")
expect(page).to have_content order.payments.last.amount
end
end
end
end

View File

@@ -0,0 +1,161 @@
# frozen_string_literal: true
require 'spec_helper'
feature '
As an hub manager
I want to make Stripe payments
' do
include AuthenticationHelper
include StripeHelper
let!(:order) { create(:completed_order_with_fees) }
let!(:stripe_payment_method) do
create(:stripe_sca_payment_method, distributors: [order.distributor])
end
let!(:stripe_account) do
create(:stripe_account, enterprise: order.distributor, stripe_user_id: "abc123")
end
before { setup_stripe }
context "making a new Stripe payment", js: true do
before do
stub_payment_methods_post_request
stub_payment_intent_get_request
end
context "for a complete order" do
context "with a card that succeeds on card registration" do
before { stub_payment_intents_post_request order: order, stripe_account_header: true }
context "and succeeds on payment capture" do
before { stub_successful_capture_request order: order }
it "adds a payment with state complete" do
login_as_admin_and_visit spree.new_admin_order_payment_path order
fill_in "payment_amount", with: order.total.to_s
fill_in_card_details_in_backoffice
click_button "Update"
expect(page).to have_link "StripeSCA"
expect(OrderPaymentFinder.new(order.reload).last_payment.state).to eq "completed"
end
end
context "but fails on payment capture" do
let(:error_message) { "Card was declined: insufficient funds." }
before { stub_failed_capture_request order: order, response: { message: error_message } }
it "fails to add a payment due to card error" do
login_as_admin_and_visit spree.new_admin_order_payment_path order
fill_in "payment_amount", with: order.total.to_s
fill_in_card_details_in_backoffice
click_button "Update"
expect(page).to have_link "StripeSCA"
expect(page).to have_content "FAILED"
expect(OrderPaymentFinder.new(order.reload).last_payment.state).to eq "failed"
end
end
end
context "with a card that fails on registration because it requires(redirects) extra auth" do
before do
stub_payment_intents_post_request_with_redirect order: order,
redirect_url: "www.dummy.org"
end
it "fails to add a payment due to card error" do
login_as_admin_and_visit spree.new_admin_order_payment_path order
fill_in "payment_amount", with: order.total.to_s
fill_in_card_details_in_backoffice
click_button "Update"
expect(page).to have_link "StripeSCA"
expect(page).to have_content "PROCESSING"
expect(OrderPaymentFinder.new(order.reload).last_payment.state).to eq "processing"
end
end
end
context "for an order in payment state" do
let!(:order) { create(:order_with_line_items, distributor: create(:enterprise)) }
before do
stub_payment_intents_post_request order: order, stripe_account_header: true
stub_successful_capture_request order: order
while !order.payment? do break unless order.next! end
end
it "adds a payment with state complete" do
login_as_admin_and_visit spree.new_admin_order_payment_path order
fill_in "payment_amount", with: order.total.to_s
fill_in_card_details_in_backoffice
click_button "Update"
expect(page).to have_link "StripeSCA"
expect(OrderPaymentFinder.new(order.reload).last_payment.state).to eq "completed"
end
end
end
context "with a payment using a StripeSCA payment method" do
before do
order.update payments: []
order.payments << create(:payment, payment_method: stripe_payment_method, order: order)
end
it "renders the payment details" do
login_as_admin_and_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 source: nil
end
it "renders the payment details" do
login_as_admin_and_visit spree.admin_order_payments_path order
page.click_link("StripeSCA")
expect(page).to have_content order.payments.last.amount
end
end
context "that is completed", js: true do
let(:payment) { OrderPaymentFinder.new(order.reload).last_payment }
before do
stub_payment_intent_get_request stripe_account_header: false
stub_successful_capture_request order: order
payment.update response_code: "pi_123", amount: order.total
payment.purchase!
stub_refund_request
end
it "allows to refund the payment" do
login_as_admin_and_visit spree.admin_order_payments_path order
expect(page).to have_link "StripeSCA"
expect(page).to have_content "COMPLETED"
page.find('a.icon-void').click
expect(page).to have_content "VOID"
expect(payment.reload.state).to eq "void"
end
end
end
end

View File

@@ -93,7 +93,7 @@ feature "Check out with Stripe", js: true do
context "with guest checkout" do
before do
stub_payment_intent_get_request
stub_hub_payment_methods_request
stub_payment_methods_post_request
end
context "when the card is accepted" do
@@ -127,21 +127,10 @@ feature "Check out with Stripe", js: true do
end
context "when the card needs extra SCA authorization", js: true do
let(:stripe_redirect_url) { checkout_path(payment_intent: "pi_123") }
let(:payment_intent_authorize_response) do
{ status: 200, body: JSON.generate(id: "pi_123",
object: "payment_intent",
next_source_action: {
type: "authorize_with_url",
authorize_with_url: { url: stripe_redirect_url }
},
status: "requires_source_action") }
end
before do
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)
stripe_redirect_url = checkout_path(payment_intent: "pi_123")
stub_payment_intents_post_request_with_redirect order: order,
redirect_url: stripe_redirect_url
end
describe "and the authorization succeeds" do

View File

@@ -13,24 +13,23 @@ module OpenFoodNetwork
@variant1 = create(:variant)
@variant1.product.supplier = @supplier1
@variant1.product.save!
@variant1.reload
shipping_instructions = "pick up on thursday please!"
order1 = create(:order, distributor: distributor, bill_address: bill_address, special_instructions: shipping_instructions)
line_item11 = create(:line_item, variant: @variant1, order: order1)
order1.line_items << line_item11
@orders << order1
@orders << order1.reload
order2 = create(:order, distributor: distributor, bill_address: bill_address, special_instructions: shipping_instructions)
line_item21 = create(:line_item, variant: @variant1, order: order2)
order2.line_items << line_item21
@variant2 = create(:variant)
@variant2.product.supplier = @supplier1
@variant2.product.save!
line_item22 = create(:line_item, variant: @variant2, order: order2)
order2.line_items << line_item22
@orders << order2
@orders << order2.reload
@supplier2 = create(:supplier_enterprise)
@variant3 = create(:variant, weight: nil)
@@ -39,8 +38,7 @@ module OpenFoodNetwork
order3 = create(:order, distributor: distributor, bill_address: bill_address, special_instructions: shipping_instructions)
line_item31 = create(:line_item, variant: @variant3, order: order3)
order3.line_items << line_item31
@orders << order3
@orders << order3.reload
end
it "should return a header row describing the report" do

View File

@@ -10,9 +10,7 @@ module Spree
let!(:line_item2) { create(:line_item, order: order) }
before do
order.line_items << line_item
order.line_items << line_item2
order.shipments = [shipment]
order.reload.shipments = [shipment]
end
describe "#line_items_for" do

View File

@@ -30,16 +30,6 @@ describe Spree::Order::Checkout do
expect(Spree::Order.find_transition({ foo: :bar, baz: :dog })).to be_falsy
end
it '.remove_transition' do
options = { from: transitions.first.keys.first, to: transitions.first.values.first }
allow(Spree::Order).to receive(:next_event_transition).and_return([options])
expect(Spree::Order.remove_transition(options)).to be_truthy
end
it '.remove_transition when contract was broken' do
expect(Spree::Order.remove_transition(nil)).to be_falsy
end
context "#checkout_steps" do
context "when payment not required" do
before { allow(order).to receive_messages payment_required?: false }
@@ -160,145 +150,6 @@ describe Spree::Order::Checkout do
end
end
context "subclassed order" do
# This causes another test above to fail, but fixing this test should make
# the other test pass
class SubclassedOrder < Spree::Order
checkout_flow do
go_to_state :payment
go_to_state :complete
end
end
it "should only call default transitions once when checkout_flow is redefined" do
order = SubclassedOrder.new
allow(order).to receive_messages payment_required?: true
expect(order).to receive(:process_payments!).once
order.state = "payment"
order.next!
expect(order.state).to eq "complete"
end
end
context "re-define checkout flow" do
before do
@old_checkout_flow = Spree::Order.checkout_flow
Spree::Order.class_eval do
checkout_flow do
go_to_state :payment
go_to_state :complete
end
end
end
after do
Spree::Order.checkout_flow(&@old_checkout_flow)
end
it "should not keep old event transitions when checkout_flow is redefined" do
expect(Spree::Order.next_event_transitions).to eq [{ cart: :payment }, { payment: :complete }]
end
it "should not keep old events when checkout_flow is redefined" do
state_machine = Spree::Order.state_machine
expect(state_machine.states.any? { |s| s.name == :address }).to be_falsy
known_states = state_machine.events[:next].branches.map(&:known_states).flatten
expect(known_states).to_not include(:address)
expect(known_states).to_not include(:delivery)
expect(known_states).to_not include(:confirm)
end
end
# Regression test for Spree #3665
context "with only a complete step" do
before do
@old_checkout_flow = Spree::Order.checkout_flow
Spree::Order.class_eval do
checkout_flow do
go_to_state :complete
end
end
end
after do
Spree::Order.checkout_flow(&@old_checkout_flow)
end
it "does not attempt to process payments" do
allow(order).to receive_message_chain(:line_items, :present?).and_return(true)
expect(order).to_not receive(:payment_required?)
expect(order).to_not receive(:process_payments!)
order.next!
end
end
context "insert checkout step" do
before do
@old_checkout_flow = Spree::Order.checkout_flow
Spree::Order.class_eval do
insert_checkout_step :new_step, before: :address
end
end
after do
Spree::Order.checkout_flow(&@old_checkout_flow)
end
it "should maintain removed transitions" do
transition = Spree::Order.find_transition(from: :delivery, to: :confirm)
expect(transition).to be_nil
end
context "before" do
before do
Spree::Order.class_eval do
insert_checkout_step :before_address, before: :address
end
end
specify do
order = Spree::Order.new
expect(order.checkout_steps).to eq %w(new_step before_address address delivery complete)
end
end
context "after" do
before do
Spree::Order.class_eval do
insert_checkout_step :after_address, after: :address
end
end
specify do
order = Spree::Order.new
expect(order.checkout_steps).to eq %w(new_step address after_address delivery complete)
end
end
end
context "remove checkout step" do
before do
@old_checkout_flow = Spree::Order.checkout_flow
Spree::Order.class_eval do
remove_checkout_step :address
end
end
after do
Spree::Order.checkout_flow(&@old_checkout_flow)
end
it "should maintain removed transitions" do
transition = Spree::Order.find_transition(from: :delivery, to: :confirm)
expect(transition).to be_nil
end
specify do
order = Spree::Order.new
expect(order.checkout_steps).to eq %w(delivery complete)
end
end
describe 'event :restart_checkout' do
let(:order) { build_stubbed(:order) }

View File

@@ -23,7 +23,7 @@ describe OrderCheckoutRestart do
order.update_attribute(:state, "payment")
end
xcontext "when order ship address is nil" do
context "when order ship address is nil" do
before { order.ship_address = nil }
it "resets the order state, and clears incomplete shipments and payments" do
@@ -33,7 +33,7 @@ describe OrderCheckoutRestart do
end
end
xcontext "when order ship address is not empty" do
context "when order ship address is not empty" do
before { order.ship_address = order.address_from_distributor }
it "resets the order state, and clears incomplete shipments and payments" do

View File

@@ -21,16 +21,31 @@ module StripeHelper
fill_in 'CVC', with: '123'
end
def fill_in_card_details_in_backoffice
choose "StripeSCA"
fill_in "cardholder_name", with: "David Gilmour"
fill_in "stripe-cardnumber", with: "4242424242424242"
fill_in "exp-date", with: "01-01-2050"
fill_in "cvc", with: "678"
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: {})
def stub_payment_intents_post_request(order:, response: {}, stripe_account_header: true)
stub = stub_request(:post, "https://api.stripe.com/v1/payment_intents")
.with(basic_auth: ["sk_test_12345", ""], body: /.*#{order.number}/)
stub = stub.with(headers: { 'Stripe-Account' => 'abc123' }) if stripe_account_header
stub.to_return(payment_intent_authorize_response_mock(response))
end
def stub_payment_intents_post_request_with_redirect(order:, redirect_url:)
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))
.to_return(payment_intent_redirect_response_mock(redirect_url))
end
def stub_payment_intent_get_request(response: {}, stripe_account_header: true)
@@ -39,7 +54,7 @@ module StripeHelper
stub.to_return(payment_intent_authorize_response_mock(response))
end
def stub_hub_payment_methods_request(response: {})
def stub_payment_methods_post_request(response: {})
stub_request(:post, "https://api.stripe.com/v1/payment_methods")
.with(body: { payment_method: "pm_123" },
headers: { 'Stripe-Account' => 'abc123' })
@@ -61,6 +76,13 @@ module StripeHelper
.to_return(response_mock)
end
def stub_refund_request
stub_request(:post, "https://api.stripe.com/v1/charges/ch_1234/refunds")
.with(body: { amount: 2000, expand: ["charge"] },
headers: { 'Stripe-Account' => 'abc123' })
.to_return(payment_successful_refund_mock)
end
private
def payment_intent_authorize_response_mock(options)
@@ -74,6 +96,16 @@ module StripeHelper
charges: { data: [{ id: "ch_1234", amount: 2000 }] }) }
end
def payment_intent_redirect_response_mock(redirect_url)
{ status: 200, body: JSON.generate(id: "pi_123",
object: "payment_intent",
next_source_action: {
type: "authorize_with_url",
authorize_with_url: { url: redirect_url }
},
status: "requires_source_action") }
end
def payment_successful_capture_mock(options)
{ status: options[:code] || 200,
body: JSON.generate(object: "payment_intent",
@@ -91,4 +123,11 @@ module StripeHelper
{ status: options[:code] || 200,
body: JSON.generate(id: "pm_456", customer: "cus_A123") }
end
def payment_successful_refund_mock
{ status: 200,
body: JSON.generate(object: "refund",
amount: 2000,
charge: "ch_1234") }
end
end