@pipedream/quaderno 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Overview
2
+
3
+ The Quaderno API provides robust capabilities for handling sales tax, VAT, and GST compliance. It allows you to automate tax calculations, create and send invoices, and manage transactions and reports with ease. Integrating the Quaderno API on Pipedream opens up opportunities to streamline your finance operations by connecting to various other services like CRMs, payment gateways, and e-commerce platforms, all while leveraging Pipedream's serverless platform to execute custom logic without managing infrastructure.
4
+
5
+ # Example Use Cases
6
+
7
+ - **Automated Tax Compliance for E-Commerce Sales**: Set up a workflow that triggers whenever a new order is placed in your e-commerce platform (like Shopify). The workflow calculates the appropriate taxes using Quaderno, creates an invoice, and then stores the transaction details in a Google Sheet for record-keeping.
8
+
9
+ - **Invoice Generation on Subscription Renewal**: Each time a subscription renews in a payment service like Stripe, trigger a Pipedream workflow that uses Quaderno to generate an invoice with the correct tax rates and sends it to the customer via email, simplifying the recurring billing process.
10
+
11
+ - **Expense Tracking and Reporting**: Create a workflow that listens for new expenses entered in an accounting app like QuickBooks. When a new expense is detected, the workflow adds the expense to Quaderno, calculates the tax implications, and then updates a dashboard in a BI tool like Tableau, providing real-time expense tracking and insights.
@@ -0,0 +1,163 @@
1
+ import app from "../../quaderno.app.mjs";
2
+ import constants from "../../common/constants.mjs";
3
+
4
+ const { SEP } = constants;
5
+
6
+ export default {
7
+ props: {
8
+ app,
9
+ firstName: {
10
+ description: "The customer's first name who will be billed.",
11
+ propDefinition: [
12
+ app,
13
+ "firstName",
14
+ ],
15
+ },
16
+ lastName: {
17
+ description: "The customer's last name who will be billed.",
18
+ propDefinition: [
19
+ app,
20
+ "lastName",
21
+ ],
22
+ },
23
+ dueDate: {
24
+ propDefinition: [
25
+ app,
26
+ "dueDate",
27
+ ],
28
+ },
29
+ currency: {
30
+ propDefinition: [
31
+ app,
32
+ "currency",
33
+ ],
34
+ },
35
+ recurringPeriod: {
36
+ propDefinition: [
37
+ app,
38
+ "recurringPeriod",
39
+ ],
40
+ },
41
+ recurringFrequency: {
42
+ propDefinition: [
43
+ app,
44
+ "recurringFrequency",
45
+ ],
46
+ },
47
+ country: {
48
+ propDefinition: [
49
+ app,
50
+ "country",
51
+ ],
52
+ },
53
+ postalCode: {
54
+ propDefinition: [
55
+ app,
56
+ "postalCode",
57
+ ],
58
+ },
59
+ region: {
60
+ propDefinition: [
61
+ app,
62
+ "region",
63
+ ],
64
+ },
65
+ streetLine1: {
66
+ propDefinition: [
67
+ app,
68
+ "streetLine1",
69
+ ],
70
+ },
71
+ subject: {
72
+ propDefinition: [
73
+ app,
74
+ "subject",
75
+ ],
76
+ },
77
+ howManyItems: {
78
+ propDefinition: [
79
+ app,
80
+ "howManyItems",
81
+ ],
82
+ },
83
+ },
84
+ additionalProps() {
85
+ return Array.from({
86
+ length: this.howManyItems,
87
+ }).reduce((props, _, idx) => {
88
+ const counter = idx + 1;
89
+ const item = `item${counter}`;
90
+ const label = `Item ${counter}:`;
91
+ const description = `${item}${SEP}description`;
92
+ const discountRate = `${item}${SEP}discountRate`;
93
+ const productCode = `${item}${SEP}productCode`;
94
+ const quantity = `${item}${SEP}quantity`;
95
+ const totalAmount = `${item}${SEP}totalAmount`;
96
+ const unitPrice = `${item}${SEP}unitPrice`;
97
+ return {
98
+ ...props,
99
+ [description]: {
100
+ type: "string",
101
+ label: `${label} Description`,
102
+ description: "The description of the item.",
103
+ optional: true,
104
+ },
105
+ [discountRate]: {
106
+ type: "string",
107
+ label: `${label} Discount Rate`,
108
+ description: "Discount percent out of 100, if applicable.",
109
+ optional: true,
110
+ },
111
+ [productCode]: {
112
+ type: "string",
113
+ label: `${label} Product Code`,
114
+ description: "The SKU of the Quaderno **Product** being invoiced. Use this attribute if you want to track your sales per product.",
115
+ optional: true,
116
+ },
117
+ [quantity]: {
118
+ type: "integer",
119
+ label: `${label} Quantity`,
120
+ description: "The quantity of the item.",
121
+ optional: true,
122
+ default: 1,
123
+ },
124
+ [totalAmount]: {
125
+ type: "string",
126
+ label: `${label} Total Amount`,
127
+ description: "The total amount to be charged after discounts and taxes. Required if **Unit Price** is not passed.",
128
+ optional: true,
129
+ },
130
+ [unitPrice]: {
131
+ type: "string",
132
+ label: `${label} Unit Price`,
133
+ description: "The unit price of the item before any discount or tax is applied. Required if **Total Amount** is not passed.",
134
+ optional: true,
135
+ },
136
+ };
137
+ }, {});
138
+ },
139
+ methods: {
140
+ getItems(length) {
141
+ return Array.from({
142
+ length,
143
+ }).map((_, idx) => {
144
+ const counter = idx + 1;
145
+ const item = `item${counter}`;
146
+ const description = this[`${item}${SEP}description`];
147
+ const discountRate = this[`${item}${SEP}discountRate`];
148
+ const productCode = this[`${item}${SEP}productCode`];
149
+ const quantity = this[`${item}${SEP}quantity`];
150
+ const totalAmount = this[`${item}${SEP}totalAmount`];
151
+ const unitPrice = this[`${item}${SEP}unitPrice`];
152
+ return {
153
+ description,
154
+ discount_rate: discountRate,
155
+ product_code: productCode,
156
+ quantity,
157
+ total_amount: totalAmount,
158
+ unit_price: unitPrice,
159
+ };
160
+ });
161
+ },
162
+ },
163
+ };
@@ -0,0 +1,148 @@
1
+ import constants from "../../common/constants.mjs";
2
+ import app from "../../quaderno.app.mjs";
3
+
4
+ export default {
5
+ key: "quaderno-create-contact",
6
+ name: "Create Contact",
7
+ description: "Add a new contact to Quaderno. [See the Documentation](https://developers.quaderno.io/api/#tag/Contacts/operation/createContact).",
8
+ type: "action",
9
+ version: "0.0.3",
10
+ annotations: {
11
+ destructiveHint: false,
12
+ openWorldHint: true,
13
+ readOnlyHint: false,
14
+ },
15
+ props: {
16
+ app,
17
+ kind: {
18
+ type: "string",
19
+ label: "Kind",
20
+ description: "The type of contact. Can be `person` or `company`",
21
+ options: Object.values(constants.CONTACT_TYPE),
22
+ default: constants.CONTACT_TYPE.PERSON,
23
+ reloadProps: true,
24
+ },
25
+ country: {
26
+ propDefinition: [
27
+ app,
28
+ "country",
29
+ ],
30
+ },
31
+ city: {
32
+ propDefinition: [
33
+ app,
34
+ "city",
35
+ ],
36
+ },
37
+ region: {
38
+ propDefinition: [
39
+ app,
40
+ "region",
41
+ ],
42
+ },
43
+ streetLine1: {
44
+ propDefinition: [
45
+ app,
46
+ "streetLine1",
47
+ ],
48
+ },
49
+ email: {
50
+ propDefinition: [
51
+ app,
52
+ "email",
53
+ ],
54
+ },
55
+ phone1: {
56
+ propDefinition: [
57
+ app,
58
+ "phone1",
59
+ ],
60
+ },
61
+ postalCode: {
62
+ propDefinition: [
63
+ app,
64
+ "postalCode",
65
+ ],
66
+ },
67
+ fullName: {
68
+ propDefinition: [
69
+ app,
70
+ "fullName",
71
+ ],
72
+ },
73
+ },
74
+ async additionalProps() {
75
+ if (this.kind === constants.CONTACT_TYPE.PERSON) {
76
+ return {
77
+ firstName: {
78
+ type: "string",
79
+ label: "First Name",
80
+ description: "The contact's first name.",
81
+ },
82
+ lastName: {
83
+ type: "string",
84
+ label: "Last Name",
85
+ description: "The contact's last name.",
86
+ optional: true,
87
+ },
88
+ };
89
+ }
90
+ return {
91
+ firstName: {
92
+ type: "string",
93
+ label: "Business Name",
94
+ description: "The contact's business name.",
95
+ },
96
+ department: {
97
+ type: "string",
98
+ label: "Department",
99
+ description: "If the contact is a `company`, this is the deparment.",
100
+ optional: true,
101
+ },
102
+ contactPerson: {
103
+ type: "string",
104
+ label: "Contact Person",
105
+ description: "If the contact is a `company`, this is its contact person.",
106
+ optional: true,
107
+ },
108
+ };
109
+ },
110
+ methods: {
111
+ createContact(args = {}) {
112
+ return this.app.post({
113
+ path: "/contacts",
114
+ ...args,
115
+ });
116
+ },
117
+ },
118
+ async run({ $: step }) {
119
+ const {
120
+ kind, country, city, region, streetLine1, contactPerson,
121
+ department, email, phone1, postalCode, fullName, firstName,
122
+ lastName,
123
+ } = this;
124
+
125
+ const response = await this.createContact({
126
+ step,
127
+ data: {
128
+ kind,
129
+ country,
130
+ city,
131
+ region,
132
+ street_line_1: streetLine1,
133
+ contact_person: contactPerson,
134
+ department,
135
+ email,
136
+ phone_1: phone1,
137
+ postal_code: postalCode,
138
+ full_name: fullName,
139
+ first_name: firstName,
140
+ last_name: lastName,
141
+ },
142
+ });
143
+
144
+ step.export("$summary", `Successfully created contact with ID ${response.id}`);
145
+
146
+ return response;
147
+ },
148
+ };
@@ -0,0 +1,62 @@
1
+ import common from "../common/invoice.mjs";
2
+
3
+ export default {
4
+ ...common,
5
+ key: "quaderno-create-invoice",
6
+ name: "Create Invoice",
7
+ description: "Generate a new invoice in Quaderno. [See the Documentation](https://developers.quaderno.io/api/#tag/Invoices/operation/createInvoice).",
8
+ type: "action",
9
+ version: "0.0.2",
10
+ annotations: {
11
+ destructiveHint: false,
12
+ openWorldHint: true,
13
+ readOnlyHint: false,
14
+ },
15
+ methods: {
16
+ ...common.methods,
17
+ createInvoice(args = {}) {
18
+ return this.app.post({
19
+ path: "/invoices",
20
+ ...args,
21
+ });
22
+ },
23
+ },
24
+ async run({ $: step }) {
25
+ const {
26
+ firstName,
27
+ lastName,
28
+ dueDate,
29
+ currency,
30
+ recurringPeriod,
31
+ recurringFrequency,
32
+ country,
33
+ postalCode,
34
+ region,
35
+ streetLine1,
36
+ howManyItems,
37
+ } = this;
38
+
39
+ const response = await this.createInvoice({
40
+ step,
41
+ data: {
42
+ contact: {
43
+ first_name: firstName,
44
+ last_name: lastName,
45
+ },
46
+ due_date: dueDate,
47
+ currency,
48
+ recurring_period: recurringPeriod,
49
+ recurring_frequency: recurringFrequency,
50
+ country,
51
+ postal_code: postalCode,
52
+ region,
53
+ street_line_1: streetLine1,
54
+ items_attributes: this.getItems(howManyItems),
55
+ },
56
+ });
57
+
58
+ step.export("$summary", `Successfully created invoice with ID ${response.id}`);
59
+
60
+ return response;
61
+ },
62
+ };
@@ -0,0 +1,83 @@
1
+ import common from "../common/invoice.mjs";
2
+
3
+ export default {
4
+ ...common,
5
+ key: "quaderno-update-invoice",
6
+ name: "Update Invoice",
7
+ description: "Modify an existing invoice's details in Quaderno. [See the Documentation](https://developers.quaderno.io/api/#tag/Invoices/operation/updateInvoice).",
8
+ type: "action",
9
+ version: "0.0.2",
10
+ annotations: {
11
+ destructiveHint: true,
12
+ openWorldHint: true,
13
+ readOnlyHint: false,
14
+ },
15
+ props: {
16
+ ...common.props,
17
+ invoiceId: {
18
+ propDefinition: [
19
+ common.props.app,
20
+ "invoiceId",
21
+ ],
22
+ },
23
+ howManyItems: {
24
+ optional: true,
25
+ propDefinition: [
26
+ common.props.app,
27
+ "howManyItems",
28
+ ],
29
+ description: "The items here will be ADDED to the existing invoice items",
30
+ },
31
+ },
32
+ methods: {
33
+ ...common.methods,
34
+ updateInvoice({
35
+ invoiceId, ...args
36
+ } = {}) {
37
+ return this.app.put({
38
+ path: `/invoices/${invoiceId}`,
39
+ ...args,
40
+ });
41
+ },
42
+ },
43
+ async run({ $: step }) {
44
+ const {
45
+ invoiceId,
46
+ firstName,
47
+ lastName,
48
+ dueDate,
49
+ currency,
50
+ recurringPeriod,
51
+ recurringFrequency,
52
+ country,
53
+ postalCode,
54
+ region,
55
+ streetLine1,
56
+ howManyItems,
57
+ } = this;
58
+
59
+ const response = await this.updateInvoice({
60
+ step,
61
+ invoiceId,
62
+ data: {
63
+ contact: {
64
+ first_name: firstName,
65
+ last_name: lastName,
66
+ },
67
+ due_date: dueDate,
68
+ currency,
69
+ recurring_period: recurringPeriod,
70
+ recurring_frequency: recurringFrequency,
71
+ country,
72
+ postal_code: postalCode,
73
+ region,
74
+ street_line_1: streetLine1,
75
+ items_attributes: this.getItems(howManyItems),
76
+ },
77
+ });
78
+
79
+ step.export("$summary", `Successfully updated invoice with ID ${response.id}`);
80
+
81
+ return response;
82
+ },
83
+ };
@@ -0,0 +1,40 @@
1
+ const DOMAIN_PLACEHOLDER = "{domain}";
2
+ const ACCOUNT_PLACEHOLDER = "{account_name}";
3
+ const BASE_URL = `https://${ACCOUNT_PLACEHOLDER}.${DOMAIN_PLACEHOLDER}`;
4
+ const VERSION_PATH = "/api";
5
+ const LAST_CREATED_AT = "lastCreatedAt";
6
+ const DEFAULT_MAX = 600;
7
+
8
+ const API_VERSION = "20220325";
9
+
10
+ const CONTACT_TYPE = {
11
+ PERSON: "person",
12
+ COMPANY: "company",
13
+ };
14
+
15
+ const PERIOD = {
16
+ DAYS: "days",
17
+ WEEKS: "weeks",
18
+ MONTHS: "months",
19
+ YEARS: "years",
20
+ };
21
+
22
+ const SEP = "_";
23
+
24
+ const WEBHOOK_ID = "webhookId";
25
+ const AUTH_KEY = "authKey";
26
+
27
+ export default {
28
+ BASE_URL,
29
+ VERSION_PATH,
30
+ DEFAULT_MAX,
31
+ LAST_CREATED_AT,
32
+ API_VERSION,
33
+ DOMAIN_PLACEHOLDER,
34
+ ACCOUNT_PLACEHOLDER,
35
+ CONTACT_TYPE,
36
+ PERIOD,
37
+ SEP,
38
+ WEBHOOK_ID,
39
+ AUTH_KEY,
40
+ };
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@pipedream/quaderno",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Pipedream Quaderno Components",
5
- "main": "dist/app/quaderno.app.mjs",
5
+ "main": "quaderno.app.mjs",
6
6
  "keywords": [
7
7
  "pipedream",
8
8
  "quaderno"
9
9
  ],
10
- "files": [
11
- "dist"
12
- ],
13
10
  "homepage": "https://pipedream.com/apps/quaderno",
14
11
  "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
12
+ "dependencies": {
13
+ "@pipedream/platform": "^1.6.8"
14
+ },
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  }
@@ -0,0 +1,187 @@
1
+ import { axios } from "@pipedream/platform";
2
+ import constants from "./common/constants.mjs";
3
+
4
+ export default {
5
+ type: "app",
6
+ app: "quaderno",
7
+ propDefinitions: {
8
+ invoiceId: {
9
+ type: "string",
10
+ label: "Invoice ID",
11
+ description: "The ID of the invoice to retrieve.",
12
+ async options() {
13
+ const invoices = await this.listInvoices();
14
+ return invoices.map(({
15
+ id, contact,
16
+ }) => ({
17
+ value: String(id),
18
+ label: contact?.full_name
19
+ ? `${id} (${contact.full_name})`
20
+ : id,
21
+ }));
22
+ },
23
+ },
24
+ firstName: {
25
+ type: "string",
26
+ label: "First Name",
27
+ description: "The contact's first name.",
28
+ },
29
+ lastName: {
30
+ type: "string",
31
+ label: "Last Name",
32
+ description: "The contact's last name.",
33
+ optional: true,
34
+ },
35
+ country: {
36
+ type: "string",
37
+ label: "Country",
38
+ description: "2-letter [ISO country code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes).",
39
+ optional: true,
40
+ },
41
+ city: {
42
+ type: "string",
43
+ label: "City",
44
+ description: "City/District/Suburb/Town/Village.",
45
+ optional: true,
46
+ },
47
+ region: {
48
+ type: "string",
49
+ label: "Region",
50
+ description: "State/Province/Region.",
51
+ optional: true,
52
+ },
53
+ streetLine1: {
54
+ type: "string",
55
+ label: "Street Line 1",
56
+ description: "Address line 1 (Street address/PO Box).",
57
+ optional: true,
58
+ },
59
+ email: {
60
+ type: "string",
61
+ label: "Email",
62
+ description: "The contact's email address.",
63
+ optional: true,
64
+ },
65
+ phone1: {
66
+ type: "string",
67
+ label: "Phone Number",
68
+ description: "The contact's phone number.",
69
+ optional: true,
70
+ },
71
+ postalCode: {
72
+ type: "string",
73
+ label: "Postal Code",
74
+ description: "ZIP or postal code.",
75
+ optional: true,
76
+ },
77
+ fullName: {
78
+ type: "string",
79
+ label: "Full Name",
80
+ description: "The contact's full name.",
81
+ optional: true,
82
+ },
83
+ dueDate: {
84
+ type: "string",
85
+ label: "Due Date",
86
+ description: "The date on which payment for this invoice is due. Must be in `YYYY-MM-DD` format.",
87
+ optional: true,
88
+ },
89
+ currency: {
90
+ type: "string",
91
+ label: "Currency",
92
+ description: "Three-letter [ISO currency code](https://en.wikipedia.org/wiki/ISO_4217), in uppercase.",
93
+ optional: true,
94
+ },
95
+ recurringPeriod: {
96
+ type: "string",
97
+ label: "Recurring Period",
98
+ description: "The period of time between each invoice. Can be `days`, `weeks`, `months`, `years`.",
99
+ optional: true,
100
+ options: Object.values(constants.PERIOD),
101
+ },
102
+ recurringFrequency: {
103
+ type: "integer",
104
+ label: "Recurring Frequency",
105
+ description: "The number of periods between each invoice.",
106
+ optional: true,
107
+ },
108
+ subject: {
109
+ type: "string",
110
+ label: "Subject",
111
+ description: "The subject of the invoice.",
112
+ optional: true,
113
+ },
114
+ howManyItems: {
115
+ type: "integer",
116
+ label: "How Many Items",
117
+ description: "The number of line items to add to the invoice.",
118
+ reloadProps: true,
119
+ default: 1,
120
+ },
121
+ },
122
+ methods: {
123
+ getBaseUrl() {
124
+ const baseUrl = `${constants.BASE_URL}${constants.VERSION_PATH}`;
125
+ return baseUrl.replace(constants.ACCOUNT_PLACEHOLDER, this.$auth.account_name)
126
+ .replace(constants.DOMAIN_PLACEHOLDER, this.$auth.domain);
127
+ },
128
+ getUrl(path, url) {
129
+ return url || `${this.getBaseUrl()}${path}`;
130
+ },
131
+ getAuth() {
132
+ return {
133
+ username: this.$auth.api_key,
134
+ };
135
+ },
136
+ getHeaders(headers) {
137
+ return {
138
+ "Content-Type": "application/json",
139
+ "Accept": `application/json; api_version=${constants.API_VERSION}`,
140
+ ...headers,
141
+ };
142
+ },
143
+ makeRequest({
144
+ step = this, path, headers, url, ...args
145
+ } = {}) {
146
+
147
+ const config = {
148
+ auth: this.getAuth(),
149
+ headers: this.getHeaders(headers),
150
+ url: this.getUrl(path, url),
151
+ ...args,
152
+ };
153
+
154
+ return axios(step, config);
155
+ },
156
+ post(args = {}) {
157
+ return this.makeRequest({
158
+ method: "post",
159
+ ...args,
160
+ });
161
+ },
162
+ put(args = {}) {
163
+ return this.makeRequest({
164
+ method: "put",
165
+ ...args,
166
+ });
167
+ },
168
+ delete(args = {}) {
169
+ return this.makeRequest({
170
+ method: "delete",
171
+ ...args,
172
+ });
173
+ },
174
+ listInvoices(args = {}) {
175
+ return this.makeRequest({
176
+ path: "/invoices",
177
+ ...args,
178
+ });
179
+ },
180
+ listPayments(args = {}) {
181
+ return this.makeRequest({
182
+ path: "/payments",
183
+ ...args,
184
+ });
185
+ },
186
+ },
187
+ };
@@ -0,0 +1,14 @@
1
+ import { ConfigurationError } from "@pipedream/platform";
2
+ import app from "../../quaderno.app.mjs";
3
+
4
+ export default {
5
+ props: {
6
+ app,
7
+ db: "$.service.db",
8
+ },
9
+ methods: {
10
+ generateMeta() {
11
+ throw new ConfigurationError("generateMeta is not implemented");
12
+ },
13
+ },
14
+ };
@@ -0,0 +1,25 @@
1
+ // Docs: https://developers.quaderno.io/guides/webhooks/#events
2
+ export default {
3
+ ACCOUNT_UPDATED: "account.updated",
4
+ ACCOUNT_APPLICATION_DEAUTHORIZED: "account.application.deauthorized",
5
+ CHECKOUT_SUCCEEDED: "checkout.succeeded",
6
+ CHECKOUT_FAILED: "checkout.failed",
7
+ CHECKOUT_ABANDONED: "checkout.abandoned",
8
+ CONTACT_CREATED: "contact.created",
9
+ CONTACT_UPDATED: "contact.updated",
10
+ CONTACT_DELETED: "contact.deleted",
11
+ CREDIT_CREATED: "credit.created",
12
+ CREDIT_UPDATED: "credit.updated",
13
+ EXPENSE_CREATED: "expense.created",
14
+ EXPENSE_UPDATED: "expense.updated",
15
+ EXPENSE_DELETED: "expense.deleted",
16
+ INVOICE_CREATED: "invoice.created",
17
+ INVOICE_UPDATED: "invoice.updated",
18
+ PAYMENT_CREATED: "payment.created",
19
+ PAYMENT_DELETED: "payment.deleted",
20
+ REPORTING_REQUEST_SUCCEDED: "reporting.request.suceeded",
21
+ REPORTING_REQUEST_FAILED: "reporting.request.failed",
22
+ THRESHOLD_WARNING: "threshold.warning",
23
+ THRESHOLD_EXCEEDED: "threshold.exceeded",
24
+ THRESHOLD_EU_100K: "threshold.eu.100k",
25
+ };
@@ -0,0 +1,103 @@
1
+ import { createHmac } from "crypto";
2
+ import { ConfigurationError } from "@pipedream/platform";
3
+ import common from "./base.mjs";
4
+ import constants from "../../common/constants.mjs";
5
+
6
+ export default {
7
+ ...common,
8
+ props: {
9
+ ...common.props,
10
+ http: {
11
+ type: "$.interface.http",
12
+ customResponse: true,
13
+ },
14
+ },
15
+ hooks: {
16
+ async activate() {
17
+ const response =
18
+ await this.createWebhook({
19
+ data: {
20
+ url: this.http.endpoint,
21
+ events_types: this.getEventName(),
22
+ },
23
+ });
24
+
25
+ this.setWebhookId(response.id);
26
+ this.setAuthKey(response.auth_key);
27
+ },
28
+ async deactivate() {
29
+ const webhookId = this.getWebhookId();
30
+ if (webhookId) {
31
+ await this.deleteWebhook({
32
+ webhookId,
33
+ });
34
+ }
35
+ },
36
+ },
37
+ methods: {
38
+ ...common.methods,
39
+ setWebhookId(value) {
40
+ this.db.set(constants.WEBHOOK_ID, value);
41
+ },
42
+ getWebhookId() {
43
+ return this.db.get(constants.WEBHOOK_ID);
44
+ },
45
+ setAuthKey(value) {
46
+ this.db.set(constants.AUTH_KEY, value);
47
+ },
48
+ getAuthKey() {
49
+ return this.db.get(constants.AUTH_KEY);
50
+ },
51
+ getEventName() {
52
+ throw new ConfigurationError("getEventName is not implemented");
53
+ },
54
+ createWebhook(args = {}) {
55
+ return this.app.post({
56
+ path: "/webhooks",
57
+ ...args,
58
+ });
59
+ },
60
+ deleteWebhook({
61
+ webhookId, ...args
62
+ } = {}) {
63
+ return this.app.delete({
64
+ path: `/webhooks/${webhookId}`,
65
+ ...args,
66
+ });
67
+ },
68
+ isSignatureValid(signature, data, skip = true) {
69
+ // skip signature validation for now. Due to the following issue:
70
+ // https://github.com/quaderno/quaderno-api/issues/54
71
+ if (skip) {
72
+ return true;
73
+ }
74
+ const authKey = this.getAuthKey();
75
+ const computedSignature = createHmac("sha1", authKey)
76
+ .update(data)
77
+ .digest("base64");
78
+
79
+ return computedSignature === signature;
80
+ },
81
+ processEvent(event) {
82
+ this.$emit(event, this.generateMeta(event.data?.object || event));
83
+ },
84
+ },
85
+ async run({
86
+ method, url, body, headers, bodyRaw,
87
+ }) {
88
+ if (method === "HEAD") {
89
+ return this.http.respond({
90
+ status: 200,
91
+ });
92
+ }
93
+
94
+ const signature = headers["x-quaderno-signature"];
95
+ const data = `${url}${bodyRaw}`;
96
+
97
+ if (!this.isSignatureValid(signature, data)) {
98
+ throw new Error("Invalid signature");
99
+ }
100
+
101
+ this.processEvent(body);
102
+ },
103
+ };
@@ -0,0 +1,34 @@
1
+ import common from "../common/webhook.mjs";
2
+ import events from "../common/events.mjs";
3
+
4
+ export default {
5
+ ...common,
6
+ key: "quaderno-invoice-created",
7
+ name: "New Invoice Created",
8
+ description: "Emit new event when a new invoice is generated in Quaderno. [See the Documentation](https://developers.quaderno.io/api/#tag/Webhooks/operation/createWebhook).",
9
+ type: "source",
10
+ version: "0.0.1",
11
+ dedupe: "unique",
12
+ hooks: {
13
+ async deploy() {
14
+ const invoices = await this.app.listInvoices();
15
+ invoices.reverse().forEach(this.processEvent);
16
+ },
17
+ ...common.hooks,
18
+ },
19
+ methods: {
20
+ ...common.methods,
21
+ getEventName() {
22
+ return [
23
+ events.INVOICE_CREATED,
24
+ ];
25
+ },
26
+ generateMeta(resource) {
27
+ return {
28
+ id: resource.id,
29
+ summary: `New Invoice: ${resource.id}`,
30
+ ts: Date.now(),
31
+ };
32
+ },
33
+ },
34
+ };
@@ -0,0 +1,27 @@
1
+ import common from "../common/webhook.mjs";
2
+ import events from "../common/events.mjs";
3
+
4
+ export default {
5
+ ...common,
6
+ key: "quaderno-payment-received",
7
+ name: "New Payment Received",
8
+ description: "Emit new event when a payment is successfully processed in Quaderno. [See the Documentation](https://developers.quaderno.io/api/#tag/Webhooks/operation/createWebhook).",
9
+ type: "source",
10
+ version: "0.0.1",
11
+ dedupe: "unique",
12
+ methods: {
13
+ ...common.methods,
14
+ getEventName() {
15
+ return [
16
+ events.PAYMENT_CREATED,
17
+ ];
18
+ },
19
+ generateMeta(resource) {
20
+ return {
21
+ id: resource.id,
22
+ summary: `New Payment: ${resource.id}`,
23
+ ts: Date.now(),
24
+ };
25
+ },
26
+ },
27
+ };