paypal-integrations-intacct
Version:
Integration between paypal and intacct using hapi.
449 lines • 19.9 kB
JavaScript
"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