Files
openfoodnetwork/spec/controllers/payment_gateways/stripe_controller_spec.rb
Maikel Linke 52e934ec2b Consistently use our FeatureToggle module
Direct calls to Flipper have the downside that we can't add any new
functionality like storing the feature in the database when used.
2022-10-08 16:23:17 +02:00

319 lines
12 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
module PaymentGateways
describe StripeController, type: :controller do
include StripeStubs
let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
let!(:order) { create(:order_with_totals, distributor: distributor, order_cycle: order_cycle) }
let(:exchange) { order_cycle.exchanges.to_enterprises(distributor).outgoing.first }
let(:order_cycle_distributed_variants) { instance_double(OrderCycleDistributedVariants) }
before do
exchange.variants << order.line_items.first.variant
allow(controller).to receive(:current_order).and_return(order)
end
describe "#confirm" do
context "when the order is in payment state and a stripe payment intent is provided" do
let(:user) { order.user }
let(:payment_method) { create(:stripe_sca_payment_method) }
let(:payment) {
create(
:payment,
amount: order.total,
state: "requires_authorization",
payment_method: payment_method,
response_code: "pi_123"
)
}
before do
Stripe.api_key = "sk_test_12345"
stub_payment_intent_get_request
stub_successful_capture_request(order: order)
allow(controller).to receive(:spree_current_user).and_return(user)
user.bill_address = create(:address)
user.ship_address = create(:address)
user.save!
order.update_attribute :state, "payment"
order.payments << payment
end
shared_examples "successful order completion" do
it "completes the order and redirects to the order confirmation page" do
expect(controller).to receive(:processing_succeeded).and_call_original
expect(controller).to receive(:order_completion_reset).and_call_original
get :confirm, params: { payment_intent: "pi_123" }
expect(order.completed?).to be true
expect(response).to redirect_to order_path(order, order_token: order.token)
expect(flash[:notice]).to eq I18n.t(:order_processed_successfully)
end
end
include_examples "successful order completion"
it "creates a customer record" do
order.update_columns(customer_id: nil)
Customer.delete_all
expect {
get :confirm, params: { payment_intent: "pi_123" }
}.to change { Customer.count }.by(1)
end
context "when the order cycle has closed" do
it "redirects to shopfront with message if order cycle is expired" do
allow(controller).to receive(:current_distributor).and_return(distributor)
expect(controller).to receive(:current_order_cycle).and_return(order_cycle)
expect(controller).to receive(:current_order).and_return(order).at_least(:once)
expect(order_cycle).to receive(:closed?).and_return(true)
expect(order).to receive(:empty!)
expect(order).to receive(:set_order_cycle!).with(nil)
get :confirm, params: { payment_intent: "pi_123" }
expect(response).to redirect_to shop_url
expect(flash[:info]).to eq I18n.t('order_cycle_closed')
end
end
context "using split checkout" do
before do
allow(OpenFoodNetwork::FeatureToggle).
to receive(:enabled?).with(:split_checkout) { true }
allow(OpenFoodNetwork::FeatureToggle).
to receive(:enabled?).with(:split_checkout, anything) { true }
order.update_attribute :state, "confirmation"
end
include_examples "successful order completion"
end
end
context "when the order is not in payment state" do
before { order.update_columns(state: "cart", completed_at: nil) }
it "fails" do
expect(controller).to receive(:processing_failed).and_call_original
get :confirm, params: { payment_intent: "pi_123" }
expect(order.completed?).to be false
expect(response).to redirect_to checkout_path
expect(flash[:error]).to eq I18n.t(:payment_processing_failed)
end
end
context "when a valid payment intent is not provided" do
it "fails" do
expect(controller).to receive(:processing_failed).and_call_original
get :confirm, params: { payment_intent: "pi_666" }
expect(order.completed?).to be false
expect(response).to redirect_to checkout_path
expect(flash[:error]).to eq I18n.t(:payment_processing_failed)
end
end
context "when items in the cart are invalid" do
before do
allow(order_cycle_distributed_variants).
to receive(:distributes_order_variants?).and_return(false)
end
it "fails" do
expect(controller).to receive(:processing_failed).and_call_original
get :confirm, params: { payment_intent: "pi_123" }
expect(order.completed?).to be false
expect(response).to redirect_to checkout_path
expect(flash[:error]).to eq I18n.t(:payment_processing_failed)
end
end
context "items running out of stock during order completion" do
it "redirects to cart when some items are out of stock" do
allow(controller).to receive(:valid_payment_intent?).and_return true
allow(order).to receive_message_chain(:insufficient_stock_lines, :empty?).and_return false
get :confirm, params: { payment_intent: "pi_123" }
expect(response).to redirect_to cart_path
end
context "handling pending payments" do
let!(:payment) { create(:payment, state: "pending", amount: order.total, order: order) }
let!(:transaction_fee) {
create(:adjustment, state: "open", amount: 10, order: order, adjustable: payment)
}
before do
allow(order).to receive_message_chain(:insufficient_stock_lines, :empty?) { false }
order.save
allow(order).to receive_message_chain(:payments, :completed) { [] }
allow(order).to receive_message_chain(:payments, :incomplete) { [payment] }
allow(controller).to receive(:valid_payment_intent?) { true }
allow(controller).to receive(:last_payment) { payment }
allow(payment).to receive(:adjustment) { transaction_fee }
end
it "cancels the payment and resets the order to cart" do
expect(payment).to receive(:void_transaction!).and_call_original
get :confirm, params: { payment_intent: "pi_123" }
expect(response).to redirect_to cart_path
expect(flash[:notice]).to eq I18n.t('checkout.payment_cancelled_due_to_stock')
expect(order.state).to eq "cart"
expect(payment.state).to eq "void"
expect(transaction_fee.reload.eligible).to eq false
expect(transaction_fee.state).to eq "finalized"
end
end
end
end
describe "#authorize" do
let(:customer) { create(:customer) }
let(:order) {
create(:order_with_totals, customer: customer, distributor: customer.enterprise,
state: "payment")
}
let(:payment_method) { create(:stripe_sca_payment_method) }
let!(:payment) {
create(
:payment,
payment_method: payment_method,
cvv_response_message: "https://stripe.com/redirect",
response_code: "pi_123",
order: order,
state: "requires_authorization"
)
}
before do
allow(controller).to receive(:spree_current_user) { current_user }
end
context "after returning from Stripe to authorize a payment" do
let(:current_user) { order.user }
context "with a valid payment intent" do
let(:payment_intent) { "pi_123" }
let(:payment_intent_response) { double(id: "pi_123", status: "requires_capture") }
before do
allow(Stripe::PaymentIntentValidator)
.to receive_message_chain(:new, :call).and_return(payment_intent_response)
allow(Spree::Order).to receive(:find_by) { order }
end
context "when the order is in payment state" do
it "completes the payment" do
expect(order).to receive(:process_payments!) do
payment.complete!
end
get :authorize, params: { order_number: order.number, payment_intent: payment_intent }
expect(response).to redirect_to order_path(order)
payment.reload
expect(payment.state).to eq("completed")
expect(payment.cvv_response_message).to be nil
end
end
context "when the order is already completed" do
before do
order.update_columns(state: "complete")
end
it "should still process the payment" do
expect(order).to receive(:process_payments!) do
payment.complete!
end
get :authorize, params: { order_number: order.number, payment_intent: payment_intent }
expect(response).to redirect_to order_path(order)
payment.reload
expect(payment.state).to eq("completed")
expect(payment.cvv_response_message).to be nil
end
end
context "when the order cycle has closed" do
it "should still authorize the payment successfully" do
expect(order).to receive(:process_payments!) do
payment.complete!
end
get :authorize, params: { order_number: order.number, payment_intent: payment_intent }
expect(response).to redirect_to order_path(order)
payment.reload
expect(payment.state).to eq("completed")
expect(payment.cvv_response_message).to be nil
end
end
end
context "when the payment intent response has errors" do
let(:payment_intent) { "pi_123" }
before do
allow(Stripe::PaymentIntentValidator)
.to receive_message_chain(:new, :call).and_raise(Stripe::StripeError, "error message")
end
it "does not complete the payment" do
get :authorize, params: { order_number: order.number, payment_intent: payment_intent }
expect(response).to redirect_to order_path(order)
expect(flash[:error]).to eq("#{I18n.t('payment_could_not_process')}. error message")
payment.reload
expect(payment.cvv_response_message).to be nil
expect(payment.state).to eq("failed")
end
end
context "with an invalid last payment" do
let(:payment_intent) { "valid" }
let(:finder) { instance_double(OrderPaymentFinder, last_payment: payment) }
before do
allow(payment).to receive(:response_code).and_return("invalid")
allow(OrderPaymentFinder).to receive(:new).with(order).and_return(finder)
allow(Stripe::PaymentIntentValidator)
.to receive_message_chain(:new, :call).and_return(payment_intent)
stub_payment_intent_get_request(payment_intent_id: "valid")
end
it "does not complete the payment" do
get :authorize, params: { order_number: order.number, payment_intent: payment_intent }
expect(response).to redirect_to order_path(order)
expect(flash[:error]).to eq("#{I18n.t('payment_could_not_process')}. ")
payment.reload
expect(payment.cvv_response_message).to eq("https://stripe.com/redirect")
expect(payment.state).to eq("requires_authorization")
end
end
end
end
end
end