From acb7b40da8d10539cdfb9f8fbc52c7b1b2c03dfb Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 21 Jan 2025 16:58:14 -0300 Subject: [PATCH 1/3] invoice_ninja init --- .../actions/create-client/create-client.mjs | 153 ++++++ .../actions/create-invoice/create-invoice.mjs | 174 +++++++ .../actions/create-payment/create-payment.mjs | 91 ++++ .../invoice_ninja/invoice_ninja.app.mjs | 486 +++++++++++++++++- .../new-client-instant/new-client-instant.mjs | 92 ++++ .../new-invoice-instant.mjs | 90 ++++ .../new-payment-instant.mjs | 70 +++ 7 files changed, 1154 insertions(+), 2 deletions(-) create mode 100644 components/invoice_ninja/actions/create-client/create-client.mjs create mode 100644 components/invoice_ninja/actions/create-invoice/create-invoice.mjs create mode 100644 components/invoice_ninja/actions/create-payment/create-payment.mjs create mode 100644 components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs create mode 100644 components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs create mode 100644 components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs diff --git a/components/invoice_ninja/actions/create-client/create-client.mjs b/components/invoice_ninja/actions/create-client/create-client.mjs new file mode 100644 index 0000000000000..acd357ecf4830 --- /dev/null +++ b/components/invoice_ninja/actions/create-client/create-client.mjs @@ -0,0 +1,153 @@ +import invoice_ninja from "../../invoice_ninja.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "invoice_ninja-create-client", + name: "Create Client", + description: "Creates a new client in Invoice Ninja. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + invoice_ninja, + contacts: { + propDefinition: [ + invoice_ninja, + "contacts", + ], + }, + countryId: { + propDefinition: [ + invoice_ninja, + "countryId", + ], + }, + name: { + propDefinition: [ + invoice_ninja, + "name", + ], + optional: true, + }, + website: { + propDefinition: [ + invoice_ninja, + "website", + ], + optional: true, + }, + privateNotes: { + propDefinition: [ + invoice_ninja, + "privateNotes", + ], + optional: true, + }, + industryId: { + propDefinition: [ + invoice_ninja, + "industryId", + ], + optional: true, + }, + sizeId: { + propDefinition: [ + invoice_ninja, + "sizeId", + ], + optional: true, + }, + address1: { + propDefinition: [ + invoice_ninja, + "address1", + ], + optional: true, + }, + address2: { + propDefinition: [ + invoice_ninja, + "address2", + ], + optional: true, + }, + city: { + propDefinition: [ + invoice_ninja, + "city", + ], + optional: true, + }, + state: { + propDefinition: [ + invoice_ninja, + "state", + ], + optional: true, + }, + postalCode: { + propDefinition: [ + invoice_ninja, + "postalCode", + ], + optional: true, + }, + phone: { + propDefinition: [ + invoice_ninja, + "phone", + ], + optional: true, + }, + vatNumber: { + propDefinition: [ + invoice_ninja, + "vatNumber", + ], + optional: true, + }, + idNumber: { + propDefinition: [ + invoice_ninja, + "idNumber", + ], + optional: true, + }, + groupSettingsId: { + propDefinition: [ + invoice_ninja, + "groupSettingsId", + ], + optional: true, + }, + classification: { + propDefinition: [ + invoice_ninja, + "classification", + ], + optional: true, + }, + }, + async run({ $ }) { + const client = await this.invoice_ninja.createNewClient({ + contacts: this.contacts.map(JSON.parse), + country_id: this.countryId, + name: this.name, + website: this.website, + private_notes: this.privateNotes, + industry_id: this.industryId, + size_id: this.sizeId, + address1: this.address1, + address2: this.address2, + city: this.city, + state: this.state, + postal_code: this.postalCode, + phone: this.phone, + vat_number: this.vatNumber, + id_number: this.idNumber, + group_settings_id: this.groupSettingsId, + classification: this.classification, + }); + $.export("$summary", `Created client ${client.name}`); + return client; + }, +}; diff --git a/components/invoice_ninja/actions/create-invoice/create-invoice.mjs b/components/invoice_ninja/actions/create-invoice/create-invoice.mjs new file mode 100644 index 0000000000000..3d1284c8a71b6 --- /dev/null +++ b/components/invoice_ninja/actions/create-invoice/create-invoice.mjs @@ -0,0 +1,174 @@ +import invoice_ninja from "../../invoice_ninja.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "invoice_ninja-create-invoice", + name: "Create Invoice", + description: "Creates a new invoice. [See the documentation]().", + version: "0.0.{{ts}}", + type: "action", + props: { + invoice_ninja: { + type: "app", + app: "invoice_ninja", + }, + clientId: { + propDefinition: [ + invoice_ninja, + "clientId", + ], + }, + userId: { + propDefinition: [ + invoice_ninja, + "userId", + ], + optional: true, + }, + assignedUserId: { + propDefinition: [ + invoice_ninja, + "assignedUserId", + ], + optional: true, + }, + statusId: { + propDefinition: [ + invoice_ninja, + "statusId", + ], + optional: true, + }, + number: { + propDefinition: [ + invoice_ninja, + "number", + ], + optional: true, + }, + poNumber: { + propDefinition: [ + invoice_ninja, + "poNumber", + ], + optional: true, + }, + terms: { + propDefinition: [ + invoice_ninja, + "terms", + ], + optional: true, + }, + publicNotes: { + propDefinition: [ + invoice_ninja, + "publicNotes", + ], + optional: true, + }, + privateNotes: { + propDefinition: [ + invoice_ninja, + "privateNotes", + ], + optional: true, + }, + footer: { + propDefinition: [ + invoice_ninja, + "footer", + ], + optional: true, + }, + customValue1: { + propDefinition: [ + invoice_ninja, + "customValue1", + ], + optional: true, + }, + customValue2: { + propDefinition: [ + invoice_ninja, + "customValue2", + ], + optional: true, + }, + customValue3: { + propDefinition: [ + invoice_ninja, + "customValue3", + ], + optional: true, + }, + customValue4: { + propDefinition: [ + invoice_ninja, + "customValue4", + ], + optional: true, + }, + totalTaxes: { + propDefinition: [ + invoice_ninja, + "totalTaxes", + ], + optional: true, + }, + lineItems: { + propDefinition: [ + invoice_ninja, + "lineItems", + ], + optional: true, + }, + amount: { + propDefinition: [ + invoice_ninja, + "amount", + ], + optional: true, + }, + balance: { + propDefinition: [ + invoice_ninja, + "balance", + ], + optional: true, + }, + paidToDate: { + propDefinition: [ + invoice_ninja, + "paidToDate", + ], + optional: true, + }, + discount: { + propDefinition: [ + invoice_ninja, + "discount", + ], + optional: true, + }, + date: { + propDefinition: [ + invoice_ninja, + "date", + ], + optional: true, + }, + dueDate: { + propDefinition: [ + invoice_ninja, + "dueDate", + ], + optional: true, + }, + }, + async run({ $ }) { + const invoice = await this.invoice_ninja.createNewInvoice(); + $.export("$summary", `Created invoice with ID: ${invoice.id}`); + return invoice; + }, +}; diff --git a/components/invoice_ninja/actions/create-payment/create-payment.mjs b/components/invoice_ninja/actions/create-payment/create-payment.mjs new file mode 100644 index 0000000000000..dad1c1c858d00 --- /dev/null +++ b/components/invoice_ninja/actions/create-payment/create-payment.mjs @@ -0,0 +1,91 @@ +import invoice_ninja from "../../invoice_ninja.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "invoice_ninja-create-payment", + name: "Create Payment", + description: "Records a payment for an invoice. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + invoice_ninja, + paymentId: { + propDefinition: [ + invoice_ninja, + "paymentId", + ], + optional: true, + }, + clientId: { + propDefinition: [ + invoice_ninja, + "clientId", + ], + optional: true, + }, + clientContactId: { + propDefinition: [ + invoice_ninja, + "clientContactId", + ], + optional: true, + }, + userId: { + propDefinition: [ + invoice_ninja, + "userId", + ], + optional: true, + }, + typeId: { + propDefinition: [ + invoice_ninja, + "typeId", + ], + optional: true, + }, + paymentDate: { + propDefinition: [ + invoice_ninja, + "paymentDate", + ], + optional: true, + }, + paymentAmount: { + propDefinition: [ + invoice_ninja, + "paymentAmount", + ], + optional: true, + }, + companyGatewayId: { + propDefinition: [ + invoice_ninja, + "companyGatewayId", + ], + optional: true, + }, + paymentNumber: { + propDefinition: [ + invoice_ninja, + "paymentNumber", + ], + optional: true, + }, + }, + async run({ $ }) { + const payment = await this.invoice_ninja.recordPayment({ + id: this.paymentId, + client_id: this.clientId, + client_contact_id: this.clientContactId, + user_id: this.userId, + type_id: this.typeId, + date: this.paymentDate, + amount: this.paymentAmount, + company_gateway_id: this.companyGatewayId, + number: this.paymentNumber, + }); + $.export("$summary", `Payment ${this.paymentNumber || payment.id} created successfully`); + return payment; + }, +}; diff --git a/components/invoice_ninja/invoice_ninja.app.mjs b/components/invoice_ninja/invoice_ninja.app.mjs index 1c15b3e8f27b4..42de9fe6f032f 100644 --- a/components/invoice_ninja/invoice_ninja.app.mjs +++ b/components/invoice_ninja/invoice_ninja.app.mjs @@ -1,11 +1,493 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "invoice_ninja", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + // Emit Events + emitNewClientEvent: { + type: "object", + label: "Emit New Client Event", + description: "Emits an event when a new client is added", + }, + emitNewInvoiceEvent: { + type: "object", + label: "Emit New Invoice Event", + description: "Emits an event when a new invoice is created", + }, + emitNewPaymentEvent: { + type: "object", + label: "Emit New Payment Event", + description: "Emits an event when a new payment is registered", + }, + + // Create Invoice Props + clientId: { + type: "string", + label: "Client ID", + description: "The ID of the client to associate with the invoice", + }, + userId: { + type: "string", + label: "User ID", + description: "The ID of the user creating the invoice", + optional: true, + async options() { + const users = await this.getUsers(); + return users.map((user) => ({ + label: user.name, + value: user.id, + })); + }, + }, + assignedUserId: { + type: "string", + label: "Assigned User ID", + description: "The ID of the user to assign the invoice to", + optional: true, + async options() { + const users = await this.getUsers(); + return users.map((user) => ({ + label: user.name, + value: user.id, + })); + }, + }, + statusId: { + type: "string", + label: "Status ID", + description: "The status ID of the invoice", + optional: true, + }, + number: { + type: "string", + label: "Invoice Number", + description: "The number of the invoice", + optional: true, + }, + poNumber: { + type: "string", + label: "PO Number", + description: "Purchase Order number for the invoice", + optional: true, + }, + terms: { + type: "string", + label: "Terms", + description: "Payment terms for the invoice", + optional: true, + }, + publicNotes: { + type: "string", + label: "Public Notes", + description: "Public notes for the invoice", + optional: true, + }, + privateNotes: { + type: "string", + label: "Private Notes", + description: "Private notes for the invoice", + optional: true, + }, + footer: { + type: "string", + label: "Footer", + description: "Footer content for the invoice", + optional: true, + }, + customValue1: { + type: "string", + label: "Custom Value 1", + description: "Custom field 1 for the invoice", + optional: true, + }, + customValue2: { + type: "string", + label: "Custom Value 2", + description: "Custom field 2 for the invoice", + optional: true, + }, + customValue3: { + type: "string", + label: "Custom Value 3", + description: "Custom field 3 for the invoice", + optional: true, + }, + customValue4: { + type: "string", + label: "Custom Value 4", + description: "Custom field 4 for the invoice", + optional: true, + }, + totalTaxes: { + type: "integer", + label: "Total Taxes", + description: "Total taxes for the invoice", + optional: true, + }, + lineItems: { + type: "string[]", + label: "Line Items", + description: "An array of line items in JSON format", + optional: true, + }, + amount: { + type: "integer", + label: "Amount", + description: "Total amount of the invoice", + optional: true, + }, + balance: { + type: "integer", + label: "Balance", + description: "Balance remaining on the invoice", + optional: true, + }, + paidToDate: { + type: "integer", + label: "Paid To Date", + description: "Amount paid to date for the invoice", + optional: true, + }, + discount: { + type: "integer", + label: "Discount", + description: "Discount applied to the invoice", + optional: true, + }, + date: { + type: "string", + label: "Invoice Date", + description: "Date of the invoice (YYYY-MM-DD)", + optional: true, + }, + dueDate: { + type: "string", + label: "Due Date", + description: "Due date for the invoice (YYYY-MM-DD)", + optional: true, + }, + + // Create Client Props + contacts: { + type: "string[]", + label: "Contacts", + description: "An array of contact objects in JSON format", + }, + countryId: { + type: "string", + label: "Country ID", + description: "The ID of the country for the client", + async options() { + const countries = await this.getCountries(); + return countries.map((country) => ({ + label: country.name, + value: country.id, + })); + }, + }, + name: { + type: "string", + label: "Client Name", + description: "Name of the client", + optional: true, + }, + website: { + type: "string", + label: "Website", + description: "Website of the client", + optional: true, + }, + industryId: { + type: "string", + label: "Industry ID", + description: "Industry ID of the client", + optional: true, + }, + sizeId: { + type: "string", + label: "Size ID", + description: "Size ID of the client", + optional: true, + }, + address1: { + type: "string", + label: "Address 1", + description: "Primary address line for the client", + optional: true, + }, + address2: { + type: "string", + label: "Address 2", + description: "Secondary address line for the client", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "City of the client", + optional: true, + }, + state: { + type: "string", + label: "State", + description: "State of the client", + optional: true, + }, + postalCode: { + type: "string", + label: "Postal Code", + description: "Postal code of the client", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "Phone number of the client", + optional: true, + }, + vatNumber: { + type: "string", + label: "VAT Number", + description: "VAT number of the client", + optional: true, + }, + idNumber: { + type: "string", + label: "ID Number", + description: "ID number of the client", + optional: true, + }, + groupSettingsId: { + type: "string", + label: "Group Settings ID", + description: "Group settings ID for the client", + optional: true, + async options() { + const groupSettings = await this.getGroupSettings(); + return groupSettings.map((group) => ({ + label: group.name, + value: group.id, + })); + }, + }, + classification: { + type: "string", + label: "Classification", + description: "Classification of the client", + optional: true, + }, + + // Record Payment Props + paymentId: { + type: "string", + label: "Payment ID", + description: "The ID of the payment", + optional: true, + }, + clientContactId: { + type: "string", + label: "Client Contact ID", + description: "The ID of the client's contact", + optional: true, + }, + typeId: { + type: "string", + label: "Type ID", + description: "Type ID of the payment", + optional: true, + }, + paymentDate: { + type: "string", + label: "Payment Date", + description: "Date of the payment (YYYY-MM-DD)", + optional: true, + }, + paymentAmount: { + type: "integer", + label: "Payment Amount", + description: "Amount of the payment", + optional: true, + }, + companyGatewayId: { + type: "string", + label: "Company Gateway ID", + description: "The ID of the company gateway", + optional: true, + async options() { + const gateways = await this.getCompanyGateways(); + return gateways.map((gateway) => ({ + label: gateway.name, + value: gateway.id, + })); + }, + }, + paymentNumber: { + type: "string", + label: "Payment Number", + description: "Number of the payment", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.invoiceninja.com/api/v1"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + method = "GET", + path = "/", + headers = {}, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + "X-API-TOKEN": this.$auth.api_token, + "X-Requested-With": "XMLHttpRequest", + }, + }); + }, + async emitNewClientEvent(client) { + this.$emit("new_client", client); + }, + async emitNewInvoiceEvent(invoice) { + this.$emit("new_invoice", invoice); + }, + async emitNewPaymentEvent(payment) { + this.$emit("new_payment", payment); + }, + async createNewInvoice(data) { + const invoiceData = { + client_id: this.clientId, + user_id: this.userId, + assigned_user_id: this.assignedUserId, + status_id: this.statusId, + number: this.number, + po_number: this.poNumber, + terms: this.terms, + public_notes: this.publicNotes, + private_notes: this.privateNotes, + footer: this.footer, + custom_value1: this.customValue1, + custom_value2: this.customValue2, + custom_value3: this.customValue3, + custom_value4: this.customValue4, + total_taxes: this.totalTaxes, + line_items: this.lineItems + ? this.lineItems.map(JSON.parse) + : undefined, + amount: this.amount, + balance: this.balance, + paid_to_date: this.paidToDate, + discount: this.discount, + date: this.date, + due_date: this.dueDate, + ...data, + }; + const invoice = await this._makeRequest({ + method: "POST", + path: "/invoices", + data: invoiceData, + }); + await this.emitNewInvoiceEvent(invoice); + return invoice; + }, + async createNewClient(data) { + const clientData = { + contacts: this.contacts.map(JSON.parse), + country_id: this.countryId, + name: this.name, + website: this.website, + private_notes: this.privateNotes, + industry_id: this.industryId, + size_id: this.sizeId, + address1: this.address1, + address2: this.address2, + city: this.city, + state: this.state, + postal_code: this.postalCode, + phone: this.phone, + vat_number: this.vatNumber, + id_number: this.idNumber, + group_settings_id: this.groupSettingsId, + classification: this.classification, + ...data, + }; + const client = await this._makeRequest({ + method: "POST", + path: "/clients", + data: clientData, + }); + await this.emitNewClientEvent(client); + return client; + }, + async recordPayment(data) { + const paymentData = { + id: this.paymentId, + client_id: this.clientId, + client_contact_id: this.clientContactId, + user_id: this.userId, + type_id: this.typeId, + date: this.paymentDate, + amount: this.paymentAmount, + company_gateway_id: this.companyGatewayId, + number: this.paymentNumber, + ...data, + }; + const payment = await this._makeRequest({ + method: "POST", + path: "/payments", + data: paymentData, + }); + await this.emitNewPaymentEvent(payment); + return payment; + }, + async getUsers() { + return this._makeRequest({ + path: "/users", + }); + }, + async getCountries() { + return this._makeRequest({ + path: "/countries", + }); + }, + async getGroupSettings() { + return this._makeRequest({ + path: "/group_settings", + }); + }, + async getCompanyGateways() { + return this._makeRequest({ + path: "/company_gateways", + }); + }, + async paginate(fn, initialOpts = {}) { + const results = []; + let page = 1; + let more = true; + while (more) { + const response = await fn({ + page, + ...initialOpts, + }); + if (response.length > 0) { + results.push(...response); + page += 1; + } else { + more = false; + } + } + return results; + }, }, }; diff --git a/components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs b/components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs new file mode 100644 index 0000000000000..9f07862ab047a --- /dev/null +++ b/components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs @@ -0,0 +1,92 @@ +import invoice_ninja from "../../invoice_ninja.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "invoice_ninja-new-client-instant", + name: "New Client (Instant)", + description: "Emit new event when a new client is added. [See the documentation](https://api.invoiceninja.com/docs)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + invoice_ninja: { + type: "app", + app: "invoice_ninja", + }, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + hooks: { + async activate() { + const webhookURL = this.http.endpoint; + const secret = crypto.randomBytes(32).toString("hex"); + await this.db.set("webhookSecret", secret); + const response = await this.invoice_ninja._makeRequest({ + method: "POST", + path: "/webhooks", + data: { + event: "client.created", + url: webhookURL, + secret: secret, + }, + }); + const webhookId = response.id; + await this.db.set("webhookId", webhookId); + }, + async deactivate() { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this.invoice_ninja._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + await this.db.delete("webhookId"); + } + await this.db.delete("webhookSecret"); + }, + async deploy() { + const clients = await this.paginate(this.invoice_ninja.getClients, { + limit: 50, + }); + for (const client of clients.reverse()) { + this.$emit(client, { + id: client.id, + summary: `New client: ${client.name}`, + ts: Date.parse(client.created_at) || Date.now(), + }); + } + }, + }, + async run(event) { + const secret = await this.db.get("webhookSecret"); + const signature = event.headers["X-Signature"] || event.headers["x-signature"]; + const rawBody = JSON.stringify(event.body); + const computedSignature = crypto.createHmac("sha256", secret).update(rawBody) + .digest("hex"); + if (computedSignature !== signature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + const client = event.body; + const timestamp = Date.parse(client.created_at) || Date.now(); + + this.$emit(client, { + id: client.id, + summary: `New client: ${client.name}`, + ts: timestamp, + }); + + this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; diff --git a/components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs b/components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs new file mode 100644 index 0000000000000..09e7aa33e8b0a --- /dev/null +++ b/components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs @@ -0,0 +1,90 @@ +import invoice_ninja from "../../invoice_ninja.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "invoice_ninja-new-invoice-instant", + name: "New Invoice Created", + description: "Emit new event when a new invoice is created. [See the documentation]()", // Replace () with actual docs link if available + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + invoice_ninja: { + type: "app", + app: "invoice_ninja", + }, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + hooks: { + async deploy() { + const recentInvoices = await this.invoice_ninja.paginate(this.invoice_ninja.getInvoices, { + sort: "created_at_desc", + limit: 50, + }); + for (const invoice of recentInvoices) { + this.$emit(invoice, { + id: invoice.id || `${invoice.number}-${invoice.created_at}`, + summary: `New invoice created: ${invoice.number}`, + ts: Date.parse(invoice.created_at) || Date.now(), + }); + } + }, + async activate() { + const webhookUrl = this.http.endpoint; + const webhook = await this.invoice_ninja._makeRequest({ + method: "POST", + path: "/webhooks", + data: { + url: webhookUrl, + event: "invoice.created", + }, + }); + await this.db.set("webhookId", webhook.id); + }, + async deactivate() { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this.invoice_ninja._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + await this.db.set("webhookId", null); + } + }, + }, + async run(event) { + const signature = event.headers["X-Signature"]; + const secret = this.invoice_ninja.$auth.webhook_secret; + const hmac = crypto.createHmac("sha256", secret); + const digest = hmac.update(event.body_raw).digest("hex"); + + if (digest !== signature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + const invoice = event.body; + const id = invoice.id || `${invoice.number}-${invoice.created_at}`; + const summary = `New invoice created: ${invoice.number}`; + const ts = Date.parse(invoice.created_at) || Date.now(); + + this.$emit(invoice, { + id, + summary, + ts, + }); + + this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; diff --git a/components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs b/components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs new file mode 100644 index 0000000000000..15b9a88390622 --- /dev/null +++ b/components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs @@ -0,0 +1,70 @@ +import invoice_ninja from "../../invoice_ninja.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "invoice_ninja-new-payment-instant", + name: "New Payment Registered", + description: "Emit new event when a new payment is registered. [See the documentation]().", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + invoice_ninja: { + type: "app", + app: "invoice_ninja", + }, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + hooks: { + async activate() { + const webhookUrl = this.http.endpoint; + const payload = { + event: "payment.created", + url: webhookUrl, + }; + + const response = await this.invoice_ninja._makeRequest({ + method: "POST", + path: "/webhooks", + data: payload, + }); + + const webhookId = response.id; + await this.db.set("webhookId", webhookId); + }, + async deactivate() { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this.invoice_ninja._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + await this.db.delete("webhookId"); + } + }, + async deploy() { + const payments = await this.invoice_ninja._makeRequest({ + method: "GET", + path: "/payments", + params: { + limit: 50, + sort: "created_at", + order: "desc", + }, + }); + + const last50Payments = payments.reverse(); + for (const payment of last50Payments) { + await this.invoice_ninja.emitNewPaymentEvent(payment); + } + }, + }, + async run(event) { + const payment = event.body; + await this.invoice_ninja.emitNewPaymentEvent(payment); + }, +}; From d74be78fb2e49350b88bb3c14a4b4cb7423dea2f Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 24 Jan 2025 12:17:01 -0300 Subject: [PATCH 2/3] [Components] invoice_ninja #15154 Sources - New Client (Instant) - New Invoice (Instant) - New Payment (Instant) Actions - Create Client - Create Invoice - Create Payment --- .../actions/create-client/create-client.mjs | 257 ++++++--- .../actions/create-invoice/create-invoice.mjs | 188 +++--- .../actions/create-payment/create-payment.mjs | 156 +++-- components/invoice_ninja/common/constants.mjs | 34 ++ components/invoice_ninja/common/utils.mjs | 24 + .../invoice_ninja/invoice_ninja.app.mjs | 535 +++++------------- components/invoice_ninja/package.json | 18 + .../invoice_ninja/sources/common/base.mjs | 47 ++ .../new-client-instant/new-client-instant.mjs | 94 +-- .../sources/new-client-instant/test-event.mjs | 282 +++++++++ .../new-invoice-instant.mjs | 94 +-- .../new-invoice-instant/test-event.mjs | 82 +++ .../new-payment-instant.mjs | 74 +-- .../new-payment-instant/test-event.mjs | 42 ++ 14 files changed, 1092 insertions(+), 835 deletions(-) create mode 100644 components/invoice_ninja/common/constants.mjs create mode 100644 components/invoice_ninja/common/utils.mjs create mode 100644 components/invoice_ninja/package.json create mode 100644 components/invoice_ninja/sources/common/base.mjs create mode 100644 components/invoice_ninja/sources/new-client-instant/test-event.mjs create mode 100644 components/invoice_ninja/sources/new-invoice-instant/test-event.mjs create mode 100644 components/invoice_ninja/sources/new-payment-instant/test-event.mjs diff --git a/components/invoice_ninja/actions/create-client/create-client.mjs b/components/invoice_ninja/actions/create-client/create-client.mjs index acd357ecf4830..c0281a7ffcf25 100644 --- a/components/invoice_ninja/actions/create-client/create-client.mjs +++ b/components/invoice_ninja/actions/create-client/create-client.mjs @@ -1,153 +1,246 @@ -import invoice_ninja from "../../invoice_ninja.app.mjs"; -import { axios } from "@pipedream/platform"; +import { CLASSIFICATION_OPTIONS } from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; +import app from "../../invoice_ninja.app.mjs"; export default { key: "invoice_ninja-create-client", name: "Create Client", - description: "Creates a new client in Invoice Ninja. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Creates a new client in Invoice Ninja. [See the documentation](https://api-docs.invoicing.co/#tag/clients/POST/api/v1/clients)", + version: "0.0.1", type: "action", props: { - invoice_ninja, + app, contacts: { - propDefinition: [ - invoice_ninja, - "contacts", - ], - }, - countryId: { - propDefinition: [ - invoice_ninja, - "countryId", - ], + type: "string[]", + label: "Contacts", + description: "An array of contact objects in JSON format. **Example: { \"first_name\": \"John\", \"last_name\": \"Smith\", \"email\": \"john@example.com\", \"phone\": \"555-0123\" }**", }, name: { - propDefinition: [ - invoice_ninja, - "name", - ], + type: "string", + label: "Client Name", + description: "Name of the client", optional: true, }, website: { - propDefinition: [ - invoice_ninja, - "website", - ], + type: "string", + label: "Website", + description: "Website of the client", optional: true, }, privateNotes: { - propDefinition: [ - invoice_ninja, - "privateNotes", - ], + type: "string", + label: "Private Notes", + description: "Notes that are only visible to the user who created the client", optional: true, }, industryId: { propDefinition: [ - invoice_ninja, + app, "industryId", ], optional: true, }, sizeId: { propDefinition: [ - invoice_ninja, + app, "sizeId", ], optional: true, }, address1: { - propDefinition: [ - invoice_ninja, - "address1", - ], + type: "string", + label: "Address 1", + description: "Primary address line for the client", optional: true, }, address2: { - propDefinition: [ - invoice_ninja, - "address2", - ], + type: "string", + label: "Address 2", + description: "Secondary address line for the client", optional: true, }, city: { - propDefinition: [ - invoice_ninja, - "city", - ], + type: "string", + label: "City", + description: "City of the client", optional: true, }, state: { - propDefinition: [ - invoice_ninja, - "state", - ], + type: "string", + label: "State", + description: "State of the client", optional: true, }, postalCode: { - propDefinition: [ - invoice_ninja, - "postalCode", - ], + type: "string", + label: "Postal Code", + description: "Postal code of the client", optional: true, }, phone: { + type: "string", + label: "Phone", + description: "Phone number of the client", + optional: true, + }, + countryId: { propDefinition: [ - invoice_ninja, - "phone", + app, + "countryId", ], + }, + customValue1: { + type: "string", + label: "Custom Value 1", + description: "A custom field for storing additional information", + optional: true, + }, + customValue2: { + type: "string", + label: "Custom Value 2", + description: "A custom field for storing additional information", + optional: true, + }, + customValue3: { + type: "string", + label: "Custom Value 3", + description: "A custom field for storing additional information", + optional: true, + }, + customValue4: { + type: "string", + label: "Custom Value 4", + description: "A custom field for storing additional information", optional: true, }, vatNumber: { - propDefinition: [ - invoice_ninja, - "vatNumber", - ], + type: "string", + label: "VAT Number", + description: "The client's VAT (Value Added Tax) number, if applicable", optional: true, }, idNumber: { + type: "string", + label: "ID Number", + description: "A unique identification number for the client, such as a tax ID or business registration number", + optional: true, + }, + number: { + type: "string", + label: "Number", + description: "A system-assigned unique number for the client, typically used for invoicing purposes", + optional: true, + }, + shippingAddress1: { + type: "string", + label: "Shipping Address 1", + description: "First line of the client's shipping address", + optional: true, + }, + shippingAddress2: { + type: "string", + label: "Shipping Address 2", + description: "Second line of the client's shipping address, if needed", + optional: true, + }, + shippingCity: { + type: "string", + label: "Shipping City", + description: "City of the client's shipping address", + optional: true, + }, + shippingState: { + type: "string", + label: "Shipping State", + description: "State of the client's shipping address", + optional: true, + }, + shippingPostalCode: { + type: "string", + label: "Shipping Postal Code", + description: "Postal code of the client's shipping address", + optional: true, + }, + shippingCountryId: { propDefinition: [ - invoice_ninja, - "idNumber", + app, + "countryId", ], + label: "Shipping Country ID", + description: "The ID of the country for the client's shipping address", optional: true, }, groupSettingsId: { propDefinition: [ - invoice_ninja, + app, "groupSettingsId", ], optional: true, }, + isTaxExempt: { + type: "boolean", + label: "Is Tax Exempt", + description: "Flag which defines if the client is exempt from taxes", + optional: true, + }, + hasValidVatNumber: { + type: "boolean", + label: "Has Valid VAT Number", + description: "Flag which defines if the client has a valid VAT number", + optional: true, + }, classification: { - propDefinition: [ - invoice_ninja, - "classification", - ], + type: "string", + label: "Classification", + description: "Classification of the client", + options: CLASSIFICATION_OPTIONS, + optional: true, + }, + settings: { + type: "object", + label: "Settings", + description: "An array of settings objects in JSON format. **Example: {\"currency_id\": 1, \"timezone_id\": 5, \"date_format_id\": 1, \"language_id\": 1}** [See the documentation](https://api-docs.invoicing.co/#tag/clients/POST/api/v1/clients) for further details", optional: true, }, }, async run({ $ }) { - const client = await this.invoice_ninja.createNewClient({ - contacts: this.contacts.map(JSON.parse), - country_id: this.countryId, - name: this.name, - website: this.website, - private_notes: this.privateNotes, - industry_id: this.industryId, - size_id: this.sizeId, - address1: this.address1, - address2: this.address2, - city: this.city, - state: this.state, - postal_code: this.postalCode, - phone: this.phone, - vat_number: this.vatNumber, - id_number: this.idNumber, - group_settings_id: this.groupSettingsId, - classification: this.classification, + const { data } = await this.app.createNewClient({ + $, + data: { + contacts: parseObject(this.contacts), + name: this.name, + website: this.website, + private_notes: this.privateNotes, + industry_id: this.industryId, + size_id: this.sizeId, + address1: this.address1, + address2: this.address2, + city: this.city, + state: this.state, + postal_code: this.postalCode, + phone: this.phone, + country_id: this.countryId, + custom_value1: this.customValue1, + custom_value2: this.customValue2, + custom_value3: this.customValue3, + custom_value4: this.customValue4, + vat_number: this.vatNumber, + id_number: this.idNumber, + number: this.number, + shipping_address1: this.shippingAddress1, + shipping_address2: this.shippingAddress2, + shipping_city: this.shippingCity, + shipping_state: this.shippingState, + shipping_postal_code: this.shippingPostalCode, + shipping_country_id: this.shipping_country_id, + group_settings_id: this.groupSettingsId, + is_tax_exempt: this.isTaxExempt, + has_valid_vat_number: this.hasValidVatNumber, + classification: this.classification, + settings: parseObject(this.settings), + }, }); - $.export("$summary", `Created client ${client.name}`); - return client; + $.export("$summary", `Created client: ${data.id}`); + return data; }, }; diff --git a/components/invoice_ninja/actions/create-invoice/create-invoice.mjs b/components/invoice_ninja/actions/create-invoice/create-invoice.mjs index 3d1284c8a71b6..acc9cf426cc6e 100644 --- a/components/invoice_ninja/actions/create-invoice/create-invoice.mjs +++ b/components/invoice_ninja/actions/create-invoice/create-invoice.mjs @@ -1,174 +1,174 @@ -import invoice_ninja from "../../invoice_ninja.app.mjs"; -import { axios } from "@pipedream/platform"; +import { parseObject } from "../../common/utils.mjs"; +import app from "../../invoice_ninja.app.mjs"; export default { key: "invoice_ninja-create-invoice", name: "Create Invoice", - description: "Creates a new invoice. [See the documentation]().", - version: "0.0.{{ts}}", + description: "Creates a new invoice. [See the documentation](https://api-docs.invoicing.co/#tag/invoices/POST/api/v1/invoices).", + version: "0.0.1", type: "action", props: { - invoice_ninja: { - type: "app", - app: "invoice_ninja", - }, + app, clientId: { propDefinition: [ - invoice_ninja, + app, "clientId", ], }, userId: { propDefinition: [ - invoice_ninja, + app, "userId", ], optional: true, }, assignedUserId: { propDefinition: [ - invoice_ninja, - "assignedUserId", - ], - optional: true, - }, - statusId: { - propDefinition: [ - invoice_ninja, - "statusId", + app, + "userId", ], + label: "Assigned User ID", + description: "The ID of the user to assign the invoice to", optional: true, }, number: { - propDefinition: [ - invoice_ninja, - "number", - ], + type: "string", + label: "Invoice Number", + description: "The number of the invoice", optional: true, }, poNumber: { - propDefinition: [ - invoice_ninja, - "poNumber", - ], + type: "string", + label: "PO Number", + description: "Purchase Order number for the invoice", optional: true, }, terms: { - propDefinition: [ - invoice_ninja, - "terms", - ], + type: "string", + label: "Terms", + description: "Payment terms for the invoice", optional: true, }, publicNotes: { - propDefinition: [ - invoice_ninja, - "publicNotes", - ], + type: "string", + label: "Public Notes", + description: "Public notes for the invoice", optional: true, }, privateNotes: { - propDefinition: [ - invoice_ninja, - "privateNotes", - ], + type: "string", + label: "Private Notes", + description: "Private notes for the invoice", optional: true, }, footer: { - propDefinition: [ - invoice_ninja, - "footer", - ], + type: "string", + label: "Footer", + description: "Footer content for the invoice", optional: true, }, customValue1: { - propDefinition: [ - invoice_ninja, - "customValue1", - ], + type: "string", + label: "Custom Value 1", + description: "Custom field 1 for the invoice", optional: true, }, customValue2: { - propDefinition: [ - invoice_ninja, - "customValue2", - ], + type: "string", + label: "Custom Value 2", + description: "Custom field 2 for the invoice", optional: true, }, customValue3: { - propDefinition: [ - invoice_ninja, - "customValue3", - ], + type: "string", + label: "Custom Value 3", + description: "Custom field 3 for the invoice", optional: true, }, customValue4: { - propDefinition: [ - invoice_ninja, - "customValue4", - ], + type: "string", + label: "Custom Value 4", + description: "Custom field 4 for the invoice", optional: true, }, totalTaxes: { - propDefinition: [ - invoice_ninja, - "totalTaxes", - ], + type: "string", + label: "Total Taxes", + description: "Total taxes for the invoice", optional: true, }, lineItems: { - propDefinition: [ - invoice_ninja, - "lineItems", - ], + type: "string[]", + label: "Line Items", + description: "An array of line items in JSON format. **Example: { \"quantity\": 1, \"cost\": 10, \"product_key\": \"Product key\", \"product_cost\": 10, \"notes\": \"Item notes\", \"discount\": 5, \"is_amount_discount\": false, \"tax_name1\": \"GST\", \"tax_rate1\": 10, \"tax_name2\": \"VAT\", \"tax_rate2\": 5, \"tax_name3\": \"CA Sales Tax\", \"tax_rate3\": 3, \"sort_id\": \"0\", \"date\": \"2023-03-19T00:00:00Z\", \"custom_value1\": \"Custom value 1\", \"custom_value2\": \"Custom value 2\", \"custom_value3\": \"Custom value 3\", \"custom_value4\": \"Custom value 4\", \"type_id\": \"1\", \"tax_id\": \"1\" }.**", optional: true, }, amount: { - propDefinition: [ - invoice_ninja, - "amount", - ], + type: "string", + label: "Amount", + description: "Total amount of the invoice", optional: true, }, balance: { - propDefinition: [ - invoice_ninja, - "balance", - ], + type: "string", + label: "Balance", + description: "Balance remaining on the invoice", optional: true, }, paidToDate: { - propDefinition: [ - invoice_ninja, - "paidToDate", - ], + type: "string", + label: "Paid To Date", + description: "Amount paid to date for the invoice", optional: true, }, discount: { - propDefinition: [ - invoice_ninja, - "discount", - ], + type: "string", + label: "Discount", + description: "Discount applied to the invoice", optional: true, }, date: { - propDefinition: [ - invoice_ninja, - "date", - ], + type: "string", + label: "Invoice Date", + description: "Date of the invoice (YYYY-MM-DD)", optional: true, }, dueDate: { - propDefinition: [ - invoice_ninja, - "dueDate", - ], + type: "string", + label: "Due Date", + description: "Due date for the invoice (YYYY-MM-DD)", optional: true, }, }, async run({ $ }) { - const invoice = await this.invoice_ninja.createNewInvoice(); - $.export("$summary", `Created invoice with ID: ${invoice.id}`); - return invoice; + const { data } = await this.app.createNewInvoice({ + $, + data: { + client_id: this.clientId, + user_id: this.userId, + assigned_user_id: this.assignedUserId, + status_id: this.statusId, + number: this.number, + po_number: this.poNumber, + terms: this.terms, + public_notes: this.publicNotes, + private_notes: this.privateNotes, + footer: this.footer, + custom_value1: this.customValue1, + custom_value2: this.customValue2, + custom_value3: this.customValue3, + custom_value4: this.customValue4, + total_taxes: this.totalTaxes && parseFloat(this.totalTaxes), + line_items: parseObject(this.lineItems), + amount: this.amount && parseFloat(this.amount), + balance: this.balance && parseFloat(this.balance), + paid_to_date: this.paidToDate && parseFloat(this.paidToDate), + discount: this.discount && parseFloat(this.discount), + date: this.date, + due_date: this.dueDate, + }, + }); + $.export("$summary", `Created invoice with ID: ${data.id}`); + return data; }, }; diff --git a/components/invoice_ninja/actions/create-payment/create-payment.mjs b/components/invoice_ninja/actions/create-payment/create-payment.mjs index dad1c1c858d00..5e11d96e8b127 100644 --- a/components/invoice_ninja/actions/create-payment/create-payment.mjs +++ b/components/invoice_ninja/actions/create-payment/create-payment.mjs @@ -1,91 +1,165 @@ -import invoice_ninja from "../../invoice_ninja.app.mjs"; -import { axios } from "@pipedream/platform"; +import { parseObject } from "../../common/utils.mjs"; +import app from "../../invoice_ninja.app.mjs"; export default { key: "invoice_ninja-create-payment", name: "Create Payment", - description: "Records a payment for an invoice. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Records a payment for an invoice. [See the documentation](https://api-docs.invoicing.co/#tag/payments/POST/api/v1/payments)", + version: "0.0.1", type: "action", props: { - invoice_ninja, - paymentId: { - propDefinition: [ - invoice_ninja, - "paymentId", - ], - optional: true, - }, + app, clientId: { propDefinition: [ - invoice_ninja, + app, "clientId", ], + description: "The client hashed id", + }, + paymentId: { + type: "string", + label: "Payment ID", + description: "The payment hashed id", optional: true, }, clientContactId: { propDefinition: [ - invoice_ninja, + app, "clientContactId", + ({ clientId }) => ({ + clientId, + }), ], optional: true, }, userId: { propDefinition: [ - invoice_ninja, + app, "userId", ], + description: "The user hashed id", optional: true, }, typeId: { propDefinition: [ - invoice_ninja, + app, "typeId", ], optional: true, }, paymentDate: { - propDefinition: [ - invoice_ninja, - "paymentDate", - ], + type: "string", + label: "Payment Date", + description: "Date of the payment (D-M-YYYY)", optional: true, }, - paymentAmount: { + transactionReference: { + type: "string", + label: "Transaction Reference", + description: "The transaction reference as defined by the payment gateway", + optional: true, + }, + assignedUserId: { propDefinition: [ - invoice_ninja, - "paymentAmount", + app, + "userId", ], + description: "The assigned user hashed id", + optional: true, + }, + privateNotes: { + type: "string", + label: "Private Notes", + description: "The private notes of the payment", + optional: true, + }, + isManual: { + type: "boolean", + label: "Is Manual", + description: "Flags whether the payment was made manually or processed via a gateway", + optional: true, + }, + isDeleted: { + type: "boolean", + label: "Is Deleted", + description: "Defines if the payment has been deleted", + optional: true, + }, + amount: { + type: "string", + label: "Amount", + description: "The amount of this payment", + optional: true, + }, + refunded: { + type: "string", + label: "Refunded", + description: "The refunded amount of this payment", optional: true, }, companyGatewayId: { propDefinition: [ - invoice_ninja, + app, "companyGatewayId", ], optional: true, }, - paymentNumber: { - propDefinition: [ - invoice_ninja, - "paymentNumber", - ], + paymentAmount: { + type: "integer", + label: "Payment Amount", + description: "Amount of the payment", + optional: true, + }, + paymentables: { + type: "object", + label: "Paymentables", + description: "An object containing the paymentables configuration. **Example: { \"id\": \"AS3df3A\", \"invoice_id\": \"AS3df3A\", \"credit_id\": \"AS3df3A\", \"refunded\": \"10.00\", \"amount\": \"10.00\", \"updated_at\": \"1434342123\", \"created_at\": \"1434342123\" }** [See the documentation](https://api-docs.invoicing.co/#tag/payments/POST/api/v1/payments) for futher details", + optional: true, + }, + invoices: { + type: "string[]", + label: "Invoices", + description: "An array of invoice objects. **Example: [{\"invoice_id\": \"AS3df3A\", \"amount\": \"10.00\"}]** [See the documentation](https://api-docs.invoicing.co/#tag/payments/POST/api/v1/payments) for further details", + optional: true, + }, + credits: { + type: "string[]", + label: "Credits", + description: "An array of credit objects. **Example: [{\"credit_id\": \"AS3df3A\", \"amount\": \"10.00\"}]** [See the documentation](https://api-docs.invoicing.co/#tag/payments/POST/api/v1/payments) for further details", + optional: true, + }, + number: { + type: "string", + label: "Number", + description: "The payment number - is a unique alpha numeric number per payment per company", optional: true, }, }, async run({ $ }) { - const payment = await this.invoice_ninja.recordPayment({ - id: this.paymentId, - client_id: this.clientId, - client_contact_id: this.clientContactId, - user_id: this.userId, - type_id: this.typeId, - date: this.paymentDate, - amount: this.paymentAmount, - company_gateway_id: this.companyGatewayId, - number: this.paymentNumber, + const { data } = await this.app.recordPayment({ + $, + data: { + id: this.paymentId, + client_id: this.clientId, + client_contact_id: this.clientContactId, + user_id: this.userId, + type_id: this.typeId, + date: this.paymentDate, + transaction_reference: this.transactionReference, + assigned_user_id: this.assignedUserId, + private_notes: this.privateNotes, + is_manual: this.isManual, + is_deleted: this.isDeleted, + amount: this.amount && parseFloat(this.amount), + refunded: this.refunded && parseFloat(this.refunded), + company_gateway_id: this.companyGatewayId, + paymentables: this.paymentables, + invoices: parseObject(this.invoices), + credits: parseObject(this.credits), + number: this.number, + }, }); - $.export("$summary", `Payment ${this.paymentNumber || payment.id} created successfully`); - return payment; + $.export("$summary", `Payment ${data.id} created successfully`); + return data; }, }; diff --git a/components/invoice_ninja/common/constants.mjs b/components/invoice_ninja/common/constants.mjs new file mode 100644 index 0000000000000..c89250ef5e75c --- /dev/null +++ b/components/invoice_ninja/common/constants.mjs @@ -0,0 +1,34 @@ +export const CLASSIFICATION_OPTIONS = [ + { + label: "Individual", + value: "individual", + }, + { + label: "Business", + value: "business", + }, + { + label: "Company", + value: "company", + }, + { + label: "Partnership", + value: "partnership", + }, + { + label: "Trust", + value: "trust", + }, + { + label: "Charity", + value: "charity", + }, + { + label: "Government", + value: "government", + }, + { + label: "Other", + value: "other", + }, +]; diff --git a/components/invoice_ninja/common/utils.mjs b/components/invoice_ninja/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/invoice_ninja/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/invoice_ninja/invoice_ninja.app.mjs b/components/invoice_ninja/invoice_ninja.app.mjs index 42de9fe6f032f..91b9c788dc8e0 100644 --- a/components/invoice_ninja/invoice_ninja.app.mjs +++ b/components/invoice_ninja/invoice_ninja.app.mjs @@ -3,491 +3,238 @@ import { axios } from "@pipedream/platform"; export default { type: "app", app: "invoice_ninja", - version: "0.0.{{ts}}", propDefinitions: { - // Emit Events - emitNewClientEvent: { - type: "object", - label: "Emit New Client Event", - description: "Emits an event when a new client is added", - }, - emitNewInvoiceEvent: { - type: "object", - label: "Emit New Invoice Event", - description: "Emits an event when a new invoice is created", - }, - emitNewPaymentEvent: { - type: "object", - label: "Emit New Payment Event", - description: "Emits an event when a new payment is registered", - }, - // Create Invoice Props clientId: { type: "string", label: "Client ID", description: "The ID of the client to associate with the invoice", + async options({ page }) { + const { data } = await this.getClients({ + params: { + page: page + 1, + }, + }); + return data.map(({ + id: value, ...client + }) => ({ + label: `${client.display_name || client.name} ${client.email + ? `(${client.email})` + : ""}}`, + value, + })); + }, }, userId: { type: "string", label: "User ID", description: "The ID of the user creating the invoice", - optional: true, - async options() { - const users = await this.getUsers(); - return users.map((user) => ({ - label: user.name, - value: user.id, + async options({ page }) { + const { data } = await this.getUsers({ + params: { + page: page + 1, + }, + }); + return data.map(({ + id: value, ...user + }) => ({ + label: `${user.email || `${user.first_name} ${user.last_name}`}`, + value, })); }, }, - assignedUserId: { + industryId: { type: "string", - label: "Assigned User ID", - description: "The ID of the user to assign the invoice to", - optional: true, + label: "Industry ID", + description: "Industry ID of the client", async options() { - const users = await this.getUsers(); - return users.map((user) => ({ - label: user.name, - value: user.id, + const { industries } = await this.getStatics(); + return industries.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - statusId: { - type: "string", - label: "Status ID", - description: "The status ID of the invoice", - optional: true, - }, - number: { - type: "string", - label: "Invoice Number", - description: "The number of the invoice", - optional: true, - }, - poNumber: { - type: "string", - label: "PO Number", - description: "Purchase Order number for the invoice", - optional: true, - }, - terms: { - type: "string", - label: "Terms", - description: "Payment terms for the invoice", - optional: true, - }, - publicNotes: { - type: "string", - label: "Public Notes", - description: "Public notes for the invoice", - optional: true, - }, - privateNotes: { - type: "string", - label: "Private Notes", - description: "Private notes for the invoice", - optional: true, - }, - footer: { - type: "string", - label: "Footer", - description: "Footer content for the invoice", - optional: true, - }, - customValue1: { - type: "string", - label: "Custom Value 1", - description: "Custom field 1 for the invoice", - optional: true, - }, - customValue2: { - type: "string", - label: "Custom Value 2", - description: "Custom field 2 for the invoice", - optional: true, - }, - customValue3: { - type: "string", - label: "Custom Value 3", - description: "Custom field 3 for the invoice", - optional: true, - }, - customValue4: { - type: "string", - label: "Custom Value 4", - description: "Custom field 4 for the invoice", - optional: true, - }, - totalTaxes: { - type: "integer", - label: "Total Taxes", - description: "Total taxes for the invoice", - optional: true, - }, - lineItems: { - type: "string[]", - label: "Line Items", - description: "An array of line items in JSON format", - optional: true, - }, - amount: { - type: "integer", - label: "Amount", - description: "Total amount of the invoice", - optional: true, - }, - balance: { - type: "integer", - label: "Balance", - description: "Balance remaining on the invoice", - optional: true, - }, - paidToDate: { - type: "integer", - label: "Paid To Date", - description: "Amount paid to date for the invoice", - optional: true, - }, - discount: { - type: "integer", - label: "Discount", - description: "Discount applied to the invoice", - optional: true, - }, - date: { - type: "string", - label: "Invoice Date", - description: "Date of the invoice (YYYY-MM-DD)", - optional: true, - }, - dueDate: { + sizeId: { type: "string", - label: "Due Date", - description: "Due date for the invoice (YYYY-MM-DD)", + label: "Size ID", + description: "Size ID of the client", + async options() { + const { sizes } = await this.getStatics(); + return sizes.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, optional: true, }, - // Create Client Props - contacts: { - type: "string[]", - label: "Contacts", - description: "An array of contact objects in JSON format", - }, countryId: { type: "string", label: "Country ID", description: "The ID of the country for the client", async options() { - const countries = await this.getCountries(); - return countries.map((country) => ({ - label: country.name, - value: country.id, + const { countries } = await this.getStatics(); + return countries.map(({ + id: value, name, iso_3166_3: iso, + }) => ({ + label: `${name} (${iso})`, + value, })); }, }, - name: { - type: "string", - label: "Client Name", - description: "Name of the client", - optional: true, - }, - website: { - type: "string", - label: "Website", - description: "Website of the client", - optional: true, - }, - industryId: { - type: "string", - label: "Industry ID", - description: "Industry ID of the client", - optional: true, - }, - sizeId: { - type: "string", - label: "Size ID", - description: "Size ID of the client", - optional: true, - }, - address1: { - type: "string", - label: "Address 1", - description: "Primary address line for the client", - optional: true, - }, - address2: { - type: "string", - label: "Address 2", - description: "Secondary address line for the client", - optional: true, - }, - city: { - type: "string", - label: "City", - description: "City of the client", - optional: true, - }, - state: { - type: "string", - label: "State", - description: "State of the client", - optional: true, - }, - postalCode: { - type: "string", - label: "Postal Code", - description: "Postal code of the client", - optional: true, - }, - phone: { - type: "string", - label: "Phone", - description: "Phone number of the client", - optional: true, - }, - vatNumber: { - type: "string", - label: "VAT Number", - description: "VAT number of the client", - optional: true, - }, - idNumber: { - type: "string", - label: "ID Number", - description: "ID number of the client", - optional: true, - }, groupSettingsId: { type: "string", label: "Group Settings ID", description: "Group settings ID for the client", - optional: true, - async options() { - const groupSettings = await this.getGroupSettings(); - return groupSettings.map((group) => ({ - label: group.name, - value: group.id, + async options({ page }) { + const { data } = await this.getGroupSettings({ + params: { + page: page + 1, + }, + }); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - classification: { - type: "string", - label: "Classification", - description: "Classification of the client", - optional: true, - }, - // Record Payment Props - paymentId: { - type: "string", - label: "Payment ID", - description: "The ID of the payment", - optional: true, - }, clientContactId: { type: "string", label: "Client Contact ID", description: "The ID of the client's contact", - optional: true, + async options({ clientId }) { + const { data: { contacts } } = await this.showClient({ + clientId, + }); + return contacts.map(({ + id: value, ...contact + }) => ({ + label: `${contact.first_name} ${contact.last_name} ${contact.email + ? `(${contact.email})` + : ""}`, + value, + })); + }, }, typeId: { type: "string", label: "Type ID", - description: "Type ID of the payment", - optional: true, - }, - paymentDate: { - type: "string", - label: "Payment Date", - description: "Date of the payment (YYYY-MM-DD)", - optional: true, - }, - paymentAmount: { - type: "integer", - label: "Payment Amount", - description: "Amount of the payment", - optional: true, + description: "The Payment Type ID", + async options() { + const { payment_types: data } = await this.getStatics(); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, companyGatewayId: { type: "string", label: "Company Gateway ID", description: "The ID of the company gateway", - optional: true, async options() { - const gateways = await this.getCompanyGateways(); - return gateways.map((gateway) => ({ - label: gateway.name, - value: gateway.id, + const { data } = await this.getCompanyGateways(); + return data.map(({ + id: value, config: label, + }) => ({ + label, + value, })); }, }, - paymentNumber: { - type: "string", - label: "Payment Number", - description: "Number of the payment", - optional: true, - }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { - return "https://api.invoiceninja.com/api/v1"; + return "https://invoicing.co/api/v1"; }, - async _makeRequest(opts = {}) { - const { - $ = this, - method = "GET", - path = "/", - headers = {}, - ...otherOpts - } = opts; + _headers() { + return { + "X-Api-Token": this.$auth.api_token, + "X-Requested-With": "XMLHttpRequest", + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - ...otherOpts, - method, url: this._baseUrl() + path, - headers: { - ...headers, - "X-API-TOKEN": this.$auth.api_token, - "X-Requested-With": "XMLHttpRequest", - }, + headers: this._headers(), + ...opts, }); }, - async emitNewClientEvent(client) { - this.$emit("new_client", client); - }, - async emitNewInvoiceEvent(invoice) { - this.$emit("new_invoice", invoice); - }, - async emitNewPaymentEvent(payment) { - this.$emit("new_payment", payment); - }, - async createNewInvoice(data) { - const invoiceData = { - client_id: this.clientId, - user_id: this.userId, - assigned_user_id: this.assignedUserId, - status_id: this.statusId, - number: this.number, - po_number: this.poNumber, - terms: this.terms, - public_notes: this.publicNotes, - private_notes: this.privateNotes, - footer: this.footer, - custom_value1: this.customValue1, - custom_value2: this.customValue2, - custom_value3: this.customValue3, - custom_value4: this.customValue4, - total_taxes: this.totalTaxes, - line_items: this.lineItems - ? this.lineItems.map(JSON.parse) - : undefined, - amount: this.amount, - balance: this.balance, - paid_to_date: this.paidToDate, - discount: this.discount, - date: this.date, - due_date: this.dueDate, - ...data, - }; - const invoice = await this._makeRequest({ + createNewInvoice(opts = {}) { + return this._makeRequest({ method: "POST", path: "/invoices", - data: invoiceData, + ...opts, }); - await this.emitNewInvoiceEvent(invoice); - return invoice; }, - async createNewClient(data) { - const clientData = { - contacts: this.contacts.map(JSON.parse), - country_id: this.countryId, - name: this.name, - website: this.website, - private_notes: this.privateNotes, - industry_id: this.industryId, - size_id: this.sizeId, - address1: this.address1, - address2: this.address2, - city: this.city, - state: this.state, - postal_code: this.postalCode, - phone: this.phone, - vat_number: this.vatNumber, - id_number: this.idNumber, - group_settings_id: this.groupSettingsId, - classification: this.classification, - ...data, - }; - const client = await this._makeRequest({ + createNewClient(opts = {}) { + return this._makeRequest({ method: "POST", path: "/clients", - data: clientData, + ...opts, }); - await this.emitNewClientEvent(client); - return client; }, - async recordPayment(data) { - const paymentData = { - id: this.paymentId, - client_id: this.clientId, - client_contact_id: this.clientContactId, - user_id: this.userId, - type_id: this.typeId, - date: this.paymentDate, - amount: this.paymentAmount, - company_gateway_id: this.companyGatewayId, - number: this.paymentNumber, - ...data, - }; - const payment = await this._makeRequest({ + recordPayment(opts = {}) { + return this._makeRequest({ method: "POST", path: "/payments", - data: paymentData, + ...opts, + }); + }, + getClients() { + return this._makeRequest({ + path: "/clients", }); - await this.emitNewPaymentEvent(payment); - return payment; }, - async getUsers() { + getUsers() { return this._makeRequest({ path: "/users", }); }, - async getCountries() { + getStatics() { return this._makeRequest({ - path: "/countries", + path: "/statics", }); }, - async getGroupSettings() { + getGroupSettings() { return this._makeRequest({ path: "/group_settings", }); }, - async getCompanyGateways() { + getCompanyGateways() { return this._makeRequest({ path: "/company_gateways", }); }, - async paginate(fn, initialOpts = {}) { - const results = []; - let page = 1; - let more = true; - while (more) { - const response = await fn({ - page, - ...initialOpts, - }); - if (response.length > 0) { - results.push(...response); - page += 1; - } else { - more = false; - } - } - return results; + showClient({ clientId }) { + return this._makeRequest({ + path: `/clients/${clientId}`, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/webhooks", + ...opts, + }); + }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); }, }, }; diff --git a/components/invoice_ninja/package.json b/components/invoice_ninja/package.json new file mode 100644 index 0000000000000..f72dc0dd55673 --- /dev/null +++ b/components/invoice_ninja/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/invoice_ninja", + "version": "0.1.0", + "description": "Pipedream Invoice Ninja Components", + "main": "invoice_ninja.app.mjs", + "keywords": [ + "pipedream", + "invoice_ninja" + ], + "homepage": "https://pipedream.com/apps/invoice_ninja", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/invoice_ninja/sources/common/base.mjs b/components/invoice_ninja/sources/common/base.mjs new file mode 100644 index 0000000000000..3d84bfa0a8248 --- /dev/null +++ b/components/invoice_ninja/sources/common/base.mjs @@ -0,0 +1,47 @@ +import app from "../../invoice_ninja.app.mjs"; + +export default { + props: { + app, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getExtraData() { + return {}; + }, + }, + hooks: { + async activate() { + const { data } = await this.app.createWebhook({ + data: { + target_url: this.http.endpoint, + event_id: this.getEvent(), + format: "JSON", + }, + }); + + this._setHookId(data.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.app.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + this.$emit(body, { + id: body.id, + summary: this.getSummary(body), + ts: body.created_at, + }); + }, +}; diff --git a/components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs b/components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs index 9f07862ab047a..c93b626d52571 100644 --- a/components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs +++ b/components/invoice_ninja/sources/new-client-instant/new-client-instant.mjs @@ -1,92 +1,22 @@ -import invoice_ninja from "../../invoice_ninja.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "invoice_ninja-new-client-instant", name: "New Client (Instant)", - description: "Emit new event when a new client is added. [See the documentation](https://api.invoiceninja.com/docs)", - version: "0.0.{{ts}}", + description: "Emit new event when a new client is added.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - invoice_ninja: { - type: "app", - app: "invoice_ninja", + methods: { + ...common.methods, + getEvent() { + return 1; }, - http: { - type: "$.interface.http", - customResponse: true, + getSummary(client) { + return `New client: ${client.name}`; }, - db: "$.service.db", - }, - hooks: { - async activate() { - const webhookURL = this.http.endpoint; - const secret = crypto.randomBytes(32).toString("hex"); - await this.db.set("webhookSecret", secret); - const response = await this.invoice_ninja._makeRequest({ - method: "POST", - path: "/webhooks", - data: { - event: "client.created", - url: webhookURL, - secret: secret, - }, - }); - const webhookId = response.id; - await this.db.set("webhookId", webhookId); - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.invoice_ninja._makeRequest({ - method: "DELETE", - path: `/webhooks/${webhookId}`, - }); - await this.db.delete("webhookId"); - } - await this.db.delete("webhookSecret"); - }, - async deploy() { - const clients = await this.paginate(this.invoice_ninja.getClients, { - limit: 50, - }); - for (const client of clients.reverse()) { - this.$emit(client, { - id: client.id, - summary: `New client: ${client.name}`, - ts: Date.parse(client.created_at) || Date.now(), - }); - } - }, - }, - async run(event) { - const secret = await this.db.get("webhookSecret"); - const signature = event.headers["X-Signature"] || event.headers["x-signature"]; - const rawBody = JSON.stringify(event.body); - const computedSignature = crypto.createHmac("sha256", secret).update(rawBody) - .digest("hex"); - if (computedSignature !== signature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - const client = event.body; - const timestamp = Date.parse(client.created_at) || Date.now(); - - this.$emit(client, { - id: client.id, - summary: `New client: ${client.name}`, - ts: timestamp, - }); - - this.http.respond({ - status: 200, - body: "OK", - }); }, + sampleEmit, }; diff --git a/components/invoice_ninja/sources/new-client-instant/test-event.mjs b/components/invoice_ninja/sources/new-client-instant/test-event.mjs new file mode 100644 index 0000000000000..17b37844896d3 --- /dev/null +++ b/components/invoice_ninja/sources/new-client-instant/test-event.mjs @@ -0,0 +1,282 @@ +export default { + "id": "Opnel5aKBz", + "contacts": [ + { + "id": "Opnel5aKBz", + "user_id": "Opnel5aKBz", + "client_id": "Opnel5aKBz", + "first_name": "John", + "last_name": "Doe", + "phone": "555-152-4524", + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "email": "", + "accepted_terms_version": "A long set of ToS", + "password": "*****", + "confirmation_code": "333-sdjkh34gbasd", + "token": "333-sdjkh34gbasd", + "contact_key": "JD0X52bkfZlJRiroCJ0tcSiAjsJTntZ5uqKdiZ0a", + "is_primary": true, + "confirmed": true, + "is_locked": true, + "send_email": true, + "failed_logins": "3", + "email_verified_at": "134341234234", + "last_login": "134341234234", + "created_at": "134341234234", + "updated_at": "134341234234", + "deleted_at": "134341234234" + } + ], + "user_id": "Ua6Rw4pVbS", + "assigned_user_id": "Ua6Rw4pVbS", + "name": "Jim's Housekeeping", + "website": "https://www.jims-housekeeping.com", + "private_notes": "Client prefers email communication over phone calls", + "client_hash": "asdfkjhk342hjhbfdvmnfb1", + "industry_id": "5", + "size_id": "2", + "address1": "123 Main St", + "address2": "Apt 4B", + "city": "Beverly Hills", + "state": "California", + "postal_code": "90210", + "phone": "555-3434-3434", + "country_id": "1", + "custom_value1": "Preferred contact: Email", + "custom_value2": "Account manager: John Doe", + "custom_value3": "VIP client: Yes", + "custom_value4": "Annual contract value: $50,000", + "vat_number": "VAT123456", + "id_number": "…", + "number": "CL-0001", + "shipping_address1": "5 Wallaby Way", + "shipping_address2": "Suite 5", + "shipping_city": "Perth", + "shipping_state": "Western Australia", + "shipping_postal_code": "6110", + "shipping_country_id": "4", + "is_deleted": false, + "balance": "500.00", + "paid_to_date": "2000.00", + "credit_balance": "100.00", + "last_login": "1628686031", + "created_at": "1617629031", + "updated_at": "1628445631", + "group_settings_id": "Opnel5aKBz", + "routing_id": "Opnel5aKBz3489-dfkiu-2239-sdsd", + "is_tax_exempt": false, + "has_valid_vat_number": false, + "payment_balance": 100, + "settings": { + "currency_id": true, + "timezone_id": "15", + "date_format_id": "15", + "military_time": true, + "language_id": "1", + "show_currency_code": true, + "payment_terms": "1", + "company_gateway_ids": "1,2,3,4", + "custom_value1": "Custom Label", + "custom_value2": "Custom Label", + "custom_value3": "Custom Label", + "custom_value4": "Custom Label", + "default_task_rate": "10.00", + "send_reminders": true, + "enable_client_portal_tasks": true, + "email_style": "light", + "reply_to_email": "email@gmail.com", + "bcc_email": "email@gmail.com, contact@gmail.com", + "pdf_email_attachment": true, + "ubl_email_attachment": true, + "email_style_custom": "", + "counter_number_applied": "when_sent", + "quote_number_applied": "when_sent", + "custom_message_dashboard": "Please pay invoices immediately", + "custom_message_unpaid_invoice": "Please pay invoices immediately", + "custom_message_paid_invoice": "Thanks for paying this invoice!", + "custom_message_unapproved_quote": "Please approve quote", + "lock_invoices": true, + "auto_archive_invoice": true, + "auto_archive_quote": true, + "auto_convert_quote": true, + "inclusive_taxes": true, + "task_number_pattern": "{$year}-{$counter}", + "task_number_counter": "1", + "reminder_send_time": "32400", + "expense_number_pattern": "{$year}-{$counter}", + "expense_number_counter": "1", + "vendor_number_pattern": "{$year}-{$counter}", + "vendor_number_counter": "1", + "ticket_number_pattern": "{$year}-{$counter}", + "ticket_number_counter": "1", + "payment_number_pattern": "{$year}-{$counter}", + "payment_number_counter": "1", + "invoice_number_pattern": "{$year}-{$counter}", + "invoice_number_counter": "1", + "quote_number_pattern": "{$year}-{$counter}", + "quote_number_counter": "1", + "client_number_pattern": "{$year}-{$counter}", + "client_number_counter": "1", + "credit_number_pattern": "{$year}-{$counter}", + "credit_number_counter": "1", + "recurring_invoice_number_prefix": "R", + "reset_counter_frequency_id": "1", + "reset_counter_date": "2019-01-01", + "counter_padding": "1", + "shared_invoice_quote_counter": true, + "update_products": true, + "convert_products": true, + "fill_products": true, + "invoice_terms": "Invoice Terms are...", + "quote_terms": "Quote Terms are...", + "invoice_taxes": "1", + "invoice_design_id": "1", + "quote_design_id": "1", + "invoice_footer": "1", + "invoice_labels": "1", + "tax_rate1": "10", + "tax_name1": "GST", + "tax_rate2": "10", + "tax_name2": "GST", + "tax_rate3": "10", + "tax_name3": "GST", + "payment_type_id": "1", + "custom_fields": "{}", + "email_footer": "A default email footer", + "email_sending_method": "default", + "gmail_sending_user_id": "F76sd34D", + "email_subject_invoice": "Your Invoice Subject", + "email_subject_quote": "Your Quote Subject", + "email_subject_payment": "Your Payment Subject", + "email_template_invoice": "", + "email_template_quote": "", + "email_template_payment": "", + "email_subject_reminder1": "", + "email_subject_reminder2": "", + "email_subject_reminder3": "", + "email_subject_reminder_endless": "", + "email_template_reminder1": "", + "email_template_reminder2": "", + "email_template_reminder3": "", + "email_template_reminder_endless": "", + "enable_portal_password": true, + "show_accept_invoice_terms": true, + "show_accept_quote_terms": true, + "require_invoice_signature": true, + "require_quote_signature": true, + "name": "Acme Co", + "company_logo": "logo.png", + "website": "www.acme.com", + "address1": "Suite 888", + "address2": "5 Jimbo Way", + "city": "Sydney", + "state": "Florisa", + "postal_code": "90210", + "phone": "555-213-3948", + "email": "joe@acme.co", + "country_id": "1", + "vat_number": "32 120 377 720", + "page_size": "A4", + "font_size": "9", + "primary_font": "roboto", + "secondary_font": "roboto", + "hide_paid_to_date": false, + "embed_documents": false, + "all_pages_header": false, + "all_pages_footer": false, + "document_email_attachment": false, + "enable_client_portal_password": false, + "enable_email_markup": false, + "enable_client_portal_dashboard": false, + "enable_client_portal": false, + "email_template_statement": "template matter", + "email_subject_statement": "subject matter", + "signature_on_pdf": false, + "quote_footer": "the quote footer", + "email_subject_custom1": "Custom Subject 1", + "email_subject_custom2": "Custom Subject 2", + "email_subject_custom3": "Custom Subject 3", + "email_template_custom1": "", + "email_template_custom2": "", + "email_template_custom3": "", + "enable_reminder1": false, + "enable_reminder2": false, + "enable_reminder3": false, + "num_days_reminder1": "9", + "num_days_reminder2": "9", + "num_days_reminder3": "9", + "schedule_reminder1": "after_invoice_date", + "schedule_reminder2": "after_invoice_date", + "schedule_reminder3": "after_invoice_date", + "late_fee_amount1": 10, + "late_fee_amount2": 20, + "late_fee_amount3": 100, + "endless_reminder_frequency_id": "1", + "client_online_payment_notification": false, + "client_manual_payment_notification": false, + "enable_e_invoice": false, + "default_expense_payment_type_id": "0", + "e_invoice_type": "EN16931", + "mailgun_endpoint": "api.mailgun.net or api.eu.mailgun.net", + "client_initiated_payments": false, + "client_initiated_payments_minimum": 10, + "sync_invoice_quote_columns": false, + "show_task_item_description": false, + "allow_billable_task_items": false, + "accept_client_input_quote_approval": false, + "custom_sending_email": "bob@gmail.com", + "show_paid_stamp": false, + "show_shipping_address": false, + "company_logo_size": 100, + "show_email_footer": false, + "email_alignment": "left", + "auto_bill_standard_invoices": false, + "postmark_secret": "123456", + "mailgun_secret": "123456", + "mailgun_domain": "sandbox123456.mailgun.org", + "send_email_on_mark_paid": false, + "vendor_portal_enable_uploads": false, + "besr_id": "123456", + "qr_iban": "CH123456", + "email_subject_purchase_order": "Purchase Order", + "email_template_purchase_order": "Please see attached your purchase order.", + "require_purchase_order_signature": false, + "purchase_order_public_notes": "Please see attached your purchase order.", + "purchase_order_terms": "Please see attached your purchase order.", + "purchase_order_footer": "Please see attached your purchase order.", + "purchase_order_design_id": "hd677df", + "purchase_order_number_pattern": "PO-000000", + "purchase_order_number_counter": 1, + "page_numbering_alignment": "left", + "page_numbering": false, + "auto_archive_invoice_cancelled": false, + "email_from_name": "Bob Smith", + "show_all_tasks_client_portal": false, + "entity_send_time": 9, + "shared_invoice_credit_counter": false, + "reply_to_name": "Bob Smith", + "hide_empty_columns_on_pdf": false, + "enable_reminder_endless": false, + "use_credits_payment": false, + "recurring_invoice_number_pattern": "R-000000", + "recurring_invoice_number_counter": 1, + "client_portal_under_payment_minimum": 10, + "auto_bill_date": "on_send_date", + "primary_color": "#ffffff", + "secondary_color": "#ffffff", + "client_portal_allow_under_payment": false, + "client_portal_allow_over_payment": false, + "auto_bill": "off", + "client_portal_terms": "Please see attached your invoice.", + "client_portal_privacy_policy": "These are the terms of use for using the client portal.", + "client_can_register": false, + "portal_design_id": "hd677df", + "late_fee_endless_percent": 10, + "late_fee_endless_amount": 10, + "auto_email_invoice": false, + "email_signature": "Bob Smith" + } +} \ No newline at end of file diff --git a/components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs b/components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs index 09e7aa33e8b0a..c8908a37e4920 100644 --- a/components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs +++ b/components/invoice_ninja/sources/new-invoice-instant/new-invoice-instant.mjs @@ -1,90 +1,22 @@ -import invoice_ninja from "../../invoice_ninja.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "invoice_ninja-new-invoice-instant", - name: "New Invoice Created", - description: "Emit new event when a new invoice is created. [See the documentation]()", // Replace () with actual docs link if available - version: "0.0.{{ts}}", + name: "New Invoice Created (Instant)", + description: "Emit new event when a new invoice is created.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - invoice_ninja: { - type: "app", - app: "invoice_ninja", + methods: { + ...common.methods, + getEvent() { + return 2; }, - http: { - type: "$.interface.http", - customResponse: true, + getSummary(invoice) { + return `New invoice created: ${invoice.number}`; }, - db: "$.service.db", - }, - hooks: { - async deploy() { - const recentInvoices = await this.invoice_ninja.paginate(this.invoice_ninja.getInvoices, { - sort: "created_at_desc", - limit: 50, - }); - for (const invoice of recentInvoices) { - this.$emit(invoice, { - id: invoice.id || `${invoice.number}-${invoice.created_at}`, - summary: `New invoice created: ${invoice.number}`, - ts: Date.parse(invoice.created_at) || Date.now(), - }); - } - }, - async activate() { - const webhookUrl = this.http.endpoint; - const webhook = await this.invoice_ninja._makeRequest({ - method: "POST", - path: "/webhooks", - data: { - url: webhookUrl, - event: "invoice.created", - }, - }); - await this.db.set("webhookId", webhook.id); - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.invoice_ninja._makeRequest({ - method: "DELETE", - path: `/webhooks/${webhookId}`, - }); - await this.db.set("webhookId", null); - } - }, - }, - async run(event) { - const signature = event.headers["X-Signature"]; - const secret = this.invoice_ninja.$auth.webhook_secret; - const hmac = crypto.createHmac("sha256", secret); - const digest = hmac.update(event.body_raw).digest("hex"); - - if (digest !== signature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - const invoice = event.body; - const id = invoice.id || `${invoice.number}-${invoice.created_at}`; - const summary = `New invoice created: ${invoice.number}`; - const ts = Date.parse(invoice.created_at) || Date.now(); - - this.$emit(invoice, { - id, - summary, - ts, - }); - - this.http.respond({ - status: 200, - body: "OK", - }); }, + sampleEmit, }; diff --git a/components/invoice_ninja/sources/new-invoice-instant/test-event.mjs b/components/invoice_ninja/sources/new-invoice-instant/test-event.mjs new file mode 100644 index 0000000000000..b831cd3e7472e --- /dev/null +++ b/components/invoice_ninja/sources/new-invoice-instant/test-event.mjs @@ -0,0 +1,82 @@ +export default { + "id": "Opnel5aKBz", + "user_id": "Opnel5aKBz", + "assigned_user_id": "Opnel5aKBz", + "client_id": "Opnel5aKBz", + "status_id": "4", + "number": "INV_101", + "po_number": "PO-1234", + "terms": "These are invoice terms", + "public_notes": "These are some public notes", + "private_notes": "These are some private notes", + "footer": "", + "custom_value1": "2022-10-01", + "custom_value2": "Something custom", + "custom_value3": "", + "custom_value4": "", + "tax_name1": "", + "tax_name2": "", + "tax_rate1": "10.00", + "tax_rate2": "10.00", + "tax_name3": "", + "tax_rate3": "10.00", + "total_taxes": "10.00", + "line_items": [ + { + "quantity": 1, + "cost": 10, + "product_key": "Product key", + "product_cost": 10, + "notes": "Item notes", + "discount": 5, + "is_amount_discount": false, + "tax_name1": "GST", + "tax_rate1": 10, + "tax_name2": "VAT", + "tax_rate2": 5, + "tax_name3": "CA Sales Tax", + "tax_rate3": 3, + "sort_id": "0", + "line_total": 10, + "gross_line_total": 15, + "tax_amount": 1, + "date": "2023-03-19T00:00:00Z", + "custom_value1": "Custom value 1", + "custom_value2": "Custom value 2", + "custom_value3": "Custom value 3", + "custom_value4": "Custom value 4", + "type_id": "1", + "tax_id": "1" + } + ], + "invitations": [], + "amount": "10.00", + "balance": "10.00", + "paid_to_date": "10.00", + "discount": "10.00", + "partial": "10.00", + "is_amount_discount": true, + "is_deleted": true, + "uses_inclusive_taxes": true, + "date": "1994-07-30", + "last_sent_date": "1994-07-30", + "next_send_date": "1994-07-30", + "partial_due_date": "1994-07-30", + "due_date": "1994-07-30", + "last_viewed": "1434342123", + "updated_at": "1434342123", + "created_at": "1434342123", + "archived_at": "1434342123", + "custom_surcharge1": "10.00", + "custom_surcharge2": "10.00", + "custom_surcharge3": "10.00", + "custom_surcharge4": "10.00", + "custom_surcharge_tax1": true, + "custom_surcharge_tax2": true, + "custom_surcharge_tax3": true, + "custom_surcharge_tax4": true, + "project_id": "Opnel5aKBz", + "auto_bill_tries": "1", + "auto_bill_enabled": true, + "subscription_id": "Opnel5aKBz" +} \ No newline at end of file diff --git a/components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs b/components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs index 15b9a88390622..47b1346550d3f 100644 --- a/components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs +++ b/components/invoice_ninja/sources/new-payment-instant/new-payment-instant.mjs @@ -1,70 +1,22 @@ -import invoice_ninja from "../../invoice_ninja.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "invoice_ninja-new-payment-instant", - name: "New Payment Registered", - description: "Emit new event when a new payment is registered. [See the documentation]().", - version: "0.0.{{ts}}", + name: "New Payment Registered (Instant)", + description: "Emit new event when a new payment is registered.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - invoice_ninja: { - type: "app", - app: "invoice_ninja", + methods: { + ...common.methods, + getEvent() { + return 4; }, - http: { - type: "$.interface.http", - customResponse: true, + getSummary(payment) { + return `New payment created: ${payment.number}`; }, - db: "$.service.db", - }, - hooks: { - async activate() { - const webhookUrl = this.http.endpoint; - const payload = { - event: "payment.created", - url: webhookUrl, - }; - - const response = await this.invoice_ninja._makeRequest({ - method: "POST", - path: "/webhooks", - data: payload, - }); - - const webhookId = response.id; - await this.db.set("webhookId", webhookId); - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.invoice_ninja._makeRequest({ - method: "DELETE", - path: `/webhooks/${webhookId}`, - }); - await this.db.delete("webhookId"); - } - }, - async deploy() { - const payments = await this.invoice_ninja._makeRequest({ - method: "GET", - path: "/payments", - params: { - limit: 50, - sort: "created_at", - order: "desc", - }, - }); - - const last50Payments = payments.reverse(); - for (const payment of last50Payments) { - await this.invoice_ninja.emitNewPaymentEvent(payment); - } - }, - }, - async run(event) { - const payment = event.body; - await this.invoice_ninja.emitNewPaymentEvent(payment); }, + sampleEmit, }; diff --git a/components/invoice_ninja/sources/new-payment-instant/test-event.mjs b/components/invoice_ninja/sources/new-payment-instant/test-event.mjs new file mode 100644 index 0000000000000..6b96960c40c29 --- /dev/null +++ b/components/invoice_ninja/sources/new-payment-instant/test-event.mjs @@ -0,0 +1,42 @@ +export default { + "id": "Opnel5aKBz", + "client_id": "Opnel5aKBz", + "invitation_id": "Opnel5aKBz", + "client_contact_id": "Opnel5aKBz", + "user_id": "Opnel5aKBz", + "type_id": "1", + "date": "1-1-2014", + "transaction_reference": "xcsSxcs124asd", + "assigned_user_id": "Opnel5aKBz", + "private_notes": "The payment was refunded due to error", + "is_manual": true, + "is_deleted": true, + "amount": 10, + "refunded": 10, + "created_at": "1434342123", + "updated_at": "1434342123", + "archived_at": "1434342123", + "company_gateway_id": "3", + "paymentables": { + "id": "AS3df3A", + "invoice_id": "AS3df3A", + "credit_id": "AS3df3A", + "refunded": "10.00", + "amount": "10.00", + "updated_at": "1434342123", + "created_at": "1434342123" + }, + "invoices": [ + { + "invoice_id": "Opnel5aKBz", + "amount": "2" + } + ], + "credits": [ + { + "credit_id": "Opnel5aKBz", + "amount": "2" + } + ], + "number": "PAY_101" +} \ No newline at end of file From a33a884c68032c311c2635fd9738a97341fc97e7 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 24 Jan 2025 12:18:25 -0300 Subject: [PATCH 3/3] pnpm update --- pnpm-lock.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2d916b34964b..2984e40926adc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5333,6 +5333,12 @@ importers: specifier: ^3.0.0 version: 3.0.3 + components/invoice_ninja: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 + components/invoicing_plus: {} components/ip2location: @@ -8294,8 +8300,7 @@ importers: components/propeller: {} - components/proposify: - specifiers: {} + components/proposify: {} components/proprofs_quiz_maker: {}