UNPKG

paypal-integrations-intacct

Version:

Integration between paypal and intacct using hapi.

449 lines 19.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); const joi = require("joi"); const later = require("later"); const paypal_rest_api_1 = require("paypal-rest-api"); const intacct_1 = require("./intacct"); __export(require("./intacct")); exports.intacctInvoiceExtend = { PAYPALERROR: "", PAYPALINVOICEID: "", PAYPALINVOICESTATUS: "", PAYPALINVOICEURL: "", PAYPALINVOICING: "", }; class HapiPayPalIntacctInvoicing { constructor() { this.register = (server, options, next) => { this.server = server; this.intacct.setServer(this.server); this.paypal = server.plugins["hapi-paypal"].paypal; this.options = options; this.server.log(["info", "paypal-intacct", "options"], this.options); return this.init() .then(() => next()) .catch((err) => { throw err; }); }; this.register.attributes = { name: "hapi-paypal-intacct-invoicing", }; this.intacct = new intacct_1.HapiIntacctInvoicing(); } get intacct() { return this._intacct; } set intacct(intacct) { this._intacct = intacct; } get paypal() { return this._paypal; } set paypal(paypal) { this._paypal = paypal; } get server() { return this._server; } set server(server) { this._server = server; } get options() { return this._options; } set options(options) { options = JSON.parse(JSON.stringify(options)); const optionsSchema = joi.object().keys({ cron: joi.object().keys({ create: joi.object().keys({ auto: joi.boolean().default(true) .error(new Error("Invalid INTACCT_INVOICE_CREATE_AUTO environment variable")), latertext: joi.string().default("every 1 hour"), }).optional(), refund: joi.object().keys({ auto: joi.boolean().default(true) .error(new Error("Invalid INTACCT_INVOICE_REFUND_AUTO environment variable")), latertext: joi.string().default("every 1 day"), }).optional(), }), merchant: paypal_rest_api_1.invoiceBillingInfoSchema.required(), paymentaccounts: joi.object().keys({ currencies: joi.object().default({}), default: joi.string().required() .error(new Error("Invalid INTACCT_INVOICE_PAYMENT_DEFAULT_ACCOUNT environment variable")), }).optional(), reminderDays: joi.number().default(15), startDate: joi.string().regex(/\d{1,2}\/\d{1,2}\/\d{4}/).required() .error(new Error("Invalid INTACCT_INVOICE_START_DATE environment variable")), }); const validate = joi.validate(options, optionsSchema); if (validate.error) { throw validate.error; } this._options = validate.value; } webhookHandler(webhook) { return __awaiter(this, void 0, void 0, function* () { const intacctInvoice = { PAYPALERROR: "", }; switch (webhook.event_type) { case "INVOICING.INVOICE.REFUNDED": break; case "INVOICING.INVOICE.CANCELLED": break; case "INVOICING.INVOICE.PAID": try { yield this.createPayment(webhook.resource.invoice); } catch (err) { intacctInvoice.PAYPALERROR = err.message; } break; default: } intacctInvoice.PAYPALINVOICESTATUS = webhook.resource.invoice.status; yield this.intacct.update(webhook.resource.invoice.reference, intacctInvoice); }); } createPayment(invoice) { return __awaiter(this, void 0, void 0, function* () { if (invoice.status !== "PAID") { throw new Error("Invalid Status"); } if (!invoice.payments || invoice.payments.length < 1) { throw new Error("No recorded PayPal Payments. This should not happen and is most likely an issue with the paypal api."); } const account = this.options.paymentaccounts.currencies[invoice.total_amount.currency] || this.options.paymentaccounts.default; try { const payment = { customerid: invoice.billing_info[0].additional_info, paymentamount: invoice.total_amount.value, bankaccountid: account, refid: invoice.payments[invoice.payments.length - 1].transaction_id, paymentmethod: "Credit Card", arpaymentitem: [{ invoicekey: invoice.reference, amount: invoice.total_amount.value, }], }; yield this.intacct.createPayment(payment); this.server.log(["info", "paypal-intacct", "invoice", "payment"], payment); } catch (err) { const error = JSON.parse(err.message); if (error.length === 1 && error[0].errorno !== "BL03000130") { throw err; } } }); } validateAccounts() { return __awaiter(this, void 0, void 0, function* () { const configAccounts = []; if (!this.options.paymentaccounts) { return; } if (this.options.paymentaccounts.default) { configAccounts.push(this.options.paymentaccounts.default); } if (this.options.paymentaccounts.currencies) { const keys = Object.keys(this.options.paymentaccounts.currencies); keys.forEach((key) => configAccounts.push(this.options.paymentaccounts.currencies[key])); } const accounts = yield this.intacct.listAccounts(); configAccounts.forEach((account) => { if (!account) { return; } const filteredAccounts = accounts.filter((faccount) => { return faccount.BANKACCOUNTID === account; }); if (filteredAccounts.length < 1) { throw new Error(`Intacct Payment Account ${account} configured but does not exist in Intacct`); } }); }); } validateKeys() { return __awaiter(this, void 0, void 0, function* () { const inspect = yield this.intacct.inspect(); HapiPayPalIntacctInvoicing.intacctKeys.forEach((key) => { if ((inspect).indexOf(key) === -1) { throw new Error(`${key} not defined. Add the key to the Intacct Invoice object.`); } }); }); } refundInvoicesSync() { return __awaiter(this, void 0, void 0, function* () { let query = process.env.INTACCT_INVOICE_REFUND_QUERY || `RAWSTATE = 'V' AND PAYPALINVOICESTATUS NOT IN ('REFUNDED', 'CANCELLED')`; if (!this.options.cron.refund.auto) { query += ` AND PAYPALINVOICING = 'T'`; } query += ` AND WHENCREATED > '${this.options.startDate}'`; const invoices = yield this.intacct.query(query); this.server.log(["info", "paypal-intacct", "invoice", "refundInvoicesSync"], { count: invoices.length, invoices: invoices.map((inv) => inv.RECORDID) }); for (const invoice of invoices) { try { yield this.refundInvoiceSync(invoice); } catch (err) { this.server.log("error", `refundInvoicesSync | ${err.message}`); } } }); } refundInvoiceSync(invoice) { return __awaiter(this, void 0, void 0, function* () { const paypalInvoice = yield this.paypal.invoice.get(invoice.PAYPALINVOICEID); const intacctInvoice = { PAYPALERROR: "", }; if (paypalInvoice.model.payments) { for (const payment of paypalInvoice.model.payments) { try { yield this.paypal.sale.api.refund(payment.transaction_id); this.server.log(["info", "paypal-intacct", "invoice", "refund"], invoice); } catch (err) { intacctInvoice.PAYPALERROR += err.message; } } } yield paypalInvoice.get(); this.updateInacctInvoiceWithPayPalModel(intacctInvoice, paypalInvoice); yield this.intacct.update(invoice.RECORDNO, intacctInvoice); }); } createInvoiceSync() { return __awaiter(this, void 0, void 0, function* () { let query = process.env.INTACCT_INVOICE_CREATE_QUERY || `RAWSTATE = 'A' AND (PAYPALINVOICESTATUS IS NULL OR PAYPALINVOICESTATUS NOT IN ('CANCELLED')) AND TOTALDUE NOT IN (0)`; if (!this.options.cron.create.auto) { query += ` AND PAYPALINVOICING = 'T'`; } query += ` AND WHENCREATED > '${this.options.startDate}'`; const invoices = yield Promise.all([ this.intacct.query(query, ["RECORDNO", "RECORDID", "PAYPALINVOICEID"]), this.paypal.invoice.search({ status: ["SENT", "UNPAID"] }), ]); this.server.log(["info", "paypal-intacct", "invoice", "createInvoiceSync"], { count: invoices[0].length, invoices: invoices[0].map((inv) => inv.RECORDID) }); for (const invoice of invoices[0]) { const intacctUpdate = { PAYPALERROR: "", }; let paypalInvoice; try { paypalInvoice = yield this.syncIntacctToPayPal(invoice); } catch (err) { intacctUpdate.PAYPALERROR = err.message.toString(); this.server.log("error", err.toString()); } try { yield this.intacct.update(invoice.RECORDNO, this.updateInacctInvoiceWithPayPalModel(intacctUpdate, paypalInvoice)); } catch (err) { this.server.log("error", err.toString()); } } for (const invoice of invoices[1]) { try { yield this.syncPayPalToIntacct(invoice); } catch (err) { this.server.log("error", `syncPayPalToIntacct | Error: ${err.message}`); } } }); } syncIntacctToPayPal(invoice) { return __awaiter(this, void 0, void 0, function* () { const invoices = yield Promise.all([ this.intacct.get(invoice.RECORDNO), invoice.PAYPALINVOICEID ? this.paypal.invoice.get(invoice.PAYPALINVOICEID) : this.paypal.invoice.search({ number: invoice.RECORDID }), ]); const intacctInvoice = invoices[0]; let paypalInvoice = (Array.isArray(invoices[1])) ? invoices[1].length > 0 ? yield invoices[1][0].get() : null : invoices[1]; if (!paypalInvoice) { paypalInvoice = new this.paypal.invoice(this.toPaypalInvoice(intacctInvoice)); yield paypalInvoice.create(); this.server.log(["info", "paypal-intacct", "invoice", "create"], paypalInvoice.model); } else { try { yield paypalInvoice.update(this.toPaypalInvoice(intacctInvoice)); this.server.log(["info", "paypal-intacct", "invoice", "update"], paypalInvoice.model); } catch (err) { if (err.message !== "Invalid Status") { throw err; } } } try { yield paypalInvoice.send(); this.server.log(["info", "paypal-intacct", "invoice", "send"], paypalInvoice.model); } catch (err) { if (err.message !== "Invalid Status") { throw err; } } try { yield this.createPayment(paypalInvoice.model); } catch (err) { if (err.message !== "Invalid Status") { throw err; } } return paypalInvoice; }); } syncPayPalToIntacct(invoice) { return __awaiter(this, void 0, void 0, function* () { if (!invoice.model.reference) { yield invoice.cancel(); this.server.log(["info", "paypal-intacct", "invoice", "cancel"], invoice.model); return; } const intacctInvoice = yield this.intacct.get(invoice.model.reference); if (!intacctInvoice) { yield invoice.cancel(); this.server.log(["info", "paypal-intacct", "invoice", "cancel"], invoice.model); } else { if (this.options.reminderDays) { yield invoice.get(); const now = new Date(); const lastSend = invoice.model.metadata.first_sent_date || invoice.model.metadata.last_sent_date; const lastReminder = new Date(lastSend); const reminder = new Date(lastReminder.setDate(lastReminder.getDate() + this.options.reminderDays)); if (now > reminder) { yield invoice.remind(); this.server.log(["info", "paypal-intacct", "invoice", "remind"], invoice.model); } } } }); } toPaypalInvoice(intacctInvoice) { const paypalInvoice = { billing_info: [{ additional_info: intacctInvoice.CUSTOMERID, address: { city: intacctInvoice.BILLTO.MAILADDRESS.CITY, country_code: intacctInvoice.BILLTO.MAILADDRESS.COUNTRYCODE, line1: intacctInvoice.BILLTO.MAILADDRESS.ADDRESS1, line2: intacctInvoice.BILLTO.MAILADDRESS.ADDRESS2, postal_code: intacctInvoice.BILLTO.MAILADDRESS.ZIP, state: intacctInvoice.BILLTO.MAILADDRESS.STATE, }, business_name: intacctInvoice.BILLTO.COMPANYNAME, email: intacctInvoice.BILLTO.EMAIL1, first_name: intacctInvoice.BILLTO.FIRSTNAME, last_name: intacctInvoice.BILLTO.LASTNAME, phone: { country_code: intacctInvoice.BILLTO.PHONE1 ? "1" : undefined, national_number: intacctInvoice.BILLTO.PHONE1, }, }], items: this.toPayPalLineItems(intacctInvoice.ARINVOICEITEMS.arinvoiceitem), merchant_info: this.options.merchant, note: intacctInvoice.CUSTMESSAGE.MESSAGE, number: intacctInvoice.RECORDID, payment_term: { due_date: intacctInvoice.WHENDUE + " PDT", }, reference: intacctInvoice.RECORDNO, shipping_info: { address: { city: intacctInvoice.SHIPTO.MAILADDRESS.CITY, country_code: intacctInvoice.SHIPTO.MAILADDRESS.COUNTRYCODE, line1: intacctInvoice.SHIPTO.MAILADDRESS.ADDRESS1, line2: intacctInvoice.SHIPTO.MAILADDRESS.ADDRESS2, postal_code: intacctInvoice.SHIPTO.MAILADDRESS.ZIP, state: intacctInvoice.SHIPTO.MAILADDRESS.STATE, }, business_name: intacctInvoice.SHIPTO.CONTACTNAME, first_name: intacctInvoice.SHIPTO.FIRSTNAME, last_name: intacctInvoice.SHIPTO.LASTNAME, }, tax_inclusive: true, }; return JSON.parse(JSON.stringify(paypalInvoice, (k, v) => ((v === "") ? undefined : v))); } toPayPalLineItems(arrInvoiceItems) { if (!Array.isArray(arrInvoiceItems)) { arrInvoiceItems = [arrInvoiceItems]; } const arrPPInvItems = []; if (arrInvoiceItems.length > 0) { for (const item of arrInvoiceItems) { arrPPInvItems.push({ name: item.ITEMNAME, quantity: 1, unit_price: { currency: item.CURRENCY, value: item.AMOUNT, }, }); } } return arrPPInvItems; } init() { return __awaiter(this, void 0, void 0, function* () { yield Promise.all([this.validateKeys(), this.validateAccounts()]); if (this.options.cron.create && this.options.cron.create.latertext) { yield this.createInvoiceSync(); const timer = later.parse.text(this.options.cron.create.latertext); later.setInterval(this.createInvoiceSync.bind(this), timer); this.server.log("info", `hapi-paypal-intacct::initInvoicing::create cron set for ${this.options.cron.create.latertext}.`); } if (this.options.cron.refund && this.options.cron.refund.latertext) { yield this.refundInvoicesSync(); const refundtimer = later.parse.text(this.options.cron.refund.latertext); later.setInterval(this.refundInvoicesSync.bind(this), refundtimer); this.server.log("info", `hapi-paypal-intacct::initInvoicing::refund cron set for ${this.options.cron.refund.latertext}.`); } }); } updateInacctInvoiceWithPayPalModel(intacctInvoice, paypalInvoice) { if (!paypalInvoice || !paypalInvoice.model) { return intacctInvoice; } intacctInvoice.PAYPALINVOICEID = paypalInvoice.model.id; intacctInvoice.PAYPALINVOICESTATUS = paypalInvoice.model.status; if (paypalInvoice.model.metadata) { intacctInvoice.PAYPALINVOICEURL = paypalInvoice.model.metadata.payer_view_url; } return intacctInvoice; } } HapiPayPalIntacctInvoicing.intacctKeys = [ "PAYPALERROR", "PAYPALINVOICEID", "PAYPALINVOICESTATUS", "PAYPALINVOICEURL", "PAYPALINVOICING", ]; exports.HapiPayPalIntacctInvoicing = HapiPayPalIntacctInvoicing; //# sourceMappingURL=index.js.map