Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions app/models/solidus_stripe/customer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module SolidusStripe
class Customer < ApplicationRecord
belongs_to :payment_method

# Source is supposed to be a user or an order and needs to respond to #email
belongs_to :source, polymorphic: true

def self.retrieve_or_create_stripe_customer_id(payment_method:, order:)
instance = find_or_initialize_by(payment_method: payment_method, source: order.user || order)

instance.stripe_id ||
instance.create_stripe_customer.tap { instance.update!(stripe_id: _1.id) }.id
end

def create_stripe_customer
payment_method.gateway.request { Stripe::Customer.create(email: source.email) }
end
end
end
7 changes: 5 additions & 2 deletions app/models/solidus_stripe/payment_intent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ def stripe_intent
end

def create_stripe_intent(stripe_intent_options)
customer = payment_method.customer_for(order)
stripe_customer_id = SolidusStripe::Customer.retrieve_or_create_stripe_customer_id(
payment_method: payment_method,
order: order
)

payment_method.gateway.request do
Stripe::PaymentIntent.create({
Expand All @@ -35,7 +38,7 @@ def create_stripe_intent(stripe_intent_options)
# avoid capturing the money before the order is completed.
capture_method: 'manual',
setup_future_usage: payment_method.preferred_setup_future_usage.presence,
customer: customer,
customer: stripe_customer_id,
metadata: { solidus_order_number: order.number },
}.merge(stripe_intent_options))
end
Expand Down
46 changes: 0 additions & 46 deletions app/models/solidus_stripe/payment_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,52 +52,6 @@ def gateway_class
end
end

concerning :Customer do
def customer_for(order)
if order.user
find_customer_for_user(order.user) || create_customer_for_user(order.user)
else
find_customer_for_order(order) || create_customer_for_order(order)
end
end

def find_customer_for_user(user)
gateway.request do
raise "unsupported email address: #{user.email.inspect}" if user.email.include?("'")

Stripe::Customer.search(
query: "metadata['solidus_user_id']:'#{user.id}' AND email:'#{user.email}'"
).first
end
end

def create_customer_for_user(user)
gateway.request do
Stripe::Customer.create(
email: user.email,
metadata: { solidus_user_id: user.id },
)
end
end

def find_customer_for_order(order)
gateway.request do
Stripe::Customer.search(
query: "metadata['solidus_order_number']:'#{order.number}'"
).first
end
end

def create_customer_for_order(order)
gateway.request do
Stripe::Customer.create(
email: order.email,
metadata: { solidus_order_number: order.number },
)
end
end
end

def skip_confirm_step?
preferred_stripe_intents_flow == 'payment' &&
preferred_skip_confirmation_for_payment_intent
Expand Down
7 changes: 5 additions & 2 deletions app/models/solidus_stripe/setup_intent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ def stripe_intent
end

def create_stripe_intent(stripe_intent_options)
customer = payment_method.customer_for(order)
stripe_customer_id = SolidusStripe::Customer.retrieve_or_create_stripe_customer_id(
payment_method: payment_method,
order: order
)

payment_method.gateway.request do
Stripe::SetupIntent.create({
customer: customer,
customer: stripe_customer_id,
usage: payment_method.preferred_setup_future_usage.presence,
metadata: { solidus_order_number: order.number },
}.merge(stripe_intent_options))
Expand Down
16 changes: 16 additions & 0 deletions db/migrate/20230313150008_create_solidus_stripe_customers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class CreateSolidusStripeCustomers < ActiveRecord::Migration[7.0]
def change
create_table :solidus_stripe_customers do |t|
t.references :payment_method, null: false, foreign_key: { to_table: :spree_payment_methods }

t.string :source_type
t.integer :source_id
Comment thread
elia marked this conversation as resolved.

t.string :stripe_id, index: true

t.timestamps

t.index [:payment_method_id, :source_type, :source_id], unique: true, name: :payment_method_and_source
end
end
end
10 changes: 10 additions & 0 deletions lib/solidus_stripe/testing_support/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,14 @@
association :payment_method, factory: :stripe_payment_method
slug { SecureRandom.hex(16) }
end

factory :stripe_customer, class: 'SolidusStripe::Customer' do
association :payment_method, factory: :stripe_payment_method
association :source, factory: :user
stripe_id { "cus_#{SecureRandom.uuid.delete('-')}" }

trait :guest do
association :source, factory: :order, email: 'guest@example.com', user: nil
end
end
end
47 changes: 47 additions & 0 deletions spec/models/solidus_stripe/customer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require 'solidus_stripe_spec_helper'

RSpec.describe SolidusStripe::Customer, type: :model do
describe ".retrieve_or_create_stripe_customer_id" do
context 'with an existing customer' do
it 'returns the customer_id' do
user = create(:user)
order = create(:order, user: user)
customer = create(:stripe_customer, stripe_id: 'cus_123', source: user)

expect(customer.source).to be_a(Spree::User)
expect(
described_class.retrieve_or_create_stripe_customer_id(order: order, payment_method: customer.payment_method)
).to eq('cus_123')
end
end

context 'without an existing customer' do
it 'creates the customer from a user' do
user = create(:user, email: 'registered@example.com')
order = create(:order, user: user)
payment_method = create(:stripe_payment_method)

stripe_customer = Stripe::Customer.construct_from(id: 'cus_123')
allow(Stripe::Customer).to receive(:create).with(email: 'registered@example.com').and_return(stripe_customer)

expect(
described_class.retrieve_or_create_stripe_customer_id(order: order, payment_method: payment_method)
).to eq('cus_123')
end

it 'creates the customer from a guest order' do
payment_method = create(:stripe_payment_method)
order = create(:order, user: nil, email: 'guest@example.com')

stripe_customer = Stripe::Customer.construct_from(id: 'cus_123')
allow(Stripe::Customer).to receive(:create).with(email: 'guest@example.com').and_return(stripe_customer)

expect(
described_class.retrieve_or_create_stripe_customer_id(order: order, payment_method: payment_method)
).to eq('cus_123')
end
end
end
end