Last active
August 28, 2023 22:42
-
-
Save iloveitaly/3702091fbc22ae464a84436ea4ee3ff6 to your computer and use it in GitHub Desktop.
Point http://SuiteSync.io/ to use existing NetSuite customers when migrating to Stripe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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