@getpopapi/n8n-nodes-pop-zoho 0.1.0

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Babini Mazzari S.r.l.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # n8n-nodes-pop-zoho
2
+
3
+ ![POP Cloud API](https://img.shields.io/badge/POP_Cloud_API-v2-ff5f5e?style=flat-square)
4
+ ![License](https://img.shields.io/badge/license-MIT-a8dadc?style=flat-square)
5
+ ![n8n community](https://img.shields.io/badge/n8n-community_node-1a1a1a?style=flat-square)
6
+
7
+ An n8n community node that receives a [POP API](https://popapi.io) payload and automatically creates the corresponding document in **Zoho Invoice** or **Zoho Books**.
8
+
9
+ Supported document types:
10
+ - `TD01` → Invoice (`POST /invoices`)
11
+ - `TD04` → Credit Note (`POST /creditnotes`)
12
+
13
+ ![](.github/images/node-picker.png)
14
+
15
+ ---
16
+
17
+ ## Table of Contents
18
+
19
+ - [How It Works](#how-it-works)
20
+ - [Prerequisites](#prerequisites)
21
+ - [Installation](#installation)
22
+ - [Credentials Setup](#credentials-setup)
23
+ - [Workflow Setup](#workflow-setup)
24
+ - [Node Parameters](#node-parameters)
25
+ - [Trigger Paths](#trigger-paths)
26
+ - [Consumer Guides](#consumer-guides)
27
+ - [WooCommerce](#woocommerce)
28
+ - [Shopify](#shopify)
29
+ - [Direct API / Postman](#direct-api--postman)
30
+ - [LLM / MCP Agents](#llm--mcp-agents)
31
+ - [Security Model](#security-model)
32
+ - [Error Codes](#error-codes)
33
+ - [Sandbox / Dry Run Mode](#sandbox--dry-run-mode)
34
+ - [Contact Resolution](#contact-resolution)
35
+ - [POP Payload Field Mapping](#pop-payload-field-mapping)
36
+ - [Known Limitations (v1)](#known-limitations-v1)
37
+ - [Development](#development)
38
+ - [License](#license)
39
+
40
+ ---
41
+
42
+ ## How It Works
43
+
44
+ The node sits inside a standard n8n webhook workflow. POP API calls the webhook with a signed payload; the node verifies the request, maps the POP fields to Zoho's data model, resolves or creates the customer contact, and creates the invoice or credit note via Zoho's REST API.
45
+
46
+ ```
47
+ POP API ──► Webhook node ──► POP → Zoho ──► Respond to Webhook node
48
+ ```
49
+
50
+ POP API handles the entry point (license validation, sandbox/live mode, quota billing). This node owns: signature verification, payload mapping, contact resolution, tax lookup, and Zoho API calls.
51
+
52
+ The connector can be triggered in two ways (see [Trigger Paths](#trigger-paths)):
53
+ - **Via webhook panel** — used by WooCommerce, Shopify, and any platform that calls a document-generation route
54
+ - **Via direct `/connector/zoho` route** — used for Postman, direct API calls, and LLM/MCP agents
55
+
56
+ ---
57
+
58
+ ## Prerequisites
59
+
60
+ ### POP API
61
+ - An active [POP API](https://popapi.io) account with a valid license key
62
+ - The **n8n Connector** enabled in your POP API account settings
63
+ - The n8n webhook URL registered in your POP API webhook panel
64
+
65
+ ### Zoho
66
+ - A **Zoho Invoice** or **Zoho Books** account
67
+ - An OAuth2 application created in the [Zoho API Console](https://api-console.zoho.com) (or the console for your region)
68
+ - **Organization ID** — found in Zoho → Settings → Organization Profile
69
+ - **Tax rates** configured in Zoho → Settings → Taxes, one entry per percentage used in your POP payloads (e.g. 22%, 10%, 4%, 0%)
70
+
71
+ ### n8n
72
+ - n8n self-hosted (v2.x or later) or n8n Cloud Enterprise (custom nodes required)
73
+ - Node.js v18+
74
+
75
+ ---
76
+
77
+ ## Installation
78
+
79
+ ### From npm (recommended)
80
+
81
+ In your n8n instance go to **Settings → Community Nodes → Install** and enter:
82
+
83
+ ```
84
+ n8n-nodes-pop-zoho
85
+ ```
86
+
87
+ <!-- SCREENSHOT: n8n Settings > Community Nodes install dialog with the package name entered -->
88
+
89
+ ### Manual / local development
90
+
91
+ ```bash
92
+ # Clone the repository
93
+ git clone https://github.com/getpopapi/n8n-nodes-pop-zoho.git
94
+ cd n8n-nodes-pop-zoho
95
+
96
+ # Install dependencies and build
97
+ npm install
98
+ npm run build
99
+
100
+ # Symlink into n8n custom nodes directory
101
+ ln -s $(pwd)/dist/nodes/PopZohoInvoice/PopZohoInvoice.node.js ~/.n8n/custom/PopZohoInvoice.node.js
102
+ ln -s $(pwd)/dist/credentials/ZohoInvoiceOAuth2Api.credentials.js ~/.n8n/custom/ZohoInvoiceOAuth2Api.credentials.js
103
+ ln -s $(pwd)/dist/nodes/PopZohoInvoice/pop-zoho-invoice.svg ~/.n8n/custom/pop-zoho-invoice.svg
104
+
105
+ # Restart n8n
106
+ n8n start
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Credentials Setup
112
+
113
+ ### 1. Create the Zoho OAuth2 Application
114
+
115
+ 1. Go to the [Zoho API Console](https://api-console.zoho.com) for your region
116
+ 2. Create a new **Server-based Application**
117
+ 3. Set the redirect URI to your n8n OAuth callback URL:
118
+ `https://<your-n8n-instance>/rest/oauth2-credential/callback`
119
+ 4. Note the **Client ID** and **Client Secret**
120
+
121
+ ![](.github/images/api-console.png)
122
+
123
+ ### 2. Configure the Credential in n8n
124
+
125
+ In n8n go to **Credentials → Add Credential → Zoho OAuth2 (Invoice / Books)** and fill in:
126
+
127
+ | Field | Description |
128
+ |-------|-------------|
129
+ | **Product** | `Zoho Invoice` or `Zoho Books` — use Books for UAE e-invoicing |
130
+ | **Region** | Your Zoho data center (EU, US, IN, AU, JP, CA) |
131
+ | **Organization ID** | From Zoho → Settings → Organization Profile |
132
+ | **Authorization URL** | Auto-filled based on region — e.g. `https://accounts.zoho.eu/oauth/v2/auth` |
133
+ | **Access Token URL** | Same host as Authorization URL, path `/oauth/v2/token` |
134
+ | **Client ID** | From your Zoho API Console application |
135
+ | **Client Secret** | From your Zoho API Console application |
136
+ | **Scope** | Pre-filled with all required scopes — do not change unless you know what you are doing |
137
+
138
+ Click **Connect** to complete the OAuth2 authorization flow. Zoho will ask you to confirm the requested permissions.
139
+
140
+ ![](.github/images/credential-zoho.png)
141
+
142
+ > **Note on scopes:** The pre-filled scope string includes all permissions required for full connector operation: invoices, credit notes, contacts, and settings. If you previously connected with a narrower scope, revoke the authorization from your Zoho account → **Connected Apps**, then reconnect to grant the updated permissions.
143
+
144
+ ---
145
+
146
+ ## Workflow Setup
147
+
148
+ ### Recommended Workflow
149
+
150
+ ```
151
+ [Webhook node]
152
+
153
+ [POP → Zoho node]
154
+
155
+ [Respond to Webhook node]
156
+ ```
157
+
158
+ 1. **Webhook node** — set **Authentication** to `None` (security is handled by the POP → Zoho node via HMAC + RSA JWT). Set **Respond** to `Using 'Respond to Webhook' Node`. Set the path to `webhook/zoho`.
159
+ 2. **POP → Zoho node** — configure as described below.
160
+ 3. **Respond to Webhook node** — leave default settings; it will forward the node output as the HTTP response.
161
+
162
+ ![](.github/images/workflow-canvas.png)
163
+
164
+ ### Register the Webhook URL in POP API
165
+
166
+ Copy the **Production URL** from the Webhook node (e.g. `https://your-n8n.example.com/webhook/zoho`) and paste it into your POP API account → **Webhooks** panel. Note the **webhook ID** (e.g. `popWh_xxxxxxxx`) — you will need it in every API call.
167
+
168
+ > **Webhook path naming:** Use `webhook/zoho` as the path in the n8n Webhook node. The node supports both Zoho Invoice and Zoho Books, so a product-neutral path avoids confusion and matches the connector slug used by POP API.
169
+
170
+ ![](.github/images/webhook-panel.png)
171
+
172
+ ---
173
+
174
+ ## Node Parameters
175
+
176
+ ### Security
177
+
178
+ | Parameter | Description |
179
+ |-----------|-------------|
180
+ | **POP API URL** | Base URL of your POP API instance (default: `https://popapi.io`). Used to auto-fetch the RSA public key. |
181
+ | **POP API License Key** | Your **default** POP API license key. Used to verify the HMAC signature on every incoming request. |
182
+ | **POP API RSA Public Key** | Click the **Refresh** button to auto-fetch the public key from your POP API instance. Required for RSA JWT verification. |
183
+
184
+ ![](.github/images/security.png)
185
+
186
+ > **Which license key to use:** Always use the **default API key** — found in your POP API account at **popapi.io → Account → API → API Key (default)**. Do not use platform-specific keys (e.g. the key generated for a Shopify or WooCommerce integration). POP API always signs connector requests with the default key regardless of which platform triggered the call, so the node must verify against that same key.
187
+
188
+ > **How to get the RSA Public Key:** Set the **POP API URL** field, then click the refresh icon (🔄) next to **POP API RSA Public Key**. The key is fetched automatically from your POP API instance. You only need to do this once per credential setup (or after a key rotation on the POP API server).
189
+
190
+ ### Zoho Behavior
191
+
192
+ | Parameter | Default | Description |
193
+ |-----------|---------|-------------|
194
+ | **Contact Match Strategy** | `VAT → Email → Name` | How to find an existing Zoho contact for the invoice customer. |
195
+ | **Create Contact If Missing** | `true` | Automatically create the contact in Zoho when no match is found. |
196
+ | **Invoice Status** | `Draft` | Status to set on the created invoice in Zoho (`Draft` or `Sent`). |
197
+ | **Send Email to Customer** | `false` | Trigger Zoho's built-in invoice email to the customer after creation. |
198
+ | **Place of Supply** | `Not specified` | Required for UAE e-invoicing — select the emirate. Leave empty for standard invoices. |
199
+ | **Deferred Payment Terms (days)** | `30` | Days granted for payment when the payload uses deferred payment terms (`TP02`). |
200
+
201
+ ### Other
202
+
203
+ | Parameter | Default | Description |
204
+ |-----------|---------|-------------|
205
+ | **Dry Run** | `false` | Validate the payload and return the Zoho body that would be sent, without making any Zoho API call. Useful for testing field mapping. |
206
+
207
+ ---
208
+
209
+ ## Trigger Paths
210
+
211
+ The connector can be triggered in two distinct ways depending on the client.
212
+
213
+ ### Path A — Via Webhook Panel (WooCommerce, Shopify, any platform using document-generation routes)
214
+
215
+ The client calls one of POP API's document-generation routes (`create-xml`, `create-ubl`, `create-ksef-xml`) with a `webhook` integration. POP API looks up the webhook entry, sees that `connector_type: zoho` is set, and automatically routes the call through the connector.
216
+
217
+ **Setup steps:**
218
+ 1. Register the n8n webhook URL in the POP API **Webhooks** panel
219
+ 2. Set the **Connector** field on that webhook entry to **n8n Connector — Zoho**
220
+ 3. In your API call, pass `integration.use: "webhook"` and `integration.id: "<webhook-id>"`
221
+
222
+ **Request example:**
223
+ ```json
224
+ {
225
+ "integration": {
226
+ "use": "webhook",
227
+ "id": "<webhook-id>"
228
+ },
229
+ "environment": "sandbox",
230
+ "data": { ... }
231
+ }
232
+ ```
233
+
234
+ This path is used automatically by the WooCommerce and Shopify integrations — no extra configuration is needed on the client side once the webhook panel is set up.
235
+
236
+ ---
237
+
238
+ ### Path B — Direct `/connector/zoho` Route (Postman, API, LLM/MCP)
239
+
240
+ The client calls POP API's dedicated connector endpoint directly. The webhook entry still needs to exist (to store the n8n URL), but the routing is explicit.
241
+
242
+ **Request:**
243
+ ```
244
+ POST https://popapi.io/wp-json/api/v2/connector/zoho
245
+
246
+ Headers:
247
+ Content-Type: application/json
248
+ X-Api-Key: <your-default-license-key>
249
+ ```
250
+
251
+ **Body:**
252
+ ```json
253
+ {
254
+ "integration": {
255
+ "use": "n8n-zoho",
256
+ "id": "<webhook-id>"
257
+ },
258
+ "environment": "sandbox",
259
+ "data": {
260
+ "invoice_body": {
261
+ "general_data": {
262
+ "doc_type": "TD01",
263
+ "date": "2026-03-04",
264
+ "invoice_number": "2026/0001",
265
+ "currency": "EUR"
266
+ }
267
+ },
268
+ "transferee_client": {
269
+ "personal_data": {
270
+ "company_name": "Cliente Test SRL",
271
+ "email": "cliente@example.com",
272
+ "tax_id_vat": {
273
+ "id_code": "12345678901",
274
+ "country_id": "IT"
275
+ },
276
+ "tax_id_code": "RSSMRA80A01H501U"
277
+ },
278
+ "place": {
279
+ "address": "Via Milano 10",
280
+ "city": "Milano",
281
+ "zip_code": "20100",
282
+ "province_id": "MI",
283
+ "country_id": "IT"
284
+ }
285
+ },
286
+ "order_items": [
287
+ {
288
+ "description": "Servizio consulenza",
289
+ "quantity": "1.00",
290
+ "unit_price": "100.00",
291
+ "rate": "22.00",
292
+ "unit": "N.",
293
+ "item_code": { "type": "SKU", "value": "PROD-001" },
294
+ "discount_percent": "",
295
+ "discount_amount": ""
296
+ }
297
+ ],
298
+ "payment_data": {
299
+ "terms_payment": "TP02",
300
+ "payment_details": "MP05",
301
+ "payment_amount": "122.00"
302
+ },
303
+ "purchase_order_data": { "id": "#1001", "date": "2026-03-04" },
304
+ "connected_invoice_data": { "id": "", "date": "" }
305
+ }
306
+ }
307
+ ```
308
+
309
+ | Field | Values | Description |
310
+ |-------|--------|-------------|
311
+ | `integration.id` | your webhook ID | The webhook ID registered in your POP API account |
312
+ | `environment` | `sandbox` / `live` | `sandbox` runs a dry-run — no Zoho calls, no quota deducted |
313
+ | `data.invoice_body.general_data.doc_type` | `TD01` / `TD04` | Document type: `TD01` = invoice, `TD04` = credit note |
314
+
315
+ For `TD04` credit notes, include `connected_invoice_data.id` with the Zoho invoice ID of the original document being reversed (returned as `zoho_invoice_id` in the invoice creation response).
316
+
317
+ ### Successful Response
318
+
319
+ ```json
320
+ {
321
+ "success": true,
322
+ "environment": "live",
323
+ "connector": "zoho",
324
+ "message": "Invoice created successfully.",
325
+ "status_code": 200,
326
+ "data": {
327
+ "success": true,
328
+ "zoho_product": "books",
329
+ "zoho_document_type": "invoice",
330
+ "zoho_invoice_id": "994269000000070002",
331
+ "zoho_invoice_number": "INV-000001",
332
+ "zoho_status": "draft",
333
+ "zoho_total": 1220.00,
334
+ "contact_id": "994269000000062001",
335
+ "contact_created": false
336
+ }
337
+ }
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Consumer Guides
343
+
344
+ ### WooCommerce
345
+
346
+ 1. In your POP API account → **Webhooks** panel, add the n8n webhook URL and set the **Connector** field to **n8n Connector — Zoho**.
347
+ 2. In your WooCommerce numeration settings, select the webhook entry you just created.
348
+ 3. When an order is processed, POP API automatically calls the connector — no changes needed in WooCommerce.
349
+ 4. The connector is triggered both during **AJAX** (immediate) and **scheduled cron** flows.
350
+
351
+ > The license key configured in the WooCommerce plugin is the standard POP API key. POP API always signs with the **default** key, so the n8n credential must use that same key.
352
+
353
+ ---
354
+
355
+ ### Shopify
356
+
357
+ 1. Register the n8n webhook URL in the POP API webhook panel and set **Connector** to **n8n Connector — Zoho**.
358
+ 2. In the Shopify app, select that webhook entry in the numeration settings for the relevant document type.
359
+ 3. When the Shopify app generates a document, POP API routes the call through the connector automatically.
360
+
361
+ > The Shopify integration uses a platform-specific license key internally, but POP API always signs connector requests with the **default** key. Make sure the `POP API License Key` credential in n8n is the **default API key** from popapi.io → Account → API → API Key (default), not the Shopify-specific key — using the wrong key causes an `auth_error` (HMAC mismatch).
362
+
363
+ ---
364
+
365
+ ### Direct API / Postman
366
+
367
+ Use [Path B](#path-b--direct-connectorzohо-route-postman-api-llmmcp) above. Send the request to `/wp-json/api/v2/connector/zoho` with the `X-Api-Key` header set to your default license key.
368
+
369
+ A Postman collection with ready-to-use examples (sandbox and live) is available in the `examples/` directory of this repository.
370
+
371
+ ---
372
+
373
+ ### LLM / MCP Agents
374
+
375
+ The connector is fully usable from LLM agents and MCP-based tools. Use [Path B](#path-b--direct-connectorzohо-route-postman-api-llmmcp) (direct `/connector/zoho` route).
376
+
377
+ Minimal call sequence for an agent:
378
+ 1. Retrieve the webhook ID from the user's POP API account (or have it pre-configured)
379
+ 2. Build the `data` object from the order information available to the agent
380
+ 3. POST to `/wp-json/api/v2/connector/zoho` with `environment: "sandbox"` first to verify mapping
381
+ 4. On success, repeat with `environment: "live"` to create the real document in Zoho
382
+
383
+ > Use `environment: "sandbox"` for all test or uncertain calls — no quota is consumed and no document is created in Zoho.
384
+
385
+ ---
386
+
387
+ ## Security Model
388
+
389
+ Every request from POP API to the n8n connector is protected by two independent verification layers:
390
+
391
+ ### 1. HMAC-SHA256 (license key binding)
392
+ POP API computes `HMAC-SHA256(body + timestamp, default_license_key)` and sends it in the `X-POP-Signature` header. The node verifies the signature using the **default** license key configured in the node parameters. This proves the payload belongs to the customer who owns that account.
393
+
394
+ POP API always uses the **default** license key for signing, regardless of which platform (WooCommerce, Shopify, direct API, LLM) triggered the request. Configuring a platform-specific key in the node will cause all requests to fail with `auth_error`.
395
+
396
+ ### 2. RSA JWT (POP API origin proof)
397
+ POP API signs a short-lived JWT (5 min TTL) with its RSA-2048 private key and injects it as `_pop_jwt` in the payload. The node verifies the JWT using the public key fetched from your POP API instance. This proves the request originated from POP API servers — a stolen license key alone is not enough to forge requests.
398
+
399
+ Both checks are **always mandatory** and cannot be disabled.
400
+
401
+ ### Replay Protection
402
+ Requests older than 5 minutes are automatically rejected based on the `X-POP-Timestamp` header.
403
+
404
+ ---
405
+
406
+ ## Error Codes
407
+
408
+ | Code | Cause | Fix |
409
+ |------|-------|-----|
410
+ | `auth_error` | Invalid HMAC signature, expired JWT, or missing security headers | Make sure the `POP API License Key` in the node is the **default** key from popapi.io → Account → API → API Key (default). Platform-specific keys (Shopify, WooCommerce) will not match. Also ensure requests always go through POP API — never call n8n directly. |
411
+ | `config_error` | License Key or RSA Public Key not set in node parameters | Configure the node parameters and refresh the public key |
412
+ | `validation_error` | Required POP field missing in the payload | Check that `data.invoice_body.general_data.*`, `data.transferee_client.*`, and `data.order_items` are all present |
413
+ | `unsupported_doc_type` | `doc_type` is not `TD01` or `TD04` | Only `TD01` (invoice) and `TD04` (credit note) are supported in v1 |
414
+ | `tax_not_found` | A tax rate in `order_items[].rate` is not configured in Zoho | Go to Zoho → Settings → Taxes and add the missing rate |
415
+ | `contact_not_found` | Customer not found in Zoho and **Create Contact If Missing** is disabled | Enable the option or create the contact manually in Zoho |
416
+ | `contact_ambiguous` | Multiple Zoho contacts match the same VAT, email, or name | Resolve the duplicate contacts in Zoho before retrying |
417
+ | `contact_creation_failed` | Zoho refused the contact creation request | Check the Zoho error details in the n8n execution log |
418
+ | `zoho_api_error` | Generic Zoho API error | Check the Zoho error details in the n8n execution log |
419
+
420
+ ---
421
+
422
+ ## Sandbox / Dry Run Mode
423
+
424
+ Two independent mechanisms to avoid creating real documents during testing:
425
+
426
+ - **`environment: "sandbox"`** in the POP API request body — POP API injects `_pop_dry_run: true` into the payload (HMAC-signed, cannot be spoofed). No quota is deducted. Use this for end-to-end testing from any consumer (WooCommerce, Shopify, Postman, LLM).
427
+ - **Dry Run** parameter on the node — skips Zoho API calls regardless of the `_pop_dry_run` flag. Useful for local development without Zoho credentials.
428
+
429
+ When dry run is active, the response includes `dry_run: true` and the full `zoho_body` that would have been sent to Zoho, allowing you to verify the field mapping before going live.
430
+
431
+ ---
432
+
433
+ ## Contact Resolution
434
+
435
+ The node searches for an existing Zoho contact before creating a new one, using the strategy configured in **Contact Match Strategy**:
436
+
437
+ 1. **VAT → Email → Name** *(default)*: searches by VAT/TRN code first, then email address, then company/person name
438
+ 2. **Email → Name**: skips the VAT lookup (useful if VAT codes are not stored in Zoho)
439
+
440
+ If multiple contacts match, the node returns a `contact_ambiguous` error — resolve the duplicate in Zoho and retry.
441
+
442
+ If no contact is found and **Create Contact If Missing** is enabled, the node creates a new contact using the `transferee_client` data from the payload, including billing address and VAT treatment for UAE/GCC accounts.
443
+
444
+ ---
445
+
446
+ ## POP Payload Field Mapping
447
+
448
+ | POP field | Zoho field |
449
+ |-----------|-----------|
450
+ | `invoice_body.general_data.date` | `date` |
451
+ | `invoice_body.general_data.currency` | `currency_code` |
452
+ | `invoice_body.general_data.invoice_number` | (informational — Zoho auto-assigns) |
453
+ | `transferee_client.*` | contact lookup / creation |
454
+ | `order_items[].description` | `line_items[].name` |
455
+ | `order_items[].unit_price` | `line_items[].rate` |
456
+ | `order_items[].quantity` | `line_items[].quantity` |
457
+ | `order_items[].rate` | tax lookup by percentage → `line_items[].tax_id` |
458
+ | `order_items[].discount_percent` | `line_items[].discount` (percentage only) |
459
+ | `purchase_order_data.id` | `reference_number` |
460
+ | `payment_data.terms_payment` | `payment_terms` (days) |
461
+ | `connected_invoice_data.id` | `reference_invoice_id` (TD04 only) |
462
+
463
+ ---
464
+
465
+ ## Known Limitations (v1)
466
+
467
+ - **Line discount precision** — Zoho line items only accept percentage discounts. When the POP payload provides an absolute `discount_amount`, the node converts it to a percentage using `discount_amount / unit_price × 100`. Rounding may produce a small discrepancy (< 0.01%) on the Zoho total.
468
+ - **Contact updates** — when an existing contact is found in Zoho, its fields are not updated even if they differ from the POP payload. Update the contact manually in Zoho if needed.
469
+
470
+ ---
471
+
472
+ ## Development
473
+
474
+ ```bash
475
+ npm install # install dependencies
476
+ npx tsc --noEmit # type check only
477
+ npm run build # compile to dist/
478
+ npm run dev # watch mode
479
+ ```
480
+
481
+ ---
482
+
483
+ ## License
484
+
485
+ MIT — see [LICENSE](LICENSE.md)
486
+
487
+ ---
488
+
489
+ ## Related
490
+
491
+ - [n8n-nodes-pop](https://github.com/getpopapi/n8n-nodes-pop) — POP API node for n8n (FatturaPA, Peppol, VAT validation)
492
+ - [POP API Documentation](https://www.postman.com/getpopapi/pop-api-public-docs)
@@ -0,0 +1,12 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare const ZOHO_URLS: Record<string, Record<string, string>>;
3
+ export declare const ZOHO_ORG_HEADER: Record<string, string>;
4
+ export declare class ZohoInvoiceOAuth2Api implements ICredentialType {
5
+ name: string;
6
+ displayName: string;
7
+ icon: "file:pop-zoho-invoice.svg";
8
+ documentationUrl: string;
9
+ extends: string[];
10
+ properties: INodeProperties[];
11
+ }
12
+ //# sourceMappingURL=ZohoInvoiceOAuth2Api.credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZohoInvoiceOAuth2Api.credentials.d.ts","sourceRoot":"","sources":["../../credentials/ZohoInvoiceOAuth2Api.credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAErE,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiB5D,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGlD,CAAC;AAgCF,qBAAa,oBAAqB,YAAW,eAAe;IAC3D,IAAI,SAA0B;IAC9B,WAAW,SAA6B;IACxC,IAAI,EAAG,2BAA2B,CAAU;IAC5C,gBAAgB,SAA0C;IAC1D,OAAO,WAAiB;IAExB,UAAU,EAAE,eAAe,EAAE,CA0F3B;CACF"}
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZohoInvoiceOAuth2Api = exports.ZOHO_ORG_HEADER = exports.ZOHO_URLS = void 0;
4
+ exports.ZOHO_URLS = {
5
+ invoice: {
6
+ com: 'https://www.zohoapis.com/invoice/v3',
7
+ eu: 'https://www.zohoapis.eu/invoice/v3',
8
+ in: 'https://www.zohoapis.in/invoice/v3',
9
+ 'com.au': 'https://www.zohoapis.com.au/invoice/v3',
10
+ jp: 'https://www.zohoapis.jp/invoice/v3',
11
+ ca: 'https://www.zohoapis.ca/invoice/v3',
12
+ },
13
+ books: {
14
+ com: 'https://www.zohoapis.com/books/v3',
15
+ eu: 'https://www.zohoapis.eu/books/v3',
16
+ in: 'https://www.zohoapis.in/books/v3',
17
+ 'com.au': 'https://www.zohoapis.com.au/books/v3',
18
+ jp: 'https://www.zohoapis.jp/books/v3',
19
+ ca: 'https://www.zohoapis.ca/books/v3',
20
+ },
21
+ };
22
+ exports.ZOHO_ORG_HEADER = {
23
+ invoice: 'X-com-zoho-invoice-organizationid',
24
+ books: 'X-com-zoho-books-organizationid',
25
+ };
26
+ const SCOPE_STRING_INVOICE = 'ZohoInvoice.invoices.CREATE,ZohoInvoice.invoices.READ,ZohoInvoice.creditnotes.CREATE,ZohoInvoice.creditnotes.READ,ZohoInvoice.contacts.CREATE,ZohoInvoice.contacts.READ,ZohoInvoice.settings.READ';
27
+ const SCOPE_STRING_BOOKS = 'ZohoBooks.invoices.CREATE,ZohoBooks.invoices.READ,ZohoBooks.creditnotes.CREATE,ZohoBooks.creditnotes.READ,ZohoBooks.contacts.CREATE,ZohoBooks.contacts.READ,ZohoBooks.settings.READ';
28
+ const REGION_OAUTH = {
29
+ com: {
30
+ auth: 'https://accounts.zoho.com/oauth/v2/auth',
31
+ token: 'https://accounts.zoho.com/oauth/v2/token',
32
+ },
33
+ eu: {
34
+ auth: 'https://accounts.zoho.eu/oauth/v2/auth',
35
+ token: 'https://accounts.zoho.eu/oauth/v2/token',
36
+ },
37
+ in: {
38
+ auth: 'https://accounts.zoho.in/oauth/v2/auth',
39
+ token: 'https://accounts.zoho.in/oauth/v2/token',
40
+ },
41
+ 'com.au': {
42
+ auth: 'https://accounts.zoho.com.au/oauth/v2/auth',
43
+ token: 'https://accounts.zoho.com.au/oauth/v2/token',
44
+ },
45
+ jp: {
46
+ auth: 'https://accounts.zoho.jp/oauth/v2/auth',
47
+ token: 'https://accounts.zoho.jp/oauth/v2/token',
48
+ },
49
+ ca: {
50
+ auth: 'https://accounts.zohocloud.ca/oauth/v2/auth',
51
+ token: 'https://accounts.zohocloud.ca/oauth/v2/token',
52
+ },
53
+ };
54
+ class ZohoInvoiceOAuth2Api {
55
+ constructor() {
56
+ this.name = 'zohoInvoiceOAuth2Api';
57
+ this.displayName = 'Zoho Invoice OAuth2 API';
58
+ this.icon = 'file:pop-zoho-invoice.svg';
59
+ this.documentationUrl = 'https://www.zoho.com/invoice/api/v3/';
60
+ this.extends = ['oAuth2Api'];
61
+ this.properties = [
62
+ {
63
+ displayName: 'Product',
64
+ name: 'product',
65
+ type: 'options',
66
+ options: [
67
+ { name: 'Zoho Invoice', value: 'invoice' },
68
+ { name: 'Zoho Books', value: 'books' },
69
+ ],
70
+ default: 'invoice',
71
+ description: 'Zoho product to connect to. Use Zoho Books for UAE e-invoicing.',
72
+ },
73
+ {
74
+ displayName: 'Region',
75
+ name: 'region',
76
+ type: 'options',
77
+ options: [
78
+ { name: 'Europe', value: 'eu' },
79
+ { name: 'United States', value: 'com' },
80
+ { name: 'India', value: 'in' },
81
+ { name: 'Australia', value: 'com.au' },
82
+ { name: 'Japan', value: 'jp' },
83
+ { name: 'Canada', value: 'ca' },
84
+ ],
85
+ default: 'eu',
86
+ description: 'Data center region of your Zoho account',
87
+ },
88
+ {
89
+ displayName: 'Organization ID',
90
+ name: 'organizationId',
91
+ type: 'string',
92
+ default: '',
93
+ required: true,
94
+ description: 'Found in Zoho → Settings → Organization Profile',
95
+ },
96
+ {
97
+ displayName: 'Grant Type',
98
+ name: 'grantType',
99
+ type: 'hidden',
100
+ default: 'authorizationCode',
101
+ },
102
+ {
103
+ displayName: 'Authorization URL',
104
+ name: 'authUrl',
105
+ type: 'string',
106
+ default: REGION_OAUTH.eu.auth,
107
+ required: true,
108
+ description: 'Set based on your region — EU: accounts.zoho.eu | US: accounts.zoho.com | IN: accounts.zoho.in | AU: accounts.zoho.com.au | JP: accounts.zoho.jp | CA: accounts.zohocloud.ca',
109
+ },
110
+ {
111
+ displayName: 'Access Token URL',
112
+ name: 'accessTokenUrl',
113
+ type: 'string',
114
+ default: REGION_OAUTH.eu.token,
115
+ required: true,
116
+ description: 'Same host as Authorization URL, path: /oauth/v2/token',
117
+ },
118
+ {
119
+ displayName: 'Scope',
120
+ name: 'scope',
121
+ type: 'string',
122
+ default: SCOPE_STRING_INVOICE,
123
+ displayOptions: { show: { product: ['invoice'] } },
124
+ description: 'OAuth2 scopes requested from Zoho Invoice. The pre-filled value includes all scopes required for full connector operation (invoices, credit notes, contacts, settings). Edit only if you know what you are doing.',
125
+ },
126
+ {
127
+ displayName: 'Scope',
128
+ name: 'scope',
129
+ type: 'string',
130
+ default: SCOPE_STRING_BOOKS,
131
+ displayOptions: { show: { product: ['books'] } },
132
+ description: 'OAuth2 scopes requested from Zoho Books. The pre-filled value includes all scopes required for full connector operation (invoices, credit notes, contacts, settings). Edit only if you know what you are doing.',
133
+ },
134
+ {
135
+ displayName: 'Auth URI Query Parameters',
136
+ name: 'authQueryParameters',
137
+ type: 'string',
138
+ default: 'access_type=offline',
139
+ },
140
+ {
141
+ displayName: 'Authentication',
142
+ name: 'authentication',
143
+ type: 'hidden',
144
+ default: 'body',
145
+ },
146
+ ];
147
+ }
148
+ }
149
+ exports.ZohoInvoiceOAuth2Api = ZohoInvoiceOAuth2Api;
150
+ //# sourceMappingURL=ZohoInvoiceOAuth2Api.credentials.js.map