Skip to content

Instantly share code, notes, and snippets.

@iloveitaly
Last active November 2, 2016 15:02
Show Gist options
  • Save iloveitaly/702f18299f2a446c7b3ee897cba93044 to your computer and use it in GitHub Desktop.
Save iloveitaly/702f18299f2a446c7b3ee897cba93044 to your computer and use it in GitHub Desktop.
Auto-pay NetSuite Invoices based on the due date using Stripe and http://SuiteSync.io/
# Author: <mike@suitesync.io>
require 'stripe'
require 'netsuite'
Stripe.api_key = ENV['STRIPE_KEY']
NetSuite.configure do
reset!
# NOTE that API versions > 2015_1 require a more complicated authentication setup
api_version '2015_1'
read_timeout 60 * 3
silent ENV['NETSUITE_SILENT'].nil? || ENV['NETSUITE_SILENT'] == 'true'
email ENV['NETSUITE_EMAIL']
password ENV['NETSUITE_PASSWORD']
account ENV['NETSUITE_ACCOUNT']
wsdl_domain 'webservices.na1.netsuite.com'
soap_header({
'platformMsgs:preferences' => {
'platformMsgs:ignoreReadOnlyFields' => true,
}
})
end
# http://stackoverflow.com/questions/16810211/netsuite-obtaining-invoice-balance
def amount_due_for_invoice(ns_invoice)
search = NetSuite::Records::Invoice.search(
criteria: {
basic: [
{
field: 'type',
operator: 'anyOf',
value: %w(_invoice)
},
{
field: 'mainLine',
value: true
},
{
field: 'internalIdNumber',
operator: 'equalTo',
value: ns_invoice.internal_id
}
]
},
columns: {
'tranSales:basic' => {
'platformCommon:internalId/' => {},
'platformCommon:amountRemaining' => {}
}
}
)
if search.results.size > 1
fail "invoice search on internalId should never return more than a single result"
end
search.results.first.attributes[:amount_remaining].to_f
end
def retrieve_stripe_customer_reference(ns_customer)
begin
Stripe::Customer.retrieve(ns_customer.external_id)
rescue Stripe::InvalidRequestError => e
return
end
end
def has_available_payment_source?(stripe_customer)
stripe_customer.sources.count > 0
end
def determine_payment_source(stripe_customer)
# depending on your business logic, you may want to choose a different payment source
stripe_customer.default_source
end
def update_memo(ns_invoice, memo)
NetSuite::Utilities.append_memo(ns_invoice, memo)
if !ns_invoice.update(memo: ns_invoice.memo)
fail "error updating memo"
end
end
def process_invoice(ns_invoice)
ns_customer = NetSuite::Utilities.get_record(NetSuite::Records::Customer, ns_invoice.entity.internal_id)
stripe_customer = retrieve_stripe_customer_reference(ns_customer)
if stripe_customer.nil?
puts "no stripe customer\t#{ns_customer.entity_id}\t#{ns_customer.internal_id}"
update_memo(ns_invoice, "Stripe: no linked customer")
# at this point, you can send an email to the customer asking them to input their payment credentials
# which would create a Stripe customer and link it to their NetSuite record
# via an app like https://github.com/iloveitaly/netsuite_invoice_payment_with_stripe
return
end
if !has_available_payment_source?(stripe_customer)
puts "no available payment methods#{ns_customer.entity_id}\t#{ns_customer.internal_id}"
update_memo(ns_invoice, "Stripe: no payment source")
# at this point, you can send an email to the customer asking them to input their payment credentials
# via an app like https://github.com/iloveitaly/netsuite_invoice_payment_with_stripe
return
end
amount_due = amount_due_for_invoice(ns_invoice)
payment_source = determine_payment_source(stripe_customer)
begin
charge = Stripe::Charge.create(
amount: (amount_due * 100.0).to_i,
currency: 'usd',
customer: stripe_customer.id,
source: payment_source,
# this description will be added to the memo field of the NetSuite customer payment
description: "NetSuite Batch Payment for #{ns_invoice.tran_id}",
metadata: {
# this metadata field instructs SuiteSync to create a CustomerPayment and apply it to the associated invoice
netsuite_invoice_id: ns_invoice.internal_id
# more metadata fields can be added to pass custom data over to the CustomerPayment
}
)
rescue Stripe::CardError => e
update_memo(ns_invoice, "Stripe: error charging payment source")
puts "error charging payment source\t#{ns_invoice.tran_id}\t#{payment_source}"
return
end
puts "invoice paid\t#{charge.id}\t#{ns_invoice.tran_id}\t"
# optionally update the suitesync auth code field to allow for automatic refund creation from CreditMemos
# ns_invoice.custom_field_list.custbody_suitesync_authorization_code = charge.id
# ns_invoice.update(custom_field_list: ns_invoice.custom_field_list)
end
current_unpaid_invoices = NetSuite::Records::Invoice.search(
basic: [
{
field: 'type',
operator: 'anyOf',
value: [
'_invoice'
]
},
{
field: 'status',
operator: 'anyOf',
value: ['_invoiceOpen']
},
{
field: 'dueDate',
operator: 'on',
value: DateTime.now
}
]
)
if !current_unpaid_invoices
fail "error running invoice search"
end
current_unpaid_invoices.results_in_batches do |batch|
batch.each do |ns_invoice|
process_invoice(ns_invoice)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment