mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-26 20:56:48 +00:00
Direct calls to Flipper have the downside that we can't add any new functionality like storing the feature in the database when used.
319 lines
12 KiB
Ruby
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
|