Last active
November 2, 2016 15:02
-
-
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/
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
# 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