Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow guest checkout #97

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ gem 'libnotify'
gem 'fuubar'
gem 'byebug'
gem 'pry-byebug'
# needed to address this issue: https://stackoverflow.com/a/35893625
gem 'rake', '< 11.0'

gemspec
3 changes: 1 addition & 2 deletions lib/spree/chimpy/controller_filters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def set_mailchimp_params
end

def mailchimp_params?
(!mc_eid.nil? || !mc_cid.nil?) &&
(!session[:order_id].nil? || !params[:record_mc_details].nil?)
!mc_eid.nil? || !mc_cid.nil?
end

def find_mail_chimp_params
Expand Down
124 changes: 86 additions & 38 deletions lib/spree/chimpy/interface/customer_upserter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,110 @@ module Interface
class CustomerUpserter
delegate :log, :store_api_call, to: Spree::Chimpy

attr_reader :order
def initialize(order)
@order = order
end
# CUSTOMER will be pulled first from the MC_EID if present on the order.source
# IF that is not found, customer will be found by our Customer ID
# IF that is not found, customer is created with the order email and our Customer ID

# CUSTOMER will be pulled first from the MC_EID if present on the
# order.source
# IF that is not found, customer will be found by our Customer ID assuming
# there is a user on the order, and their id is `customer_#{user.id}`.
# If that is not found, customer is searched via email address
# IF that is not found, customer is created with the order email and our
# Customer ID, or guest ID
def ensure_customer
# use the one from mail chimp or fall back to the order's email
# happens when this is a new user
customer_id = customer_id_from_eid(@order.source.email_id) if @order.source
customer_id || upsert_customer
lookup_customer_id_from_eid ||
lookup_customer_id_from_user_id ||
lookup_customer_id_from_email_address ||
upsert_customer
end

def self.mailchimp_customer_id(user_id)
"customer_#{user_id}"
def lookup_customer_id_from_eid
return unless order.source.try(:email_id)

email = Spree::Chimpy.list.email_for_id(order.source.email_id)
lookup_email_address email if email
end

def customer_id_from_eid(mc_eid)
email = Spree::Chimpy.list.email_for_id(mc_eid)
if email
begin
response = store_api_call
.customers
.retrieve(params: { "fields" => "customers.id", "email_address" => email })

data = response["customers"].first
data["id"] if data
rescue Gibbon::MailChimpError => e
nil
end
def lookup_customer_id_from_user_id
return unless order.user_id.present?
mailchimp_customer_id = customer_id_from_user_id order.user_id
begin
response = store_api_call.
customer(mailchimp_customer_id).
retrieve(params: { "fields" => "customers.id" })

data = response["customers"].first
data["id"] if data
rescue Gibbon::MailChimpError => _e
nil
end
end

def lookup_customer_id_from_email_address
lookup_email_address order.email
end

private

def lookup_email_address(email_address)
response = store_api_call.
customers.
retrieve params: \
{ "fields" => "customers.id", "email_address" => email_address }

data = response["customers"].first
data["id"] if data
rescue Gibbon::MailChimpError => _e
nil
end

def customer_id_from_user_id(user_id)
"customer_#{user_id}"
end

def mailchimp_customer_id
if order.user_id.present?
customer_id_from_user_id(order.user_id)
else
"guest_#{Digest::MD5.hexdigest email_from_order}"
end
end

def email_from_order
order.email.downcase
end

def first_name_from_order
order.name.split(" ").first
end

def last_name_from_order
order.name.split(" ")[1..-1].join(" ")
end

def upsert_customer
return unless @order.user_id
customer_id = mailchimp_customer_id
email = email_from_order

customer_id = self.class.mailchimp_customer_id(@order.user_id)
begin
response = store_api_call
.customers(customer_id)
.retrieve(params: { "fields" => "id,email_address"})
rescue Gibbon::MailChimpError => e
# Customer Not Found, so create them
response = store_api_call
.customers
.create(body: {
log "upserting customer: #{email}"
store_api_call.customers(customer_id).upsert body:
{
id: customer_id,
email_address: @order.email.downcase,
opt_in_status: Spree::Chimpy::Config.subscribe_to_list || false
})
email_address: email,
first_name: first_name_from_order,
last_name: last_name_from_order,
opt_in_status: Spree::Chimpy::Config.subscribe_to_list || false,
}
customer_id
rescue Gibbon::MailChimpError => e
log "failed to create customer:#{customer_id} with email: #{email}..."
log e
nil
end
customer_id
end

end
end
end
end
178 changes: 99 additions & 79 deletions spec/lib/customers_interface_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,124 +23,144 @@
Spree::Chimpy::Config.subscribe_to_list = true
end

describe ".ensure_customers" do

#TODO: Changed from skips sync when mismatch -
# Updated logic takes the customer attached to the mc_eid regardless of email matching order
# When no customer exists for that mc_eid, it will create the customer for the order email
# Should this remain due to v3.0 updates?
it "retrieves the customer id from the order source if it exists" do
order.source = Spree::Chimpy::OrderSource.new(email_id: 'id-abcd')
order.save

allow(interface).to receive(:customer_id_from_eid)
.with('id-abcd')
.and_return("customer_999")

expect(interface.ensure_customer).to eq "customer_999"
describe ".ensure_customer" do
# Cascading lookup of eid, own customer_id, email address, upsert
it "first tries the eid lookup" do
expect(interface).
to receive(:lookup_customer_id_from_eid).and_return(:foobar)
expect(interface.ensure_customer).to eq :foobar
end

context "when no customer from order source" do
before(:each) do
allow(interface).to receive(:customer_id_from_eid)
.with('id-abcd')
.and_return(nil)
describe "eid fails" do
before :each do
expect(interface).
to receive(:lookup_customer_id_from_eid).and_return(nil)
end

it "upserts the customer" do
allow(interface).to receive(:upsert_customer) { "customer_998" }

expect(interface.ensure_customer).to eq "customer_998"
it "then tries the customer_id lookup" do
expect(interface).
to receive(:lookup_customer_id_from_user_id).and_return(:foobar)
expect(interface.ensure_customer).to eq :foobar
end

it "returns nil if guest checkout" do
order.user_id = nil
expect(interface.ensure_customer).to be_nil
describe "customer_id lookup fails" do
before :each do
expect(interface).
to receive(:lookup_customer_id_from_user_id).and_return(nil)
end

it "then tries the email address lookup" do
expect(interface).to \
receive(:lookup_customer_id_from_email_address).and_return(:foobar)
expect(interface.ensure_customer).to eq :foobar
end

describe "email address lookup fails" do
before :each do
expect(interface).to \
receive(:lookup_customer_id_from_email_address).and_return(nil)
end

it "then upserts the customer" do
expect(interface).
to receive(:upsert_customer).and_return(:foobar)
expect(interface.ensure_customer).to eq :foobar
end
end
end
end
end

describe "#upsert_customer" do
describe "#mailchimp_customer_id" do
it "picks customer_* for orders with customers" do
expect(interface.send(:mailchimp_customer_id)).
to eq "customer_#{order.user_id}"
end

before(:each) do
allow(store_api).to receive(:customers)
.and_return(customers_api)
allow(store_api).to receive(:customers)
.with("customer_#{order.user_id}")
.and_return(customer_api)
it "picks guest_* for guest orders" do
order.user = nil
expect(interface.send(:mailchimp_customer_id)).
to start_with("guest_")
end
end

it "retrieves based on the customer_id" do
expect(customer_api).to receive(:retrieve)
.with(params: { "fields" => "id,email_address"})
.and_return({ "id" => "customer_#{order.user_id}", "email_address" => order.email})
describe "#upsert_customer" do
let(:customer_id) { "customer_#{order.user_id}" }

before :each do
allow(store_api).to receive(:customers).with(anything).
and_return customers_api
allow(customers_api).to receive(:upsert).and_return(nil)
end

customer_id = interface.send(:upsert_customer)
expect(customer_id).to eq "customer_#{order.user_id}"
it "upserts the customer" do
expect(interface.send(:upsert_customer)).to eq "customer_#{order.user_id}"
end

it "creates the customer when lookup fails" do
allow(customer_api).to receive(:retrieve)
.and_raise(Gibbon::MailChimpError)
it "sets first_name and last_name" do
expect(customers_api).to receive(:upsert) do |h|
body = h[:body]
expect(body[:first_name]).to be_present
expect(body[:last_name]).to be_present
end

interface.send :upsert_customer
end

expect(customers_api).to receive(:create)
.with(:body => {
id: "customer_#{order.user_id}",
email_address: order.email.downcase,
opt_in_status: true
})
it "picks the id from #mailchimp_customer_id" do
expect(interface).to receive(:mailchimp_customer_id).and_return "foobar"

customer_id = interface.send(:upsert_customer)
expect(customer_id).to eq "customer_#{order.user_id}"
expect(interface.send(:upsert_customer)).to eq "foobar"
end

it "honors subscribe_to_list settings" do
Spree::Chimpy::Config.subscribe_to_list = false

allow(customer_api).to receive(:retrieve)
.and_raise(Gibbon::MailChimpError)

expect(customers_api).to receive(:create) do |h|
expect(customers_api).to receive(:upsert) do |h|
expect(h[:body][:opt_in_status]).to eq false
end

interface.send(:upsert_customer)
end
end

describe "#customer_id_from_eid" do
describe "#lookup_customer_id_from_eid" do
let(:email) { "[email protected]" }
before(:each) do
allow(store_api).to receive(:customers) { customers_api }
end

it "returns based on the mailchimp email address when found" do
allow(list).to receive(:email_for_id).with("id-abcd")
.and_return(email)

expect(customers_api).to receive(:retrieve)
.with(params: { "fields" => "customers.id", "email_address" => email})
.and_return({ "customers" => [{"id" => "customer_xyz"}] })

id = interface.customer_id_from_eid("id-abcd")
expect(id).to eq "customer_xyz"
it "does not lookup and returns nil if there is no source" do
expect(store_api).to_not receive(:customers)
interface.order.source = nil
expect(interface.lookup_customer_id_from_eid).to be_nil
end

it "is nil if email for id not found" do
allow(list).to receive(:email_for_id).with("id-abcd")
.and_return(nil)
describe "with an email source" do
it "is nil if email for id not found" do
allow(list).to receive(:email_for_id).with(email_id).and_return(nil)

expect(interface.customer_id_from_eid("id-abcd")).to be_nil
end
expect(interface.lookup_customer_id_from_eid).to be_nil
end

it "is nil if email not found among customers" do
allow(list).to receive(:email_for_id)
.with("id-abcd")
.and_return(email)
describe "with a found email_id" do
before :each do
allow(list).to receive(:email_for_id).with(email_id).and_return(email)
end

expect(customers_api).to receive(:retrieve)
.and_raise(Gibbon::MailChimpError)
it "is nil if email not found among customers" do
expect(customers_api).to receive(:retrieve).
and_raise(Gibbon::MailChimpError)

expect(interface.customer_id_from_eid("id-abcd")).to be_nil
expect(interface.lookup_customer_id_from_eid).to be_nil
end

it "returns the customer_id if found" do
expect(customers_api).to receive(:retrieve).and_return \
"customers" => [{ "id" => "customer_123" }]
expect(interface.lookup_customer_id_from_eid).to eq "customer_123"
end
end
end
end
end
end