From c97a14047184d7ca4cd8f7df156f9558efba9b36 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 23 Feb 2017 17:53:33 +1100 Subject: [PATCH] WIP: Storing Stripe card details for later use NOTE: No interface for actually selecting a stored card to use yet --- app/models/spree/gateway/stripe_connect.rb | 33 ++++++--- app/models/spree/payment_decorator.rb | 9 +++ .../spree/gateway/stripe_connect_spec.rb | 69 +++++++++++++++++++ 3 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 spec/models/spree/gateway/stripe_connect_spec.rb diff --git a/app/models/spree/gateway/stripe_connect.rb b/app/models/spree/gateway/stripe_connect.rb index 2e5104e346..ca62c4a8fe 100644 --- a/app/models/spree/gateway/stripe_connect.rb +++ b/app/models/spree/gateway/stripe_connect.rb @@ -91,15 +91,8 @@ module Spree options[:currency] = gateway_options[:currency] options[:stripe_account] = stripe_account_id - if customer = creditcard.gateway_customer_profile_id - options[:customer] = customer - end - if token_or_card_id = creditcard.gateway_payment_profile_id - # The Stripe ActiveMerchant gateway supports passing the token directly as the creditcard parameter - # The Stripe ActiveMerchant gateway supports passing the customer_id and credit_card id - # https://github.com/Shopify/active_merchant/issues/770 - creditcard = token_or_card_id - end + creditcard = token_from_card_profile_ids(creditcard) + return money, creditcard, options end @@ -128,5 +121,27 @@ module Spree source.cc_type = CARD_TYPE_MAPPING[source.cc_type] if CARD_TYPE_MAPPING.include?(source.cc_type) source end + + def token_from_card_profile_ids(creditcard) + if token_or_card_id = creditcard.gateway_payment_profile_id + if customer = creditcard.gateway_customer_profile_id + # Assume the gateway_payment_profile_id is a Stripe card_id + # So generate a new token, using the customer_id and card_id + return tokenize_instance_customer_card(customer, token_or_card_id) + end + + # Assume the gateway_payment_profile_id is a token generated by StripeJS + return token_or_card_id + end + end + + def tokenize_instance_customer_card(customer, card) + begin + token = Stripe::Token.create({card: card, customer: customer}, { stripe_account: stripe_account_id}) + token.id + rescue Stripe::InvalidRequestError + # Not really sure what to do here.... + end + end end end diff --git a/app/models/spree/payment_decorator.rb b/app/models/spree/payment_decorator.rb index 0c87468034..715be6e670 100644 --- a/app/models/spree/payment_decorator.rb +++ b/app/models/spree/payment_decorator.rb @@ -80,5 +80,14 @@ module Spree refund_amount ||= credit_allowed >= order.outstanding_balance.abs ? order.outstanding_balance.abs : credit_allowed.abs refund_amount.to_f end + + def create_payment_profile + return unless source.is_a?(CreditCard) && + (source.number || source.gateway_payment_profile_id) && + source.gateway_customer_profile_id.nil? + payment_method.create_profile(self) + rescue ActiveMerchant::ConnectionError => e + gateway_error e + end end end diff --git a/spec/models/spree/gateway/stripe_connect_spec.rb b/spec/models/spree/gateway/stripe_connect_spec.rb new file mode 100644 index 0000000000..56cd7e6e41 --- /dev/null +++ b/spec/models/spree/gateway/stripe_connect_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Spree::Gateway::StripeConnect, type: :model do + let(:provider) do + double('provider').tap do |p| + p.stub(:purchase) + p.stub(:authorize) + p.stub(:capture) + end + end + + before do + Stripe.api_key = "sk_test_123456" + subject.stub(:options_for_purchase_or_auth).and_return(['money','cc','opts']) + subject.stub(:provider).and_return provider + end + + describe "#token_from_card_profile_ids" do + let(:creditcard) { double(:creditcard) } + context "when the credit card provided has a gateway_payment_profile_id" do + before do + allow(creditcard).to receive(:gateway_payment_profile_id) { "token_or_card_id123" } + allow(subject).to receive(:tokenize_instance_customer_card) { "tokenized" } + end + + context "when the credit card provided has a gateway_customer_profile_id" do + before { allow(creditcard).to receive(:gateway_customer_profile_id) { "customer_id123" } } + + it "requests a new token via tokenize_instance_customer_card" do + result = subject.send(:token_from_card_profile_ids, creditcard) + expect(result).to eq "tokenized" + end + end + + context "when the credit card provided does not have a gateway_customer_profile_id" do + before { allow(creditcard).to receive(:gateway_customer_profile_id) { nil } } + it "returns the gateway_payment_profile_id (assumed to be a token already)" do + result = subject.send(:token_from_card_profile_ids, creditcard) + expect(result).to eq "token_or_card_id123" + end + end + end + + context "when the credit card provided does not have a gateway_payment_profile_id" do + before { allow(creditcard).to receive(:gateway_payment_profile_id) { nil } } + + it "returns nil....?" do + result = subject.send(:token_from_card_profile_ids, creditcard) + expect(result).to be nil + end + end + end + + describe "#tokenize_instance_customer_card" do + let(:customer_id) { "customer123" } + let(:card_id) { "card123" } + let(:token_mock) { { id: "test_token123" } } + + before do + stub_request(:post, "https://api.stripe.com/v1/tokens") + .with(body: { "card"=>"card123", "customer"=>"customer123"}) + .to_return(body: JSON.generate(token_mock)) + end + + it "requests a new token for the customer and card from Stripe, and returns the id of the response" do + expect(subject.send(:tokenize_instance_customer_card, customer_id, card_id)).to eq token_mock[:id] + end + end +end