diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index d06eb1f1c2..39fccfb075 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -210,8 +210,8 @@ class CheckoutController < Spree::CheckoutController existing_card_id = params[:order].delete(:existing_card) if existing_card_id.present? credit_card = Spree::CreditCard.find(existing_card_id) - if credit_card.user_id != spree_current_user.id || credit_card.user_id.blank? - raise Core::GatewayError.new Spree.t(:invalid_credit_card) + if credit_card.try(:user_id).blank? || credit_card.user_id != spree_current_user.try(:id) + raise Spree::Core::GatewayError.new I18n.t(:invalid_credit_card) end # Not currently supported but maybe we should add it...? diff --git a/app/models/spree/payment_decorator.rb b/app/models/spree/payment_decorator.rb index c7cd8aa409..bede38dbe7 100644 --- a/app/models/spree/payment_decorator.rb +++ b/app/models/spree/payment_decorator.rb @@ -93,9 +93,9 @@ module Spree end def create_payment_profile - return unless source.is_a?(CreditCard) && - (source.number || source.gateway_payment_profile_id) && - source.gateway_customer_profile_id.nil? + return unless source.is_a?(CreditCard) + return unless source.number || source.gateway_payment_profile_id + return unless source.gateway_customer_profile_id.nil? payment_method.create_profile(self) rescue ActiveMerchant::ConnectionError => e gateway_error e diff --git a/spec/requests/stripe_connect_checkout_spec.rb b/spec/requests/stripe_connect_checkout_spec.rb index 0c4d8de701..1be74260ce 100644 --- a/spec/requests/stripe_connect_checkout_spec.rb +++ b/spec/requests/stripe_connect_checkout_spec.rb @@ -2,11 +2,12 @@ require 'spec_helper' describe "Submitting Stripe Connect charge requests", type: :request do include ShopWorkflow + include AuthenticationWorkflow let!(:order_cycle) { create(:simple_order_cycle) } let!(:enterprise) { create(:distributor_enterprise) } let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: order_cycle.coordinator, receiver: enterprise, incoming: false, pickup_time: "Monday")} - let!(:shipping_method) { create(:free_shipping_method, distributors: [enterprise]) } + let!(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0), distributors: [enterprise]) } let!(:payment_method) { create(:payment_method, type: "Spree::Gateway::StripeConnect", distributors: [enterprise]) } let!(:stripe_account) { create(:stripe_account, enterprise: enterprise) } let!(:line_item) { create(:line_item, price: 12.34) } @@ -25,38 +26,84 @@ describe "Submitting Stripe Connect charge requests", type: :request do order.update_attributes(distributor_id: enterprise.id, order_cycle_id: order_cycle.id) order.reload.update_totals set_order order - - # Storing the card against the user - stub_request(:post, "https://sk_test_123456:@api.stripe.com/v1/customers") - .with(:body => { card: token, email: order.email}) - .to_return(status: 200, body: JSON.generate({ id: "cus_A123", default_card: "card_XyZ456", sources: { data: [{id: "1"}] } }), headers: {}) - - stub_request(:post, "https://api.stripe.com/v1/tokens") - .with(:body => { card: "card_XyZ456", customer: "cus_A123"}) - .to_return(status: 200, body: JSON.generate({id: "tok_123"}), headers: {}) - - stub_request(:post, "https://sk_test_123456:@api.stripe.com/v1/charges") - .with(:body => {"amount"=>"1234", "card"=>{"exp_month"=>"10", "exp_year"=>"2025"}, "currency"=>"aud", "description"=>"Spree Order ID: #{order.number}", "payment_user_agent"=>"Stripe/v1 ActiveMerchantBindings/1.63.0"}) - .to_return(body: JSON.generate(charge_response_mock)) end - context "when the charge request is accepted" do - let(:charge_response_mock) { { id: "ch_1234", object: "charge", amount: 2000} } + context "when a new card is submitted" do + before do + # Saves the card against the user + stub_request(:post, "https://sk_test_123456:@api.stripe.com/v1/customers") + .with(:body => { card: token, email: order.email}) + .to_return(status: 200, body: JSON.generate({ id: "cus_A123", default_card: "card_XyZ456", sources: { data: [{id: "1"}] } }), headers: {}) - it "should process the payment" do - put update_checkout_path, params - expect(response).to redirect_to(spree.order_path(order)) - expect(order.payments.completed.count).to be 1 + # Requests a token from the newly saved card + stub_request(:post, "https://api.stripe.com/v1/tokens") + .with(:body => { card: "card_XyZ456", customer: "cus_A123"}) + .to_return(status: 200, body: JSON.generate({id: "newtoken_123"}), headers: {}) + + # Charges the card + stub_request(:post, "https://sk_test_123456:@api.stripe.com/v1/charges") + .with(:body => {"amount"=>"1234", "card"=>"newtoken_123", "currency"=>"aud", "description"=>"Spree Order ID: #{order.number}", "payment_user_agent"=>"Stripe/v1 ActiveMerchantBindings/1.63.0"}) + .to_return(body: JSON.generate(charge_response_mock)) + end + + context "and the charge request is accepted" do + let(:charge_response_mock) { { id: "ch_1234", object: "charge", amount: 2000} } + + it "should process the payment, and stores the card/customer details" do + put update_checkout_path, params + expect(response).to redirect_to(spree.order_path(order)) + expect(order.payments.completed.count).to be 1 + card = order.payments.completed.first.source + expect(card.gateway_customer_profile_id).to eq "cus_A123" + expect(card.gateway_payment_profile_id).to eq "card_XyZ456" + end + end + + context "when the charge request returns an error message" do + let(:charge_response_mock) { { error: { message: "Bup-bow..."} } } + + it "should not process the payment" do + put update_checkout_path, params + expect(response).to render_template(:edit) + expect(order.payments.completed.count).to be 0 + end end end - context "when the charge request returns an error message" do - let(:charge_response_mock) { { error: { message: "Bup-bow..."} } } + context "when an existing card is submitted" do + let(:credit_card) { create(:credit_card, + payment_method_id: payment_method.id, + user_id: order.user_id, + gateway_payment_profile_id: "card_AbC123", + gateway_customer_profile_id: "cus_Z456", + month: 11, year: 2026) } - it "should not process the payment" do - put update_checkout_path, params - expect(response).to render_template(:edit) - expect(order.payments.completed.count).to be 0 + before do + params[:order][:existing_card] = credit_card.id + quick_login_as(order.user) + + # Requests a token + stub_request(:post, "https://api.stripe.com/v1/tokens") + .with(:body => {"card"=>"card_AbC123", "customer"=>"cus_Z456"}) + .to_return(status: 200, body: JSON.generate({id: "newtoken_123"}), headers: {}) + + # Charges the card + stub_request(:post, "https://sk_test_123456:@api.stripe.com/v1/charges") + .with(:body => {"amount"=>"1234", "card"=>"newtoken_123", "currency"=>"aud", "description"=>"Spree Order ID: #{order.number}", "payment_user_agent"=>"Stripe/v1 ActiveMerchantBindings/1.63.0"}) + .to_return(body: JSON.generate(charge_response_mock)) + end + + context "and the charge request is accepted" do + let(:charge_response_mock) { { id: "ch_1234", object: "charge", amount: 2000} } + + it "should process the payment, and keep the profile ids" do + put update_checkout_path, params + expect(response).to redirect_to(spree.order_path(order)) + expect(order.payments.completed.count).to be 1 + card = order.payments.completed.first.source + expect(card.gateway_customer_profile_id).to eq "cus_Z456" + expect(card.gateway_payment_profile_id).to eq "card_AbC123" + end end end end