Point http://SuiteSync.io/ to use existing NetSuite customers when migrating to Stripe
# Mike Bianco <mike@suitesync.io> | |
# Description: Script to link Stripe customers to NetSuite customers using email as the primary key | |
# Link: https://gist.github.com/iloveitaly/3702091fbc22ae464a84436ea4ee3ff6 | |
# Usage: | |
# | |
# Requires the following environment variables: | |
# | |
# STRIPE_KEY | |
# NETSUITE_EMAIL | |
# NETSUITE_PASSWORD | |
# NETSUITE_ACCOUNT | |
# | |
# gem install netsuite stripe | |
# ruby match_stripe_customers_to_netsuite.rb | tee stripe_migration.log | |
# ruby match_stripe_customers_to_netsuite.rb list_of_customers_to_match.csv | tee stripe_migration.log | |
# | |
# output is tab-deliminated for easy copy/paste to google sheets | |
# helpful CLI: | |
# ag '(customer linked|already linked)' stripe_migration.log|pbcopy | |
require 'stripe' | |
require 'netsuite' | |
require 'csv' | |
STDOUT.sync = true | |
Stripe.api_key = ENV['STRIPE_KEY'] | |
Stripe.max_network_retries = 10 | |
# TODO specify that this is SS migration | |
NetSuite.configure do | |
reset! | |
# NOTE that API versions > 2015_1 require a more complicated authentication setup | |
# `3949508.suitetalk.api.netsuite.com` | |
api_version '2015_1' | |
read_timeout 60 * 3 | |
silent ENV['NETSUITE_SILENT'].nil? || ENV['NETSUITE_SILENT'] == 'true' | |
wsdl_domain '3949508.suitetalk.api.netsuite.com' | |
email ENV['NETSUITE_EMAIL'] | |
password ENV['NETSUITE_PASSWORD'] | |
account ENV['NETSUITE_ACCOUNT'] | |
role '18' | |
soap_header({ | |
'platformMsgs:preferences' => { | |
'platformMsgs:ignoreReadOnlyFields' => true, | |
} | |
}) | |
end | |
def update_netsuite_customer_external_id(ns_customer, stripe_customer) | |
if ns_customer.external_id.nil? || ns_customer.external_id.empty? | |
ns_customer.external_id = stripe_customer.id | |
if ns_customer.comments.nil? || ns_customer.comments.empty? | |
ns_customer.comments = "Stripe: #{stripe_customer.id}" | |
else | |
puts "#{stripe_customer.id}\tskipping memo update" | |
end | |
NetSuite::Utilities.backoff { ns_customer.update(comments: ns_customer.comments) } | |
end | |
end | |
$stripe_match_field = 'companyName' | |
$stripe_match_field = 'entityId' | |
# $stripe_match_field = 'email' | |
# $stripe_match_field = 'custentity_es_stripe_customer_id' | |
$skip_customers_without_payment = false | |
$whitelist_field_required = true | |
$update_customer_external_id = true | |
$update_customer_external_id_if_already_mapped = false | |
def is_custom_field?(field_name) | |
field_name.to_s =~ /^(custentity|custbody|custitem|custcol)/ | |
end | |
def process_stripe_customer(stripe_customer) | |
if !stripe_customer.metadata['netsuite_customer_id'].nil? | |
puts "#{stripe_customer.id}\talready linked" | |
if $whitelist_field_required && !stripe_customer.metadata['netsuite_allow_integration'] | |
stripe_customer.metadata['netsuite_allow_integration'] = true | |
stripe_customer.save | |
end | |
if $update_customer_external_id_if_already_mapped | |
# NOTE you may want to add the external ID to the record. This can help with scripts, etc on the NetSuite side | |
ns_customer = NetSuite::Utilities.get_record(NetSuite::Records::Customer, stripe_customer.metadata['netsuite_customer_id']) | |
if ns_customer | |
update_netsuite_customer_external_id(ns_customer, stripe_customer) | |
end | |
end | |
return | |
end | |
if $skip_customers_without_payment | |
charges = stripe_customer.charges | |
if charges.data.select(&:paid).empty? | |
puts "#{stripe_customer.id}\thas no payments" | |
return | |
end | |
end | |
stripe_customer_email = stripe_customer.email | |
stripe_customer_name = stripe_customer.description | |
# NOTE an alternative approach here is to match by companyName | |
# stripe_match_data = stripe_customer.id | |
# stripe_match_data = stripe_customer_name | |
# stripe_match_data = stripe_customer_email | |
stripe_match_data = stripe_customer_email || stripe_customer_name | |
if stripe_match_data.nil? | |
puts "#{stripe_customer.id}\tmatch field blank\t#{stripe_customer_email}\t#{stripe_match_data}" | |
return | |
end | |
# the NetSuite `contains` operator is case insensative, but it is sensative to whitespace | |
stripe_match_data = stripe_match_data.strip | |
search_criteria = if is_custom_field?($stripe_match_field) | |
{ | |
field: 'customFieldList', | |
value: [ | |
{ | |
field: $stripe_match_field, | |
type: 'SearchStringCustomField', | |
operator: 'is', | |
value: stripe_match_data | |
} | |
] | |
} | |
else | |
{ | |
field: $stripe_match_field, | |
operator: 'contains', | |
value: stripe_match_data | |
} | |
end | |
# find NetSuite customer by a match field | |
search = NetSuite::Utilities.backoff { NetSuite::Records::Customer.search( | |
basic: [ | |
{ | |
field: 'isInactive', | |
value: false, | |
}, | |
{ | |
field: 'stage', | |
operator: 'anyOf', | |
value: %w(_customer) | |
}, | |
search_criteria | |
], | |
preferences: { | |
page_size: 10, | |
body_fields_only: true | |
} | |
) } | |
if search && !search.results.empty? | |
if search.results.size > 1 | |
customer_matches = search.results.select { |c| c.stage == '_customer' } | |
if customer_matches.size == 1 | |
ns_customer = customer_matches.first | |
else | |
puts "#{stripe_customer.id}\tmultiple matches\t#{stripe_customer_email}\t#{stripe_match_data}\t#{search.results.map(&:entity).join(',')}" | |
return | |
end | |
else | |
ns_customer = search.results.first | |
end | |
# if you are translating data from Stripe livemode to NS sandbox, you'll need to use the `netsuite_sandbox_customer_id` | |
stripe_customer.metadata['netsuite_customer_id'] = ns_customer.internal_id | |
stripe_customer.save | |
if $update_customer_external_id | |
# NOTE optionally link customer on the netsuite side | |
# this is not required by SuiteSync and is available as an optional feature | |
update_netsuite_customer_external_id(ns_customer, stripe_customer) | |
end | |
puts "#{stripe_customer.id}\tcustomer linked\t#{stripe_customer_email}\t#{stripe_match_data}" | |
else | |
puts "#{stripe_customer.id}\tno match\t#{stripe_customer_email}\t#{stripe_match_data}" | |
end | |
end | |
if ARGV.length > 0 | |
puts "pulling customers from CSV..." | |
# CSV Mapping: stripe_customer_id: cus_* | |
csv_mapping = CSV.read(ARGV.first, headers: true) | |
csv_mapping.each do |csv_line| | |
stripe_customer_id = csv_line['stripe_customer_id'] | |
stripe_customer = Stripe::Customer.retrieve(stripe_customer_id) | |
process_stripe_customer(stripe_customer) | |
end | |
else | |
Stripe::Customer.list(limit: 100).auto_paging_each do |stripe_customer| | |
process_stripe_customer(stripe_customer) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment