@getpeppr/sdk 0.2.0 → 1.0.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/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # @getpeppr/sdk
2
2
 
3
- Send Peppol e-invoices in 5 lines of code.
3
+ Send Peppol e-invoices in under 20 lines of code.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@getpeppr/sdk.svg)](https://www.npmjs.com/package/@getpeppr/sdk)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-first-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Node](https://img.shields.io/badge/Node-%3E%3D18-green.svg)](https://nodejs.org/)
8
9
 
9
10
  ```typescript
10
11
  import { Peppol } from "@getpeppr/sdk";
@@ -13,13 +14,20 @@ const peppol = new Peppol({ apiKey: "sk_live_..." });
13
14
 
14
15
  const result = await peppol.invoices.send({
15
16
  number: "INV-2026-001",
16
- to: { name: "Acme Corp", peppolId: "0208:BE9876543210", country: "BE" },
17
+ to: {
18
+ name: "Acme Corp",
19
+ peppolId: "0208:BE9876543210",
20
+ street: "123 Business Ave",
21
+ city: "Brussels",
22
+ postalCode: "1000",
23
+ country: "BE",
24
+ },
17
25
  lines: [
18
26
  { description: "Consulting services", quantity: 10, unitPrice: 150, vatRate: 21 },
19
27
  ],
20
28
  });
21
29
 
22
- console.log(`Invoice sent! Status: ${result.status}`);
30
+ console.log(`Invoice sent! ID: ${result.id}`);
23
31
  ```
24
32
 
25
33
  ## Installation
@@ -30,402 +38,163 @@ npm install @getpeppr/sdk
30
38
 
31
39
  ## Features
32
40
 
33
- - **Stripe-like developer experience** -- JSON in, Peppol out
41
+ - **Stripe-like DX** -- JSON in, Peppol out
34
42
  - **Full Peppol BIS 3.0 compliance** -- UBL 2.1 XML generation handled for you
35
- - **Client-side + server-side validation** -- catch errors before they reach the network
36
- - **Create, send, list, acknowledge** invoices
37
- - **Credit notes** with automatic UBL CreditNote generation
38
- - **PDF and XML export** -- download invoices in any format
43
+ - **All invoice types** -- standard (380), credit note (381), debit (383), prepayment (386), self-billed (389)
44
+ - **Client-side + server-side validation** -- XSD and Schematron rules
39
45
  - **Peppol Directory lookup** -- verify participants before sending
40
- - **Events API** with async pagination
41
- - **Batch send** with concurrency control
42
46
  - **Webhook signature verification** -- HMAC-SHA256 with replay protection
43
- - **Built-in retry logic** with exponential backoff and rate limit handling
44
- - **TypeScript-first** with full type safety
45
- - **Zero dependencies**
46
-
47
- ## Configuration
48
-
49
- ```typescript
50
- import { Peppol } from "@getpeppr/sdk";
51
-
52
- const peppol = new Peppol({
53
- apiKey: "sk_live_...", // Required. Starts with sk_sandbox_ or sk_live_
54
- baseUrl: "https://...", // Custom gateway URL (default: https://api.getpeppr.dev/v1)
55
- timeout: 30000, // Request timeout in ms (default: 30s)
56
- retry: {
57
- maxRetries: 3, // Max retry attempts (default: 3)
58
- initialDelayMs: 500, // Initial retry delay (default: 500ms)
59
- maxDelayMs: 30000, // Max retry delay (default: 30s)
60
- },
61
- });
62
- ```
63
-
64
- ## Sending Invoices
65
-
66
- ### Basic invoice
67
-
68
- ```typescript
69
- const result = await peppol.invoices.send({
70
- number: "INV-2026-001",
71
- to: {
72
- name: "Acme Corp",
73
- peppolId: "0208:BE9876543210",
74
- country: "BE",
75
- },
76
- lines: [
77
- { description: "Consulting services", quantity: 10, unitPrice: 150, vatRate: 21 },
78
- ],
79
- });
47
+ - **Batch send** with concurrency control
48
+ - **Built-in retry** with exponential backoff and rate limit handling
49
+ - **Async pagination** -- iterate over all invoices, events, contacts
50
+ - **TypeScript-first**, zero dependencies
80
51
 
81
- console.log(result.id); // "inv-abc123"
82
- console.log(result.status); // "new" | "sent" | ...
83
- ```
52
+ ## Quick Examples
84
53
 
85
- ### Full invoice with all options
54
+ ### Create draft + send later
86
55
 
87
56
  ```typescript
88
- const result = await peppol.invoices.send({
89
- // Required
90
- number: "INV-2026-001",
57
+ const draft = await peppol.invoices.create({
58
+ number: "INV-2026-002",
91
59
  to: {
92
60
  name: "Acme Corp",
93
61
  peppolId: "0208:BE9876543210",
94
- vatNumber: "BE9876543210",
95
62
  street: "123 Business Ave",
96
63
  city: "Brussels",
97
64
  postalCode: "1000",
98
65
  country: "BE",
99
- companyId: "0876543210",
100
- contactName: "Jane Doe",
101
- phone: "+32 2 123 4567",
102
- email: "invoices@acme.be",
103
- },
104
- lines: [
105
- {
106
- description: "Consulting services",
107
- quantity: 10,
108
- unit: "hours", // Human-readable: "hours", "days", "kg", or UN/ECE: "HUR", "DAY"
109
- unitPrice: 150,
110
- vatRate: 21,
111
- vatCategory: "S", // S=standard, Z=zero-rated, E=exempt, AE=reverse charge
112
- itemId: "SRV-001",
113
- allowances: [{ reason: "Loyalty discount", amount: 50 }],
114
- charges: [{ reason: "Express delivery", amount: 25 }],
115
- },
116
- ],
117
-
118
- // Optional
119
- date: "2026-01-15", // Invoice date (default: today)
120
- dueDate: "2026-02-15", // Due date
121
- currency: "EUR", // ISO 4217 (default: EUR)
122
- taxCurrency: "GBP", // Tax reporting currency if different
123
- taxCurrencyRate: 0.86, // Exchange rate to tax currency
124
- buyerReference: "PO-2026-042", // Buyer reference (BT-10)
125
- orderReference: "ORD-789", // Purchase order number
126
- note: "Thank you for your business",
127
- paymentTerms: "Net 30 days",
128
- paymentMeans: 30, // 30=credit transfer, 58=SEPA
129
- paymentIban: "BE68539007547034",
130
- paymentBic: "BBRUBEBB",
131
- paymentReference: "+++090/9337/55493+++",
132
-
133
- payeeParty: { // When payment recipient differs from seller
134
- name: "Factoring Co",
135
- peppolId: "0208:BE1111111111",
136
- country: "BE",
137
66
  },
138
-
139
- invoicePeriod: { // Billing period (common for SaaS)
140
- startDate: "2026-01-01",
141
- endDate: "2026-01-31",
142
- },
143
-
144
- delivery: { // Delivery information
145
- date: "2026-01-20",
146
- locationId: "LOC-001",
147
- address: { street: "456 Warehouse Rd", city: "Antwerp", postalCode: "2000", country: "BE" },
148
- },
149
-
150
- allowances: [{ reason: "Volume discount", amount: 100, vatRate: 21 }],
151
- charges: [{ reason: "Handling fee", amount: 25, vatRate: 21 }],
152
-
153
- attachments: [
154
- {
155
- id: "ATT-001",
156
- description: "Timesheet",
157
- filename: "timesheet.pdf",
158
- mimeType: "application/pdf",
159
- content: "base64-encoded-content...", // Or use `url` for external reference
160
- },
161
- ],
162
- });
163
- ```
164
-
165
- ### Create draft + send later
166
-
167
- ```typescript
168
- const draft = await peppol.invoices.create({
169
- number: "INV-2026-002",
170
- to: { name: "Acme Corp", peppolId: "0208:BE9876543210", country: "BE" },
171
67
  lines: [{ description: "Consulting", quantity: 5, unitPrice: 200, vatRate: 21 }],
172
68
  });
173
69
 
174
- console.log(draft.id); // "inv-xyz456"
175
- console.log(draft.status); // "new"
176
-
177
70
  // Send when ready
178
71
  await peppol.invoices.sendById(draft.id);
179
72
  ```
180
73
 
181
- ### Credit notes
74
+ ### Credit note
182
75
 
183
76
  ```typescript
184
- const result = await peppol.invoices.send({
77
+ const creditNote = await peppol.invoices.send({
185
78
  number: "CN-2026-001",
186
79
  isCreditNote: true,
187
- invoiceReference: "INV-2026-001", // Required: original invoice number
188
- to: { name: "Acme Corp", peppolId: "0208:BE9876543210", country: "BE" },
80
+ invoiceReference: "INV-2026-001",
81
+ to: {
82
+ name: "Acme Corp",
83
+ peppolId: "0208:BE9876543210",
84
+ street: "123 Business Ave",
85
+ city: "Brussels",
86
+ postalCode: "1000",
87
+ country: "BE",
88
+ },
189
89
  lines: [
190
90
  { description: "Refund for consulting services", quantity: 2, unitPrice: 150, vatRate: 21 },
191
91
  ],
192
92
  });
193
93
  ```
194
94
 
195
- ## Listing Invoices
196
-
197
- ### Single page
95
+ ### Webhook verification
198
96
 
199
97
  ```typescript
200
- const page = await peppol.invoices.list({
201
- limit: 25,
202
- offset: 0,
203
- type: "issued", // "issued" | "received"
204
- includeLines: true, // Include line item details
205
- });
98
+ import { webhooks } from "@getpeppr/sdk";
206
99
 
207
- console.log(page.data); // InvoiceSummary[]
208
- console.log(page.meta); // { totalCount, offset, limit, hasMore }
209
- ```
210
-
211
- ### Auto-paginate all invoices
212
-
213
- ```typescript
214
- for await (const invoice of peppol.invoices.listAll({ type: "issued" })) {
215
- console.log(invoice.id, invoice.number, invoice.status);
216
- }
217
- ```
218
-
219
- ## Invoice Status
220
-
221
- ### Get current status
222
-
223
- ```typescript
224
- const result = await peppol.invoices.getStatus("inv-abc123");
225
- console.log(result.status); // "sent", "accepted", etc.
226
- ```
100
+ app.post("/webhooks/peppol", async (req, res) => {
101
+ try {
102
+ const event = await webhooks.constructEvent(
103
+ req.body, // Raw body string (NOT parsed JSON)
104
+ req.headers["getpeppr-signature"], // Getpeppr-Signature header
105
+ "whsec_your_webhook_secret", // Your webhook secret
106
+ );
227
107
 
228
- ### Wait for a specific status
108
+ switch (event.type) {
109
+ case "invoice.accepted":
110
+ console.log("Invoice accepted:", event.data);
111
+ break;
112
+ case "invoice.received":
113
+ console.log("New invoice received:", event.data);
114
+ break;
115
+ }
229
116
 
230
- ```typescript
231
- const result = await peppol.invoices.waitFor("inv-abc123", "accepted", {
232
- timeout: 60000, // Max wait time in ms (default: 120s)
233
- interval: 5000, // Polling interval in ms (default: 5s)
117
+ res.sendStatus(200);
118
+ } catch (err) {
119
+ res.status(400).send("Webhook verification failed");
120
+ }
234
121
  });
235
122
  ```
236
123
 
237
- You can also wait for multiple statuses:
238
-
239
- ```typescript
240
- const result = await peppol.invoices.waitFor("inv-abc123", ["accepted", "paid"]);
241
- ```
242
-
243
- Terminal failure statuses (`error`, `refused`, `invalid`) throw immediately without waiting for timeout.
244
-
245
- ### Document status lifecycle
246
-
247
- ```
248
- new --> sent --> downloaded --> registered --> accepted --> paid --> closed
249
- \-> refused
250
- \-> error
251
- ```
252
-
253
- ## Export PDF / XML
254
-
255
- ```typescript
256
- import { writeFileSync } from "fs";
257
-
258
- // Export as PDF
259
- const pdf = await peppol.invoices.getAs("inv-abc123", "pdf");
260
- writeFileSync("invoice.pdf", Buffer.from(pdf));
261
-
262
- // Export as UBL XML
263
- const xml = await peppol.invoices.getAs("inv-abc123", "xml.ubl.invoice.bis3");
264
- writeFileSync("invoice.xml", Buffer.from(xml));
265
-
266
- // Available formats: "pdf" | "xml.ubl.invoice.bis3" | "xml.facturae.3.2" | "original"
267
- ```
268
-
269
- ## Acknowledge Received Invoice
124
+ ### Directory lookup
270
125
 
271
126
  ```typescript
272
- const result = await peppol.invoices.acknowledge("inv-abc123");
273
- console.log(result.status); // "accepted"
127
+ const entry = await peppol.directory.lookup("0208:BE0123456789");
128
+ console.log(entry.name); // "My Company NV"
129
+ console.log(entry.country); // "BE"
130
+ console.log(entry.capabilities); // ["invoice", "credit_note"]
274
131
  ```
275
132
 
276
- ## Batch Send
133
+ ### Batch send
277
134
 
278
135
  ```typescript
279
- const invoices = [invoice1, invoice2, invoice3, /* ... */];
136
+ const invoices = [invoice1, invoice2, invoice3];
280
137
 
281
138
  const result = await peppol.invoices.sendBatch(invoices, {
282
- concurrency: 5, // Max parallel requests (default: 5)
283
- stopOnError: false, // Continue on failure (default: false)
139
+ concurrency: 5,
140
+ stopOnError: false,
284
141
  });
285
142
 
286
143
  console.log(`${result.succeeded.length} sent, ${result.failed.length} failed`);
287
-
288
- for (const failure of result.failed) {
289
- console.error(`Invoice #${failure.index} (${failure.input.number}): ${failure.error.message}`);
290
- }
291
144
  ```
292
145
 
293
- ## Peppol Directory
294
-
295
- ```typescript
296
- const entry = await peppol.directory.lookup("0208:BE0123456789");
297
-
298
- console.log(entry.name); // "My Company NV"
299
- console.log(entry.country); // "BE"
300
- console.log(entry.capabilities); // ["invoice", "credit_note"]
301
- ```
302
-
303
- ## Validation
304
-
305
- ### Client-side validation (instant, offline)
146
+ ### Client-side validation
306
147
 
307
148
  ```typescript
308
149
  const result = peppol.validate({
309
150
  number: "INV-001",
310
- to: { name: "Acme", peppolId: "0208:BE9876543210", country: "BE" },
151
+ to: {
152
+ name: "Acme",
153
+ peppolId: "0208:BE9876543210",
154
+ street: "123 Business Ave",
155
+ city: "Brussels",
156
+ postalCode: "1000",
157
+ country: "BE",
158
+ },
311
159
  lines: [{ description: "Item", quantity: 1, unitPrice: 100, vatRate: 21 }],
312
160
  });
313
161
 
314
162
  if (!result.valid) {
315
163
  for (const err of result.errors) {
316
164
  console.log(`${err.field}: ${err.message}`);
317
- if (err.suggestion) console.log(` Fix: ${err.suggestion}`);
318
165
  }
319
166
  }
320
-
321
- for (const warn of result.warnings) {
322
- console.log(`Warning: ${warn.field}: ${warn.message}`);
323
- }
324
167
  ```
325
168
 
326
- ### Server-side validation (XSD + Schematron)
169
+ ### Export PDF / XML
327
170
 
328
171
  ```typescript
329
- const result = await peppol.invoices.validateServer({
330
- number: "INV-001",
331
- to: { name: "Acme", peppolId: "0208:BE9876543210", country: "BE" },
332
- lines: [{ description: "Item", quantity: 1, unitPrice: 100, vatRate: 21 }],
333
- });
334
-
335
- console.log(result.valid); // true/false
336
- console.log(result.xsd.errors); // XSD schema errors
337
- console.log(result.schematron.errors); // Peppol business rule errors
338
- console.log(result.schematron.warnings); // Non-blocking warnings
339
- ```
340
-
341
- ## Events
342
-
343
- ### List events
344
-
345
- ```typescript
346
- const result = await peppol.events.list({
347
- limit: 10,
348
- invoiceId: "inv-abc123", // Filter by invoice
349
- dateFrom: "2026-01-01", // Filter by date range
350
- dateTo: "2026-01-31",
351
- });
352
-
353
- for (const event of result.data) {
354
- console.log(event.name, event.text, event.createdAt);
355
- }
356
- ```
172
+ import { writeFileSync } from "fs";
357
173
 
358
- ### Auto-paginate all events
174
+ const pdf = await peppol.invoices.getAs("inv-abc123", "pdf");
175
+ writeFileSync("invoice.pdf", Buffer.from(pdf));
359
176
 
360
- ```typescript
361
- for await (const event of peppol.events.listAll({ invoiceId: "inv-abc123" })) {
362
- console.log(event.name, event.createdAt);
363
- }
177
+ const xml = await peppol.invoices.getAs("inv-abc123", "xml.ubl.invoice.bis3");
178
+ writeFileSync("invoice.xml", Buffer.from(xml));
364
179
  ```
365
180
 
366
- ## Webhooks
367
-
368
- Verify and parse incoming webhook payloads with HMAC-SHA256 signature verification and replay protection.
181
+ ## Configuration
369
182
 
370
183
  ```typescript
371
- import { Peppol, webhooks } from "@getpeppr/sdk";
372
-
373
- // Express example
374
- app.post("/webhooks/peppol", async (req, res) => {
375
- try {
376
- const event = await webhooks.constructEvent(
377
- req.body, // Raw body string (NOT parsed JSON)
378
- req.headers["x-b2brouter-signature"], // Signature header
379
- "whsec_your_webhook_secret", // Your webhook secret
380
- );
381
-
382
- switch (event.type) {
383
- case "invoice.accepted":
384
- console.log("Invoice accepted:", event.data);
385
- break;
386
- case "invoice.refused":
387
- console.log("Invoice refused:", event.data);
388
- break;
389
- case "invoice.received":
390
- console.log("New invoice received:", event.data);
391
- break;
392
- }
393
-
394
- res.sendStatus(200);
395
- } catch (err) {
396
- console.error("Webhook verification failed:", err.message);
397
- res.status(400).send("Webhook verification failed");
398
- }
399
- });
400
- ```
401
-
402
- Supported event types:
403
-
404
- | Event | Description |
405
- |-------|-------------|
406
- | `invoice.sent` | Invoice sent to Peppol network |
407
- | `invoice.accepted` | Recipient accepted the invoice |
408
- | `invoice.refused` | Recipient refused the invoice |
409
- | `invoice.error` | Delivery error |
410
- | `invoice.registered` | Invoice registered by recipient |
411
- | `invoice.paid` | Invoice marked as paid |
412
- | `invoice.closed` | Invoice closed |
413
- | `invoice.received` | New invoice received |
414
- | `creditnote.sent` | Credit note sent |
415
- | `creditnote.received` | Credit note received |
416
-
417
- ## UBL XML Generation
418
-
419
- Generate UBL 2.1 XML without sending (useful for debugging or manual submission):
184
+ import { Peppol } from "@getpeppr/sdk";
420
185
 
421
- ```typescript
422
- const xml = peppol.toXml({
423
- number: "INV-001",
424
- to: { name: "Acme", peppolId: "0208:BE9876543210", country: "BE" },
425
- lines: [{ description: "Item", quantity: 1, unitPrice: 100, vatRate: 21 }],
186
+ const peppol = new Peppol({
187
+ apiKey: "sk_live_...", // Required. Starts with sk_sandbox_ or sk_live_
188
+ baseUrl: "https://...", // Default: https://api.getpeppr.dev/v1
189
+ timeout: 30000, // Request timeout in ms (default: 30s)
190
+ retry: {
191
+ maxRetries: 3, // Max retry attempts (default: 3)
192
+ initialDelayMs: 500, // Initial retry delay (default: 500ms)
193
+ maxDelayMs: 30000, // Max retry delay (default: 30s)
194
+ },
195
+ onRequest: (entry) => {}, // Optional request hook
196
+ onResponse: (entry) => {}, // Optional response hook
426
197
  });
427
-
428
- console.log(xml); // Full UBL 2.1 Invoice XML
429
198
  ```
430
199
 
431
200
  ## Error Handling
@@ -440,7 +209,7 @@ try {
440
209
  } catch (err) {
441
210
  if (err instanceof PeppolValidationError) {
442
211
  // Client-side validation failed
443
- console.log(err.validation.errors); // ValidationError[]
212
+ console.log(err.validation.errors); // ValidationError[]
444
213
  console.log(err.validation.warnings); // ValidationWarning[]
445
214
  } else if (err instanceof PeppolApiError) {
446
215
  // API returned an error
@@ -454,9 +223,29 @@ try {
454
223
  }
455
224
  ```
456
225
 
457
- ### Automatic retries
226
+ Transient errors (429, 500, 502, 503, 504) are automatically retried with exponential backoff and jitter. Rate limit responses (429) respect the `Retry-After` header.
227
+
228
+ ## Webhook Events
229
+
230
+ | Event | Description |
231
+ |-------|-------------|
232
+ | `invoice.sent` | Invoice sent to the Peppol network |
233
+ | `invoice.accepted` | Recipient accepted the invoice |
234
+ | `invoice.refused` | Recipient refused the invoice |
235
+ | `invoice.error` | Delivery error |
236
+ | `invoice.registered` | Invoice registered by recipient |
237
+ | `invoice.paid` | Invoice marked as paid |
238
+ | `invoice.closed` | Invoice closed |
239
+ | `invoice.received` | New invoice received from the network |
240
+ | `creditnote.sent` | Credit note sent |
241
+ | `creditnote.received` | Credit note received |
242
+ | `test.ping` | Test event for endpoint verification |
243
+
244
+ ## Links
458
245
 
459
- The SDK automatically retries on transient errors (429, 500, 502, 503, 504) with exponential backoff and jitter. Rate limit responses (429) respect the `Retry-After` header.
246
+ - [Documentation](https://getpeppr.dev/docs)
247
+ - [Website](https://getpeppr.dev)
248
+ - [GitHub](https://github.com/zerolooplabs/getpeppr)
460
249
 
461
250
  ## License
462
251