Skip to content

Commit

Permalink
refactor create_profile to use Stripe gem
Browse files Browse the repository at this point in the history
  • Loading branch information
brchristian committed Aug 15, 2020
1 parent 5d43fed commit 762aae8
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 77 deletions.
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ group :development, :test do
gem "pry-rails"
gem "ffaker"
gem "rails-controller-testing"
gem "stripe"
end

gemspec
Expand Down
35 changes: 35 additions & 0 deletions app/decorators/models/spree/credit_card_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Spree
module CreditCardDecorator
def cc_type=(type)
# See https://stripe.com/docs/api/cards/object#card_object-brand,
# active_merchant/lib/active_merchant/billing/credit_card.rb,
# and active_merchant/lib/active_merchant/billing/credit_card_methods.rb
# (And see also the Solidus docs at core/app/models/spree/credit_card.rb,
# which indicate that Solidus uses ActiveMerchant conventions by default.)
self[:cc_type] = case type
when 'American Express'
'american_express'
when 'Diners Club'
'diners_club'
when 'Discover'
'discover'
when 'JCB'
'jcb'
when 'MasterCard'
'master'
when 'UnionPay'
'unionpay'
when 'Visa'
'visa'
when 'Unknown'
super('')
else
super(type)
end
end

::Spree::CreditCard.prepend(self)
end
end
97 changes: 42 additions & 55 deletions app/models/spree/payment_method/stripe_credit_card.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,47 +103,55 @@ def cancel(response_code)
def create_profile(payment)
return unless payment.source.gateway_customer_profile_id.nil?

options = {
email: payment.order.email,
login: preferred_secret_key,
}.merge! address_for(payment)
order = payment.order
user = payment.source.user || order.user

source = update_source!(payment.source)
if source.number.blank? && source.gateway_payment_profile_id.present?
if v3_intents?
creditcard = ActiveMerchant::Billing::StripeGateway::StripePaymentToken.new('id' => source.gateway_payment_profile_id)
else
creditcard = source.gateway_payment_profile_id
end
else
creditcard = source
end

user_stripe_payment_sources = payment.source.user&.wallet&.wallet_payment_sources&.select do |wps|
# Check to see whether a user's previous payment sources
# are linked to a Stripe account
user_stripe_payment_sources = user&.wallet&.wallet_payment_sources&.select do |wps|
wps.payment_source.payment_method.type == 'Spree::PaymentMethod::StripeCreditCard'
end
if user_stripe_payment_sources.present?
customer_id = user_stripe_payment_sources.map {|ps| ps.payment_source&.gateway_customer_profile_id }.compact.last
options[:customer] = customer_id
end

response = gateway.store(creditcard, options)
if response.success?
if v3_intents?
payment.source.update!(
cc_type: payment.source.cc_type,
gateway_customer_profile_id: response.params['customer'],
gateway_payment_profile_id: response.params['id']
)
else
payment.source.update!(
cc_type: payment.source.cc_type,
gateway_customer_profile_id: options[:customer] ? response.params['customer'] : response.params['id'],
gateway_payment_profile_id: options[:customer] ? response.params['id'] : response.params['default_source'] || response.params['default_card']
)
end
stripe_customer = if user_stripe_payment_sources.present?
customer_id = user_stripe_payment_sources.map { |ps| ps.payment_source&.gateway_customer_profile_id }.compact.last
Stripe::Customer.retrieve(customer_id)
else
payment.send(:gateway_error, response.message)
Stripe::Customer.create({}.tap do |customer|
customer[:email] = user&.email || order.email
address = user&.bill_address || payment.bill_address
if address.present?
customer[:address] = {
city: address.city,
country: address.country&.iso,
line1: address.address1,
line2: address.address2,
postal_code: address.zipcode,
state: address.state_text
}
customer[:name] = address.try(:name) || address.full_name
# full_name is deprecated in favor of name as of Solidus 3.0
end
end)
end

# Create new Stripe card / payment method and attach to
# (new or existing) Stripe profile
if source.gateway_payment_profile_id&.starts_with?('tok_')
stripe_card = Stripe::Customer.create_source(stripe_customer.id, { source: source.gateway_payment_profile_id })
payment.source.update!(
cc_type: stripe_card.brand,
gateway_customer_profile_id: stripe_customer.id,
gateway_payment_profile_id: stripe_card.id
)
elsif source.gateway_payment_profile_id&.starts_with?('pm_')
stripe_payment_method = Stripe::PaymentMethod.attach(source.gateway_payment_profile_id, { customer: stripe_customer.id })
payment.source.update!(
cc_type: stripe_payment_method.card.brand,
gateway_customer_profile_id: stripe_customer.id,
gateway_payment_profile_id: stripe_payment_method.id
)
end
end

Expand Down Expand Up @@ -173,27 +181,6 @@ def options_for_purchase_or_auth(money, creditcard, transaction_options)
[money, creditcard, options]
end

def address_for(payment)
{}.tap do |options|
if address = payment.order.bill_address
options[:address] = {
address1: address.address1,
address2: address.address2,
city: address.city,
zip: address.zipcode
}

if country = address.country
options[:address][:country] = country.name
end

if state = address.state
options[:address].merge!(state: state.name)
end
end
end
end

def update_source!(source)
source.cc_type = CARD_TYPE_MAPPING[source.cc_type] if CARD_TYPE_MAPPING.include?(source.cc_type)
source
Expand Down
1 change: 1 addition & 0 deletions solidus_stripe.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
spec.add_dependency 'solidus_core', ['>= 2.3', '< 3']
spec.add_dependency 'solidus_support', '~> 0.5'
spec.add_dependency 'activemerchant', '>= 1.100'
spec.add_dependency 'stripe'

spec.add_development_dependency 'solidus_dev_support'
end
20 changes: 14 additions & 6 deletions spec/features/stripe_customer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,14 @@

user_card = user_sources.first.payment_source
expect(user_card.gateway_customer_profile_id).to start_with 'cus_'
expect(user_card.gateway_payment_profile_id).to start_with 'card_'
expect(user_card.gateway_payment_profile_id).to start_with (preferred_v3_intents ? 'pm_' : 'card_')

stripe_customer = Stripe::Customer.retrieve(user_card.gateway_customer_profile_id)
expect(stripe_customer[:email]).to eq(user.email)
expect(stripe_customer[:sources][:total_count]).to eq(1)
expect(stripe_customer[:sources][:data].first[:customer]).to eq(user_card.gateway_customer_profile_id)
expect(stripe_customer[:sources][:data].first[:id]).to eq(user_card.gateway_payment_profile_id)
stripe_customer_cards = Stripe::PaymentMethod.list(customer: stripe_customer.id, type: 'card')
expect(stripe_customer_cards.count).to eq(1)
expect(stripe_customer_cards.first.customer).to eq(user_card.gateway_customer_profile_id)
expect(stripe_customer_cards.first.id).to eq(user_card.gateway_payment_profile_id)

expect(user.orders.map { |o| o.payments.valid.first.source.gateway_payment_profile_id }.uniq.size).to eq(1)
expect(user.orders.map { |o| o.payments.valid.first.source.gateway_customer_profile_id }.uniq.size).to eq(1)
Expand All @@ -141,7 +142,7 @@
user_cards = user.credit_cards
expect(user_cards.size).to eq(2)
expect(user_cards.pluck(:gateway_customer_profile_id)).to all( start_with 'cus_' )
expect(user_cards.pluck(:gateway_payment_profile_id)).to all( start_with 'card_' )
expect(user_cards.pluck(:gateway_payment_profile_id)).to all( start_with (preferred_v3_intents ? 'pm_' : 'card_'))
expect(user_cards.last.gateway_customer_profile_id).to eq(user_cards.first.gateway_customer_profile_id)
expect(user_cards.pluck(:gateway_customer_profile_id).uniq.size).to eq(1)

Expand All @@ -153,7 +154,7 @@
expect(user.orders.map { |o| o.payments.valid.first.source.gateway_customer_profile_id }.uniq.size).to eq(1)

stripe_customer = Stripe::Customer.retrieve(user_cards.last.gateway_customer_profile_id)
stripe_customer_cards = Stripe::PaymentMethod.list({ customer: stripe_customer.id, type: 'card' })
stripe_customer_cards = Stripe::PaymentMethod.list(customer: stripe_customer.id, type: 'card')
expect(stripe_customer_cards.count).to eq(2)
expect(stripe_customer_cards.data.map { |card| card.id }).to match_array(user.orders.map { |o| o.payments.valid.first.source.gateway_payment_profile_id }.uniq)
expect(stripe_customer_cards.data.map { |card| card.id }).to match_array(user_cards.pluck(:gateway_payment_profile_id))
Expand All @@ -173,4 +174,11 @@

it_behaves_like "Maintain Consistent Stripe Customer Across Purchases"
end

context 'when using Stripe V3 API library with Intents' do
let(:preferred_v3_elements) { false }
let(:preferred_v3_intents) { true }

it_behaves_like "Maintain Consistent Stripe Customer Across Purchases"
end
end
35 changes: 20 additions & 15 deletions spec/models/spree/payment_method/stripe_credit_card_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@
currency: 'USD',
number: 'NUMBER',
total: 10.99
)
).tap do |o|
allow(o).to receive(:user)
end
}

let(:payment) {
double('Spree::Payment',
source: source,
order: order,
amount: order.total
)
).tap do |p|
allow(p).to receive(:bill_address).and_return(bill_address)
allow(p).to receive(:gateway_error)
end
}

let(:gateway) do
Expand Down Expand Up @@ -90,21 +95,22 @@
city: 'Suzarac',
zipcode: '95671',
state: double('Spree::State', name: 'Oregon'),
country: double('Spree::Country', name: 'United States'))
country: double('Spree::Country', name: 'United States', iso: 'US'),
state_text: 'OR',
full_name: 'John Smith')
}

it 'stores the bill address with the gateway' do
expect(subject.gateway).to receive(:store).with(payment.source, {
expect(Stripe::Customer).to receive(:create).with({
email: email,
login: secret_key,

name: 'John Smith',
address: {
address1: '123 Happy Road',
address2: 'Apt 303',
line1: '123 Happy Road',
line2: 'Apt 303',
city: 'Suzarac',
zip: '95671',
state: 'Oregon',
country: 'United States'
postal_code: '95671',
state: 'OR',
country: 'US'
}
}).and_return double.as_null_object

Expand All @@ -114,9 +120,8 @@

context 'with an order that does not have a bill address' do
it 'does not store a bill address with the gateway' do
expect(subject.gateway).to receive(:store).with(payment.source, {
email: email,
login: secret_key,
expect(Stripe::Customer).to receive(:create).with({
email: email
}).and_return double.as_null_object

subject.create_profile payment
Expand Down Expand Up @@ -154,7 +159,7 @@
let(:bill_address) { nil }

it 'stores the profile_id as a card' do
expect(subject.gateway).to receive(:store).with(source.gateway_payment_profile_id, anything).and_return double.as_null_object
expect(Stripe::Customer).to receive(:create_source).with(anything, source: source.gateway_payment_profile_id).and_return double.as_null_object

subject.create_profile payment
end
Expand Down
4 changes: 4 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
# Requires card input helper defined in lib/solidus_stripe/testing_support/card_input_helper.rb
require 'solidus_stripe/testing_support/card_input_helper'

# Stripe config
require 'stripe'
Stripe.api_key = 'sk_test_VCZnDv3GLU15TRvn8i2EsaAN'

RSpec.configure do |config|
config.infer_spec_type_from_file_location!
FactoryBot.find_definitions
Expand Down

0 comments on commit 762aae8

Please sign in to comment.