Skip to content

Commit

Permalink
Keep consistent customer across purchases
Browse files Browse the repository at this point in the history
  • Loading branch information
brchristian committed Aug 19, 2020
1 parent a7d01ca commit 35b2cf1
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 71 deletions.
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
102 changes: 53 additions & 49 deletions app/models/spree/payment_method/stripe_credit_card.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,39 +103,53 @@ 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

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: response.params['id'],
gateway_payment_profile_id: response.params['default_source'] || response.params['default_card']
)
end
else
payment.send(:gateway_error, response.message)
# 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
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
bill_address = user&.bill_address || order.bill_address
ship_address = user&.ship_address || order.ship_address
Stripe::Customer.create({
address: stripe_address_hash(bill_address),
email: user&.email || order.email,
# full_name is deprecated in favor of name as of Solidus 3.0
name: bill_address.try(:name) || bill_address&.full_name,
phone: bill_address&.phone,
shipping: {
address: stripe_address_hash(ship_address),
# full_name is deprecated in favor of name as of Solidus 3.0
name: ship_address.try(:name) || ship_address&.full_name,
phone: ship_address&.phone
}.reject { |_, v| v.blank? }
}.reject { |_, v| v.blank? })
end

# Create new Stripe card / payment method and attach to
# (new or existing) Stripe profile
if source.gateway_payment_profile_id&.starts_with?('pm_')
stripe_payment_method = Stripe::PaymentMethod.attach(source.gateway_payment_profile_id, customer: stripe_customer)
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
)
elsif 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
)
end
end

Expand Down Expand Up @@ -165,25 +179,15 @@ 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
def stripe_address_hash(address)
{
city: address&.city,
country: address&.country&.iso,
line1: address&.address1,
line2: address&.address2,
postal_code: address&.zipcode,
state: address&.state_text
}.compact
end

def update_source!(source)
Expand Down
50 changes: 28 additions & 22 deletions spec/models/spree/payment_method/stripe_credit_card_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@
let(:source) { Spree::CreditCard.new }

let(:bill_address) { nil }
let(:ship_address) { nil }

let(:order) {
double('Spree::Order',
email: email,
bill_address: bill_address,
currency: 'USD',
number: 'NUMBER',
total: 10.99
)
total: 10.99).tap do |o|
allow(o).to receive(:user)
allow(o).to receive(:bill_address).and_return(bill_address)
allow(o).to receive(:ship_address).and_return(ship_address)
end
}

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

let(:gateway) do
Expand Down Expand Up @@ -89,35 +94,36 @@
address2: 'Apt 303',
city: 'Suzarac',
zipcode: '95671',
state: double('Spree::State', name: 'Oregon'),
country: double('Spree::Country', name: 'United States'))
state_text: 'OR',
country: double('Spree::Country', name: 'United States', iso: 'US'),
full_name: 'John Smith',
phone: '555-555-5555')
}

it 'stores the 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(
address: {
address1: '123 Happy Road',
address2: 'Apt 303',
line1: '123 Happy Road',
line2: 'Apt 303',
city: 'Suzarac',
zip: '95671',
state: 'Oregon',
country: 'United States'
}
}).and_return double.as_null_object
postal_code: '95671',
state: 'OR',
country: 'US'
},
email: email,
name: 'John Smith',
phone: '555-555-5555'
).and_return double.as_null_object

subject.create_profile payment
end
end

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,
}).and_return double.as_null_object
expect(Stripe::Customer).to receive(:create).with(
email: email
).and_return double.as_null_object

subject.create_profile payment
end
Expand Down Expand Up @@ -154,7 +160,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 35b2cf1

Please sign in to comment.