Skip to content

Instantly share code, notes, and snippets.

@iloveitaly
Last active August 28, 2023 22:42
Show Gist options
  • Save iloveitaly/3702091fbc22ae464a84436ea4ee3ff6 to your computer and use it in GitHub Desktop.
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
# 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