@facturino/node 1.1.0 → 2.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 +295 -26
- package/dist/cjs/client.d.ts +1 -1
- package/dist/cjs/client.js +2 -2
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/resources/companies.d.ts +1 -1
- package/dist/cjs/resources/invoices.d.ts +31 -1
- package/dist/cjs/resources/invoices.js +51 -0
- package/dist/cjs/resources/taxDecisions.d.ts +31 -0
- package/dist/cjs/resources/taxDecisions.js +63 -0
- package/dist/cjs/types.d.ts +547 -20
- package/dist/esm/client.d.ts +1 -1
- package/dist/esm/client.js +2 -2
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/resources/companies.d.ts +1 -1
- package/dist/esm/resources/companies.d.ts.map +1 -1
- package/dist/esm/resources/invoices.d.ts +31 -1
- package/dist/esm/resources/invoices.d.ts.map +1 -1
- package/dist/esm/resources/invoices.js +51 -0
- package/dist/esm/resources/invoices.js.map +1 -1
- package/dist/esm/resources/taxDecisions.d.ts +32 -0
- package/dist/esm/resources/taxDecisions.d.ts.map +1 -0
- package/dist/esm/resources/taxDecisions.js +60 -0
- package/dist/esm/resources/taxDecisions.js.map +1 -0
- package/dist/esm/types.d.ts +547 -20
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,35 +13,107 @@ npm install @facturino/node
|
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
|
+
The recommended path is decision-first: identity → final tax decision →
|
|
17
|
+
create the decision-backed draft immediately → your chosen collection flow.
|
|
18
|
+
Facturino imposes no payment service provider and no payment method: an
|
|
19
|
+
immediate capture, a bank transfer, a direct debit or payment on agreed terms
|
|
20
|
+
all fit the same contract.
|
|
21
|
+
|
|
16
22
|
```typescript
|
|
17
23
|
import Facturino from '@facturino/node'
|
|
18
24
|
|
|
19
25
|
const facturino = new Facturino('fac_test_xxx')
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
// 1. Decide before the final amount is presented, the invoice is issued,
|
|
28
|
+
// or collection starts.
|
|
29
|
+
const decision = await facturino.taxDecisions.create({
|
|
30
|
+
taxSource: 'facturino', // or 'integration' to supply your own VAT
|
|
31
|
+
customerId: 'cus_8f2k4m9n',
|
|
32
|
+
effectiveAt: '2026-09-15',
|
|
33
|
+
currency: 'eur',
|
|
34
|
+
priceMode: 'tax_exclusive',
|
|
28
35
|
lines: [{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
reference: 'abo-pro',
|
|
37
|
+
description: 'Abonnement Pro',
|
|
38
|
+
category: 'electronically_supplied_services',
|
|
39
|
+
rateCategory: 'standard',
|
|
40
|
+
unitAmount: 2900, // integer cents
|
|
41
|
+
quantity: '1', // decimal STRING, never a float
|
|
35
42
|
}],
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
}, { idempotencyKey: `order-${orderId}` })
|
|
44
|
+
|
|
45
|
+
// 2. Act only on a final decision. `pending_verification` does not mean
|
|
46
|
+
// "nothing to charge": totals and amountToCharge are null, not 0.
|
|
47
|
+
if (decision.status !== 'final') {
|
|
48
|
+
return askForMissingEvidence(decision.issues)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Create the decision-backed draft immediately: no VAT is restated,
|
|
52
|
+
// the amounts are the decision's.
|
|
53
|
+
const invoice = await facturino.invoices.create({
|
|
54
|
+
customerId: decision.customerId,
|
|
55
|
+
taxDecisionId: decision.id,
|
|
56
|
+
decisionLines: [{ taxLineRef: 'abo-pro', unit: 'month' }],
|
|
57
|
+
buyer: buyerSnapshot,
|
|
58
|
+
dates: { issued: '2026-09-15', due: '2026-10-15' },
|
|
59
|
+
payment: paymentTerms,
|
|
38
60
|
})
|
|
39
61
|
|
|
40
|
-
|
|
62
|
+
// 4. Choose your collection flow — see the two variants below.
|
|
63
|
+
```
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
**Immediate collection** — capture the decided amount, verify, then finalize:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// Capture exactly amountToCharge through your payment provider, payment
|
|
69
|
+
// processor, bank transfer or external collection flow. Carry `decision.id`
|
|
70
|
+
// in the provider metadata, order reference or custom reference. The
|
|
71
|
+
// settlement keeps its OWN financial reference (charge id, transfer
|
|
72
|
+
// wording…): the two identifiers are different things and must stay distinct.
|
|
73
|
+
const settlement = await yourCollectionProcess.capture({
|
|
74
|
+
amount: decision.amountToCharge!, // never a locally computed total
|
|
75
|
+
currency: decision.currency,
|
|
76
|
+
metadata: { taxDecisionId: decision.id },
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Re-read the decision by its own id and verify what was actually captured.
|
|
80
|
+
const source = await facturino.taxDecisions.retrieve(decision.id)
|
|
81
|
+
if (settlement.amount !== source.amountToCharge) throw new Error('amount mismatch')
|
|
82
|
+
if (settlement.currency !== source.currency) throw new Error('currency mismatch')
|
|
83
|
+
|
|
84
|
+
await facturino.invoices.finalize(invoice.id)
|
|
85
|
+
|
|
86
|
+
// Record the REAL payment — its real date, method and the settlement's
|
|
87
|
+
// financial reference (never the decision id).
|
|
88
|
+
await facturino.payments.create(invoice.id, {
|
|
89
|
+
amount: settlement.amount,
|
|
90
|
+
// transfer, card, check, cash, direct_debit, sepa, paypal or other
|
|
91
|
+
method: settlement.method,
|
|
92
|
+
reference: settlement.reference,
|
|
93
|
+
paidAt: settlement.paidAt,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Send to the platform only on the channel the FROZEN decision states.
|
|
97
|
+
if (source.invoiceChannel === 'einvoicing') {
|
|
98
|
+
await facturino.invoices.send(invoice.id)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Payment on terms** — finalize and deliver now, collect later:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
await facturino.invoices.finalize(invoice.id)
|
|
106
|
+
if (decision.invoiceChannel === 'einvoicing') {
|
|
107
|
+
await facturino.invoices.send(invoice.id)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// …once the transfer arrives, record the REAL collection date.
|
|
111
|
+
await facturino.payments.create(invoice.id, {
|
|
112
|
+
amount: decision.amountToCharge!,
|
|
113
|
+
method: 'transfer',
|
|
114
|
+
reference: 'VIR-2026-000871',
|
|
115
|
+
paidAt: '2026-10-12',
|
|
116
|
+
})
|
|
45
117
|
```
|
|
46
118
|
|
|
47
119
|
## Configuration
|
|
@@ -50,10 +122,156 @@ const finalized = await facturino.invoices.finalize(invoice.id)
|
|
|
50
122
|
const facturino = new Facturino('fac_test_xxx', {
|
|
51
123
|
maxRetries: 3, // retries on 429/5xx
|
|
52
124
|
timeout: 30000, // ms
|
|
53
|
-
apiVersion: '2026-
|
|
125
|
+
apiVersion: '2026-09-01',
|
|
54
126
|
})
|
|
55
127
|
```
|
|
56
128
|
|
|
129
|
+
## Tax decisions
|
|
130
|
+
|
|
131
|
+
The full walkthrough lives in [Usage](#usage). A decision is immutable: it
|
|
132
|
+
fixes the VAT, the exact `amountToCharge` and the reporting obligations of one
|
|
133
|
+
commercial operation, then never changes. Only a `final` decision carries
|
|
134
|
+
amounts, and the amount always comes from the decision — never from a locally
|
|
135
|
+
computed total.
|
|
136
|
+
|
|
137
|
+
### Optional: carrying the decision id through a PSP
|
|
138
|
+
|
|
139
|
+
These are examples, not requirements. If you collect through a PSP, keep the
|
|
140
|
+
decision id on the payment so step 4 can verify what was actually captured.
|
|
141
|
+
|
|
142
|
+
Stripe — any field that survives the round trip works; `metadata` is the usual one:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const intent = await stripe.paymentIntents.create({
|
|
146
|
+
amount: decision.amountToCharge!,
|
|
147
|
+
currency: decision.currency,
|
|
148
|
+
metadata: { facturino_tax_decision_id: decision.id },
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
PayPal has no `metadata`; carry the decision id in `custom_id`, and convert the
|
|
153
|
+
cents to decimal units for the order amount:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
custom_id: decision.id,
|
|
157
|
+
amount: {
|
|
158
|
+
currency_code: decision.currency.toUpperCase(),
|
|
159
|
+
value: (decision.amountToCharge! / 100).toFixed(2),
|
|
160
|
+
},
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### What a decision states
|
|
164
|
+
|
|
165
|
+
| Field | Meaning |
|
|
166
|
+
|---|---|
|
|
167
|
+
| `status` | `final`, `pending_verification` or `unsupported`. Only `final` carries amounts. |
|
|
168
|
+
| `amountToCharge` | Exact amount to debit, integer cents. `null` unless `final`. |
|
|
169
|
+
| `totals` | `totalHT` / `totalVAT` / `totalTTC`, integer cents. `null` unless `final`. |
|
|
170
|
+
| `invoiceChannel` | `einvoicing` or `none` — whether the invoice travels the network. |
|
|
171
|
+
| `transactionReporting` | `ereporting`, `none` or `outside_scope`. |
|
|
172
|
+
| `paymentReporting` | `fr212`, `ereporting` or `none`. |
|
|
173
|
+
| `foreignTaxReviewRequired` | A foreign tax may apply; review it outside Facturino. |
|
|
174
|
+
| `vies` | VIES status only (`valid`, `invalid`, `unavailable`, `invalid_format`). |
|
|
175
|
+
| `issues` | What is missing, when the decision is not final. |
|
|
176
|
+
| `obligationReasons` | Why each axis carries the obligation it does. |
|
|
177
|
+
| `expiresAt` / `expired` | Past this instant the decision no longer opens a payment. |
|
|
178
|
+
|
|
179
|
+
`create()` requires an `Idempotency-Key` (255 characters at most; the SDK checks
|
|
180
|
+
it before sending). The API answers `201` on creation and `200` when the same
|
|
181
|
+
key already produced that decision — both return the decision, so your code
|
|
182
|
+
reads one shape either way. Reusing the same key with a different body answers
|
|
183
|
+
`409` and raises `ConflictError`.
|
|
184
|
+
|
|
185
|
+
Facturino decides **French VAT and the matching French obligations**. It does
|
|
186
|
+
not provide worldwide tax compliance: when a foreign tax may apply, the decision
|
|
187
|
+
says so through `foreignTaxReviewRequired`. An operation whose `invoiceChannel`
|
|
188
|
+
is `none` is not deposited on a certified platform — its obligation, if any,
|
|
189
|
+
goes through e-reporting.
|
|
190
|
+
|
|
191
|
+
### Missing evidence, then a retry
|
|
192
|
+
|
|
193
|
+
A decision that lacks a location or business-status proof comes back
|
|
194
|
+
`pending_verification`. Supply the evidence and retry the SAME operation:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const retried = await facturino.taxDecisions.create({
|
|
198
|
+
...sameOperation,
|
|
199
|
+
retryOfTaxDecisionId: pending.id,
|
|
200
|
+
locationEvidence: [{
|
|
201
|
+
kind: 'billing_address',
|
|
202
|
+
country: 'FR',
|
|
203
|
+
postalCode: '75002',
|
|
204
|
+
thirdParty: false,
|
|
205
|
+
source: 'declared',
|
|
206
|
+
collectedAt: '2026-09-15',
|
|
207
|
+
}],
|
|
208
|
+
}, { idempotencyKey: `order-${orderId}-retry-${pending.id}` })
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Send the territorial **signal**, never the raw one: a country and, where the
|
|
212
|
+
territory needs it, a postal code — not an IP address, a PSP payload or bank
|
|
213
|
+
account details. `reference` is a bounded opaque identifier such as a charge id.
|
|
214
|
+
|
|
215
|
+
## Three status axes
|
|
216
|
+
|
|
217
|
+
A document has three states that do not follow from one another. The historical
|
|
218
|
+
`status` field stays populated as their projection.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
invoice.documentStatus // draft | finalized | cancelled
|
|
222
|
+
invoice.transmissionStatus // not_applicable | pending | sending | deposited | transmitted | approved | rejected
|
|
223
|
+
invoice.transmissionDetail // available | received | suspended | refused | null
|
|
224
|
+
invoice.paymentStatus // unpaid | partially_paid | paid | partially_refunded | refunded
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Recording a payment never moves the transmission axis, and a refund does not
|
|
228
|
+
erase the collection that happened.
|
|
229
|
+
|
|
230
|
+
## Supplying your own VAT (`taxSource: 'integration'`)
|
|
231
|
+
|
|
232
|
+
If your own tax engine concludes the VAT, create the decision under the
|
|
233
|
+
`integration` source: the same commercial and territorial data, plus the VAT
|
|
234
|
+
per line (`vatRate`, `vatCode`, and `vatexCode` for exempt categories).
|
|
235
|
+
Facturino validates the coherence of the supplied values and refuses any
|
|
236
|
+
detectable contradiction (`integration_vat_incoherent`) — it never silently
|
|
237
|
+
corrects a rate. The amounts, the legal mentions and the three reporting axes
|
|
238
|
+
are still decided server-side, by the same engines. The invoice is then created
|
|
239
|
+
exactly like a facturino-sourced one, and carries `taxSource: 'integration'`.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const decision = await facturino.taxDecisions.create({
|
|
243
|
+
taxSource: 'integration',
|
|
244
|
+
customerId: 'cus_xxx',
|
|
245
|
+
effectiveAt: '2026-09-15',
|
|
246
|
+
currency: 'eur',
|
|
247
|
+
priceMode: 'tax_exclusive',
|
|
248
|
+
lines: [{
|
|
249
|
+
reference: 'consulting',
|
|
250
|
+
description: 'Consulting',
|
|
251
|
+
category: 'services',
|
|
252
|
+
unitAmount: 10000, // 100.00 EUR (integer cents)
|
|
253
|
+
quantity: '1', // decimal string
|
|
254
|
+
vatRate: 2000, // 20.00% — supplied, never corrected
|
|
255
|
+
vatCode: 'S',
|
|
256
|
+
}],
|
|
257
|
+
}, { idempotencyKey: `order-${orderId}` })
|
|
258
|
+
|
|
259
|
+
const invoice = await facturino.invoices.create({
|
|
260
|
+
customerId: 'cus_xxx',
|
|
261
|
+
taxDecisionId: decision.id,
|
|
262
|
+
decisionLines: [{ taxLineRef: 'consulting', unit: 'flat_rate' }],
|
|
263
|
+
buyer: buyerSnapshot,
|
|
264
|
+
dates: { issued: '2026-09-15', due: '2026-10-15' },
|
|
265
|
+
payment: { terms: 'Paiement à 30 jours', termsDays: 30, method: 'transfer', latePaymentRate: '10.00', collectionFee: '40.00' },
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const finalized = await facturino.invoices.finalize(invoice.id)
|
|
269
|
+
|
|
270
|
+
// One-shot: pass `autoFinalize: true` (and optionally
|
|
271
|
+
// `autoSend: { email: true }`) to finalize — and deliver by email — in a
|
|
272
|
+
// single call.
|
|
273
|
+
```
|
|
274
|
+
|
|
57
275
|
## Amounts
|
|
58
276
|
|
|
59
277
|
Monetary values are integers in **centimes** (10000 = 100.00 EUR).
|
|
@@ -74,8 +292,15 @@ const page = await facturino.invoices.list({ status: 'draft' })
|
|
|
74
292
|
## Resources
|
|
75
293
|
|
|
76
294
|
```typescript
|
|
295
|
+
// Tax decisions (immutable — no update, no delete)
|
|
296
|
+
facturino.taxDecisions.create(params, { idempotencyKey })
|
|
297
|
+
facturino.taxDecisions.retrieve('taxdec_xxx')
|
|
298
|
+
|
|
77
299
|
// Invoices
|
|
78
|
-
facturino.invoices.create(
|
|
300
|
+
facturino.invoices.create({ taxDecisionId, decisionLines, ... }) // always backed by a FINAL decision
|
|
301
|
+
// `deposits` and `schedule` are settled server-side against the decided
|
|
302
|
+
// amount; the decided total never changes. A create without a decision is
|
|
303
|
+
// rejected locally, before any HTTP call.
|
|
79
304
|
facturino.invoices.get('inv_xxx')
|
|
80
305
|
facturino.invoices.get('inv_xxx', { expand: ['customer', 'credit_notes'] })
|
|
81
306
|
facturino.invoices.update('inv_xxx', params)
|
|
@@ -100,19 +325,32 @@ facturino.customers.lookup({ siret: '73282932000074' })
|
|
|
100
325
|
// Products
|
|
101
326
|
facturino.products.list({ q: 'consult', category: 'services', active: true })
|
|
102
327
|
|
|
103
|
-
// Quotes
|
|
328
|
+
// Quotes — convert, decide, bind, finalize: ONE invoice throughout.
|
|
104
329
|
facturino.quotes.create(params)
|
|
105
330
|
facturino.quotes.send('quo_xxx')
|
|
106
331
|
facturino.quotes.accept('quo_xxx')
|
|
107
|
-
facturino.quotes.convert('quo_xxx') // -> draft invoice
|
|
108
332
|
facturino.quotes.clone('quo_xxx') // -> duplicated draft quote
|
|
109
333
|
|
|
334
|
+
// A converted quote yields a COMMERCIAL draft: it states the operation and no
|
|
335
|
+
// VAT (`taxSource: null`). Bind a final decision to that same invoice, then
|
|
336
|
+
// finalize it — never create a second one.
|
|
337
|
+
const { invoiceId } = await facturino.quotes.convert('quo_xxx')
|
|
338
|
+
const decision = await facturino.taxDecisions.create(decisionInput, { idempotencyKey })
|
|
339
|
+
await facturino.invoices.bindTaxDecision(invoiceId, {
|
|
340
|
+
taxDecisionId: decision.id,
|
|
341
|
+
decisionLines: [{ taxLineRef: 'l1', unit: 'unit' }],
|
|
342
|
+
})
|
|
343
|
+
await facturino.invoices.finalize(invoiceId)
|
|
344
|
+
|
|
110
345
|
// Credit Notes
|
|
111
|
-
facturino.creditNotes.create(
|
|
346
|
+
facturino.creditNotes.create({ relatedInvoiceId, creditNoteType, reasonCode, creditedLines })
|
|
347
|
+
// A credit note inherits the fiscal position of the invoice it corrects —
|
|
348
|
+
// source, snapshot and lines. It never restates any VAT.
|
|
112
349
|
facturino.creditNotes.finalize('crn_xxx')
|
|
113
350
|
|
|
114
351
|
// Recurring Invoices
|
|
115
|
-
facturino.recurringInvoices.create(
|
|
352
|
+
facturino.recurringInvoices.create({ ..., taxInputs }) // each occurrence decided on its own date,
|
|
353
|
+
// under the recurrence's single fiscal source
|
|
116
354
|
facturino.recurringInvoices.pause('rec_xxx')
|
|
117
355
|
facturino.recurringInvoices.resume('rec_xxx')
|
|
118
356
|
|
|
@@ -163,8 +401,39 @@ app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
|
|
|
163
401
|
|
|
164
402
|
## Idempotency
|
|
165
403
|
|
|
404
|
+
An `Idempotency-Key` protects the **replay of one request**. It is not a
|
|
405
|
+
deduplicator: the API never decides on its own that two requests "mean the same
|
|
406
|
+
thing".
|
|
407
|
+
|
|
408
|
+
- **Same key + same canonical body** — the first 2xx response is replayed
|
|
409
|
+
verbatim, and the operation is not executed a second time.
|
|
410
|
+
- **Same key + different body** — `409 idempotency_error`. A key belongs to a
|
|
411
|
+
request, not to an endpoint.
|
|
412
|
+
- **Different keys** — two distinct operations, even with byte-identical bodies.
|
|
413
|
+
Two requests describing the same operation are **not** deduplicated
|
|
414
|
+
automatically; the key, and only the key, declares that two sends are the same
|
|
415
|
+
attempt.
|
|
416
|
+
- **Canonical body** — JSON object keys are compared in a stable order, so
|
|
417
|
+
reordering them does not change the request. Changing a value, adding or
|
|
418
|
+
removing a field does. Array order is significant: two lines swapped are two
|
|
419
|
+
different documents.
|
|
420
|
+
- **Failure before execution** (validation, read-only field, sanitisation)
|
|
421
|
+
releases the key, so a corrected retry with the same key runs.
|
|
422
|
+
- **Business refusal during execution** is stored and replayed; the operation is
|
|
423
|
+
not re-executed.
|
|
424
|
+
- **Scope** — 24 hours, per API key. `POST /v1/tax-decisions` additionally
|
|
425
|
+
carries a durable business idempotency that never expires.
|
|
426
|
+
|
|
166
427
|
```typescript
|
|
167
|
-
|
|
428
|
+
// Same key + same body -> the first response, replayed.
|
|
429
|
+
await facturino.invoices.create(params, { idempotencyKey: 'order-4821' })
|
|
430
|
+
|
|
431
|
+
// Retry with new evidence is NOT idempotency. It takes a NEW decision on the
|
|
432
|
+
// same commercial operation: use a NEW key and link the previous decision.
|
|
433
|
+
await facturino.taxDecisions.create(
|
|
434
|
+
{ ...operation, retryOfTaxDecisionId: suspended.id },
|
|
435
|
+
{ idempotencyKey: 'order-4821-retry-1' },
|
|
436
|
+
)
|
|
168
437
|
```
|
|
169
438
|
|
|
170
439
|
## Errors
|
package/dist/cjs/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FacturinoConfig, RequestOptions } from './types.js';
|
|
2
|
-
export declare const VERSION = "
|
|
2
|
+
export declare const VERSION = "2.0.0";
|
|
3
3
|
/** HTTP client with retries, exponential backoff, and structured errors. */
|
|
4
4
|
export declare class HttpClient {
|
|
5
5
|
private readonly apiKey;
|
package/dist/cjs/client.js
CHANGED
|
@@ -5,11 +5,11 @@ const errors_js_1 = require("./errors.js");
|
|
|
5
5
|
const DEFAULT_BASE_URL = 'https://facturino.com/api';
|
|
6
6
|
const DEFAULT_MAX_RETRIES = 3;
|
|
7
7
|
const DEFAULT_TIMEOUT = 30000;
|
|
8
|
-
const DEFAULT_API_VERSION = '2026-
|
|
8
|
+
const DEFAULT_API_VERSION = '2026-09-01';
|
|
9
9
|
const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503]);
|
|
10
10
|
const INITIAL_RETRY_DELAY_MS = 500;
|
|
11
11
|
const MAX_RETRY_DELAY_MS = 30000;
|
|
12
|
-
exports.VERSION = '
|
|
12
|
+
exports.VERSION = '2.0.0';
|
|
13
13
|
/** HTTP client with retries, exponential backoff, and structured errors. */
|
|
14
14
|
class HttpClient {
|
|
15
15
|
constructor(apiKey, config = {}) {
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { ReceivedInvoices } from './resources/received-invoices.js';
|
|
|
17
17
|
import { Reporting } from './resources/reporting.js';
|
|
18
18
|
import { AccountResource } from './resources/account.js';
|
|
19
19
|
import { Billing } from './resources/billing.js';
|
|
20
|
+
import { TaxDecisions } from './resources/taxDecisions.js';
|
|
20
21
|
import { Usage } from './resources/usage.js';
|
|
21
22
|
import { Validate } from './resources/validate.js';
|
|
22
23
|
import { Reference } from './resources/reference.js';
|
|
@@ -42,6 +43,7 @@ declare class Facturino {
|
|
|
42
43
|
readonly jobs: Jobs;
|
|
43
44
|
readonly reference: Reference;
|
|
44
45
|
readonly sandbox: Sandbox;
|
|
46
|
+
readonly taxDecisions: TaxDecisions;
|
|
45
47
|
readonly usage: Usage;
|
|
46
48
|
readonly validate: Validate;
|
|
47
49
|
readonly webhooks: Webhooks;
|
|
@@ -53,7 +55,7 @@ declare class Facturino {
|
|
|
53
55
|
}
|
|
54
56
|
export default Facturino;
|
|
55
57
|
export { Facturino };
|
|
56
|
-
export type { FacturinoConfig, RequestOptions, PaginationParams, PaginatedResponse, ApiErrorBody, Address, Contact, ContactRole, LineItem, VatBreakdown, Totals, CustomerRef, CustomerSnapshot, LifecycleEntry, Currency, Unit, VatCode, VatexCode, PaymentMethod, InvoiceType, InvoiceStatus, InvoiceDates, InvoicePaymentTerms, InvoiceEinvoicing, InvoicePortal, InvoiceArchive, InvoiceFiles, Invoice, InvoiceExpandField, InvoiceExpanded, InvoiceRetrieveParams, InvoiceBuyerParam, InvoiceCreateDates, InvoiceCreateParams, InvoiceLineItemParam, InvoiceUpdateParams, InvoiceListParams, IncomingInvoiceCreateParams, InvoiceStatusResponse, InvoiceVerifyResponse, DocumentUrlResponse, JobResponse, PaymentLinkResponse, PaymentLinkCreateParams, PaymentTokenResponse, Payment, PaymentCreateParams, Customer, CustomerCreateParams, CustomerUpdateParams, CustomerListParams, CustomerLookupParams, SireneLookupResponse, SireneCompany, Product, PriceHistoryEntry, ProductCreateParams, ProductUpdateParams, ProductListParams, QuoteStatus, QuoteDates, QuoteSignature, Quote, QuoteCreateParams, QuoteUpdateParams, QuoteListParams, CreditNoteType, CreditNoteStatus, CreditNoteReasonCode, CreditNote, CreditNoteCreateParams, CreditNoteUpdateParams, CreditNoteListParams, WebhookEventType, WebhookEvent, EventListParams, WebhookEndpoint, WebhookEndpointCreateParams, WebhookEndpointUpdateParams, RecurringFrequency, RecurringInvoice, RecurringInvoiceCreateParams, RecurringInvoiceUpdateParams, RecurringInvoiceListParams, BankDetails, InvoiceSettings, CreditNoteSettings, CreditNoteNumberingMode, Company, CompanyUpdateParams, CgvResponse, FecExportParams, EReportingType, EReportingStatus, EReportingLine, EReportingLineResponse, EReporting, EReportingCreateParams, EReportingListParams, JobType, JobStatus, Job, SandboxResetResponse, SimulateStatusParams, SimulateStatusResponse, ReceivedInvoiceStatus, ReceivedInvoice, ReceivedInvoiceListParams, ReceivedInvoiceRefuseParams, ReceivedInvoiceRecordPaymentParams, ReceivedInvoiceActionResponse, VatReportParams, VatReportBreakdown, VatReport, RevenueReportParams, RevenueReportBreakdownItem, RevenueReport, AccountPlan, Account, BillingCycle, BillingSubscription, PlatformInvoice, UsageMeter, UsageSummary, ValidateParams, ValidateResponse, LegalForm, NafCode, LegalFormInput, NafCodeInput, PaProvider, HealthStatus, } from './types.js';
|
|
58
|
+
export type { FacturinoConfig, RequestOptions, PaginationParams, PaginatedResponse, ApiErrorBody, Address, Contact, ContactRole, LineItem, VatBreakdown, Totals, CustomerRef, CustomerSnapshot, LifecycleEntry, Currency, Unit, VatCode, VatexCode, PaymentMethod, InvoiceType, InvoiceStatus, InvoiceDates, InvoicePaymentTerms, InvoiceEinvoicing, InvoicePortal, InvoiceArchive, InvoiceFiles, Invoice, InvoiceExpandField, InvoiceExpanded, InvoiceRetrieveParams, InvoiceBuyerParam, InvoiceCreateDates, InvoiceCreateParams, InvoiceBindTaxDecisionParams, CommercialDraft, CommercialDraftLine, InvoiceLineItemParam, InvoiceUpdateParams, InvoiceListParams, IncomingInvoiceCreateParams, InvoiceStatusResponse, InvoiceVerifyResponse, DocumentUrlResponse, JobResponse, PaymentLinkResponse, PaymentLinkCreateParams, PaymentTokenResponse, Payment, PaymentCreateParams, Customer, CustomerCreateParams, CustomerUpdateParams, CustomerListParams, CustomerLookupParams, SireneLookupResponse, SireneCompany, Product, PriceHistoryEntry, ProductCreateParams, ProductUpdateParams, ProductListParams, QuoteStatus, QuoteDates, QuoteSignature, Quote, QuoteCreateParams, QuoteUpdateParams, QuoteListParams, CreditNoteType, CreditNoteStatus, CreditNoteReasonCode, CreditNote, CreditNoteCreateParams, CreditNoteUpdateParams, CreditNoteListParams, WebhookEventType, WebhookEvent, EventListParams, WebhookEndpoint, WebhookEndpointCreateParams, WebhookEndpointUpdateParams, RecurringFrequency, RecurringInvoice, RecurringInvoiceCreateParams, RecurringInvoiceUpdateParams, RecurringInvoiceListParams, BankDetails, InvoiceSettings, CreditNoteSettings, CreditNoteNumberingMode, Company, CompanyUpdateParams, CgvResponse, FecExportParams, EReportingType, EReportingStatus, EReportingLine, EReportingLineResponse, EReporting, EReportingCreateParams, EReportingListParams, JobType, JobStatus, Job, SandboxResetResponse, SimulateStatusParams, SimulateStatusResponse, ReceivedInvoiceStatus, ReceivedInvoice, ReceivedInvoiceListParams, ReceivedInvoiceRefuseParams, ReceivedInvoiceRecordPaymentParams, ReceivedInvoiceActionResponse, VatReportParams, VatReportBreakdown, VatReport, RevenueReportParams, RevenueReportBreakdownItem, RevenueReport, AccountPlan, Account, BillingCycle, BillingSubscription, PlatformInvoice, UsageMeter, UsageSummary, ValidateParams, ValidateResponse, PriceMode, SupplyCategory, PrimarySupplyCategory, RateCategory, GoodsMovement, TaxDecisionStatus, TaxDecisionDiscount, TaxDecisionLineParam, TaxDecisionCreateParams, TaxDecision, TaxDecisionLine, TaxDecisionCustomer, TaxDecisionIssue, TaxDecisionObligationReason, TaxDecisionVatBreakdownEntry, LocationEvidenceKind, LocationEvidenceParam, LocationEvidenceResult, NonEuBusinessEvidenceParam, NonEuBusinessEvidenceResult, EvidenceSource, ViesResult, InvoiceChannel, TransactionReporting, PaymentReporting, TaxSource, TaxSnapshot, DocumentStatus, TransmissionStatus, TransmissionDetail, PaymentStatus, DecisionBackedLineParam, InvoiceCreateBaseParams, CreditedLineParam, RecurringTaxLineParam, RecurringIntegrationTaxLineParam, RecurringTaxInputsParam, FacturinoTaxDecisionCreateParams, IntegrationTaxDecisionCreateParams, IntegrationTaxDecisionLineParam, LegalForm, NafCode, LegalFormInput, NafCodeInput, PaProvider, HealthStatus, } from './types.js';
|
|
57
59
|
export { FacturinoError, ApiError, InvalidRequestError, ValidationError, AuthenticationError, PermissionError, NotFoundError, ConflictError, RateLimitError, PlanLimitError, ApiInternalError, ConnectionError, } from './errors.js';
|
|
58
60
|
export { Webhooks } from './webhooks.js';
|
|
59
61
|
export { AutoPaginatingList } from './pagination.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const received_invoices_js_1 = require("./resources/received-invoices.js");
|
|
|
21
21
|
const reporting_js_1 = require("./resources/reporting.js");
|
|
22
22
|
const account_js_1 = require("./resources/account.js");
|
|
23
23
|
const billing_js_1 = require("./resources/billing.js");
|
|
24
|
+
const taxDecisions_js_1 = require("./resources/taxDecisions.js");
|
|
24
25
|
const usage_js_1 = require("./resources/usage.js");
|
|
25
26
|
const validate_js_1 = require("./resources/validate.js");
|
|
26
27
|
const reference_js_1 = require("./resources/reference.js");
|
|
@@ -47,6 +48,7 @@ class Facturino {
|
|
|
47
48
|
this.jobs = new jobs_js_1.Jobs(client);
|
|
48
49
|
this.reference = new reference_js_1.Reference(client);
|
|
49
50
|
this.sandbox = new sandbox_js_1.Sandbox(client);
|
|
51
|
+
this.taxDecisions = new taxDecisions_js_1.TaxDecisions(client);
|
|
50
52
|
this.usage = new usage_js_1.Usage(client);
|
|
51
53
|
this.validate = new validate_js_1.Validate(client);
|
|
52
54
|
this.webhooks = new webhooks_js_1.Webhooks();
|
|
@@ -11,7 +11,7 @@ export interface CompanyCreateParams {
|
|
|
11
11
|
tvaIntracom?: string;
|
|
12
12
|
rcs?: string;
|
|
13
13
|
capitalSocial?: string;
|
|
14
|
-
vatRegime?: 'normal' | 'franchise' | 'simplified' | 'debit';
|
|
14
|
+
vatRegime?: 'normal' | 'normal_quarterly' | 'franchise' | 'simplified' | 'debit';
|
|
15
15
|
email?: string;
|
|
16
16
|
phone?: string;
|
|
17
17
|
website?: string;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
2
|
import { AutoPaginatingList } from '../pagination.js';
|
|
3
3
|
import { Payments } from './payments.js';
|
|
4
|
-
import type { Invoice, InvoiceCreateParams, InvoiceUpdateParams, InvoiceListParams, InvoiceRetrieveParams, InvoiceStatusResponse, InvoiceVerifyResponse, DocumentUrlResponse, JobResponse, PaymentLinkResponse, PaymentLinkCreateParams, PaymentTokenResponse, PaginatedResponse, LifecycleEntry, IncomingInvoiceCreateParams, ReceivedInvoice, RequestOptions } from '../types.js';
|
|
4
|
+
import type { Invoice, InvoiceCreateParams, InvoiceBindTaxDecisionParams, InvoiceUpdateParams, InvoiceListParams, InvoiceRetrieveParams, InvoiceStatusResponse, InvoiceVerifyResponse, DocumentUrlResponse, JobResponse, PaymentLinkResponse, PaymentLinkCreateParams, PaymentTokenResponse, PaginatedResponse, LifecycleEntry, IncomingInvoiceCreateParams, ReceivedInvoice, RequestOptions } from '../types.js';
|
|
5
5
|
/** Manages invoice lifecycle — creation, finalization, sending, and document retrieval. */
|
|
6
6
|
export declare class Invoices {
|
|
7
7
|
private readonly client;
|
|
8
8
|
readonly payments: Payments;
|
|
9
9
|
constructor(client: HttpClient);
|
|
10
|
+
/**
|
|
11
|
+
* Create an invoice — ALWAYS backed by a FINAL tax decision (`taxDecisionId`
|
|
12
|
+
* + `decisionLines`), whatever its fiscal source. The decided VAT, amounts
|
|
13
|
+
* and mentions are copied verbatim and frozen; `deposits` and `schedule` are
|
|
14
|
+
* settled server-side against the decided amount, inside the creation
|
|
15
|
+
* transaction. The types enforce this at compile time; the checks below turn
|
|
16
|
+
* the same misuse into an immediate local error instead of a round trip.
|
|
17
|
+
*/
|
|
10
18
|
create(params: InvoiceCreateParams, options?: RequestOptions): Promise<Invoice>;
|
|
11
19
|
list(params?: InvoiceListParams): AutoPaginatingList<Invoice>;
|
|
12
20
|
/**
|
|
@@ -19,6 +27,28 @@ export declare class Invoices {
|
|
|
19
27
|
update(id: string, params: InvoiceUpdateParams): Promise<Invoice>;
|
|
20
28
|
/** Soft-delete (draft only). */
|
|
21
29
|
del(id: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Bind a FINAL tax decision to a commercial draft that already exists —
|
|
32
|
+
* typically the one `quotes.convert()` produced.
|
|
33
|
+
*
|
|
34
|
+
* This closes the quote cycle on ONE document:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const { invoiceId } = await facturino.quotes.convert(quoteId)
|
|
38
|
+
* const decision = await facturino.taxDecisions.create({ ... }, { idempotencyKey })
|
|
39
|
+
* await facturino.invoices.bindTaxDecision(invoiceId, {
|
|
40
|
+
* taxDecisionId: decision.id,
|
|
41
|
+
* decisionLines: [{ taxLineRef: 'l1', unit: 'unit' }],
|
|
42
|
+
* })
|
|
43
|
+
* await facturino.invoices.finalize(invoiceId)
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* The invoice stays a DRAFT: binding freezes the VAT, `finalize()` issues it.
|
|
47
|
+
* Idempotent on the decision — replaying the same call returns the same
|
|
48
|
+
* invoice. Binding a different decision to a bound invoice, or the same
|
|
49
|
+
* decision to a second invoice, is a conflict.
|
|
50
|
+
*/
|
|
51
|
+
bindTaxDecision(id: string, params: InvoiceBindTaxDecisionParams, options?: RequestOptions): Promise<Invoice>;
|
|
22
52
|
/** Assign number and lock. Irreversible. */
|
|
23
53
|
finalize(id: string, options?: RequestOptions): Promise<Invoice>;
|
|
24
54
|
/** Submit to PA. Returns 202 (async). */
|
|
@@ -9,7 +9,27 @@ class Invoices {
|
|
|
9
9
|
this.client = client;
|
|
10
10
|
this.payments = new payments_js_1.Payments(client);
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Create an invoice — ALWAYS backed by a FINAL tax decision (`taxDecisionId`
|
|
14
|
+
* + `decisionLines`), whatever its fiscal source. The decided VAT, amounts
|
|
15
|
+
* and mentions are copied verbatim and frozen; `deposits` and `schedule` are
|
|
16
|
+
* settled server-side against the decided amount, inside the creation
|
|
17
|
+
* transaction. The types enforce this at compile time; the checks below turn
|
|
18
|
+
* the same misuse into an immediate local error instead of a round trip.
|
|
19
|
+
*/
|
|
12
20
|
async create(params, options) {
|
|
21
|
+
const runtime = params;
|
|
22
|
+
if (typeof runtime.taxDecisionId !== 'string' || runtime.taxDecisionId.length === 0) {
|
|
23
|
+
throw new Error(`'taxDecisionId' is required: every invoice is backed by a FINAL tax decision `
|
|
24
|
+
+ `(facturino or integration source). Create one with taxDecisions.create() first.`);
|
|
25
|
+
}
|
|
26
|
+
if (!Array.isArray(runtime.decisionLines) || runtime.decisionLines.length === 0) {
|
|
27
|
+
throw new Error(`'decisionLines' is required: one presentation line per decision line, matched by 'taxLineRef'.`);
|
|
28
|
+
}
|
|
29
|
+
if ('lines' in runtime && runtime.lines !== undefined) {
|
|
30
|
+
throw new Error(`'lines' is not part of the invoice contract: the VAT of an invoice comes from its `
|
|
31
|
+
+ `tax decision. Send 'decisionLines' (presentation only).`);
|
|
32
|
+
}
|
|
13
33
|
return this.client.post('/v1/invoices', params, options);
|
|
14
34
|
}
|
|
15
35
|
list(params) {
|
|
@@ -33,6 +53,37 @@ class Invoices {
|
|
|
33
53
|
async del(id) {
|
|
34
54
|
await this.client.del(`/v1/invoices/${id}`);
|
|
35
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Bind a FINAL tax decision to a commercial draft that already exists —
|
|
58
|
+
* typically the one `quotes.convert()` produced.
|
|
59
|
+
*
|
|
60
|
+
* This closes the quote cycle on ONE document:
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const { invoiceId } = await facturino.quotes.convert(quoteId)
|
|
64
|
+
* const decision = await facturino.taxDecisions.create({ ... }, { idempotencyKey })
|
|
65
|
+
* await facturino.invoices.bindTaxDecision(invoiceId, {
|
|
66
|
+
* taxDecisionId: decision.id,
|
|
67
|
+
* decisionLines: [{ taxLineRef: 'l1', unit: 'unit' }],
|
|
68
|
+
* })
|
|
69
|
+
* await facturino.invoices.finalize(invoiceId)
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* The invoice stays a DRAFT: binding freezes the VAT, `finalize()` issues it.
|
|
73
|
+
* Idempotent on the decision — replaying the same call returns the same
|
|
74
|
+
* invoice. Binding a different decision to a bound invoice, or the same
|
|
75
|
+
* decision to a second invoice, is a conflict.
|
|
76
|
+
*/
|
|
77
|
+
async bindTaxDecision(id, params, options) {
|
|
78
|
+
const runtime = params;
|
|
79
|
+
if (typeof runtime.taxDecisionId !== 'string' || runtime.taxDecisionId.length === 0) {
|
|
80
|
+
throw new Error(`'taxDecisionId' is required: a draft is fiscalised by binding a FINAL tax decision to it.`);
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(runtime.decisionLines) || runtime.decisionLines.length === 0) {
|
|
83
|
+
throw new Error(`'decisionLines' is required: one presentation line per decision line, matched by 'taxLineRef'.`);
|
|
84
|
+
}
|
|
85
|
+
return this.client.post(`/v1/invoices/${id}/bind-tax-decision`, params, options);
|
|
86
|
+
}
|
|
36
87
|
/** Assign number and lock. Irreversible. */
|
|
37
88
|
async finalize(id, options) {
|
|
38
89
|
return this.client.post(`/v1/invoices/${id}/finalize`, undefined, options);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { HttpClient } from '../client.js';
|
|
2
|
+
import type { TaxDecision, TaxDecisionCreateParams, RequestOptions } from '../types.js';
|
|
3
|
+
export declare class TaxDecisions {
|
|
4
|
+
private readonly client;
|
|
5
|
+
constructor(client: HttpClient);
|
|
6
|
+
/**
|
|
7
|
+
* Take a decision on a commercial operation.
|
|
8
|
+
*
|
|
9
|
+
* Business idempotency is durable here, beyond the 24-hour transport window,
|
|
10
|
+
* and it is keyed on the KEY — not on the operation. The SAME
|
|
11
|
+
* `idempotencyKey` with the SAME canonical body always replays the SAME
|
|
12
|
+
* decision, so a retry after a lost response costs nothing and charges
|
|
13
|
+
* nothing twice. Two DIFFERENT keys describing the same operation produce TWO
|
|
14
|
+
* decisions: nothing matches them up, and reusing one key with a different
|
|
15
|
+
* body answers `409`.
|
|
16
|
+
*
|
|
17
|
+
* Only a `final` decision carries amounts. On `pending_verification` or
|
|
18
|
+
* `unsupported`, `totals` and `amountToCharge` are `null` — never `0` — and
|
|
19
|
+
* `issues` says what is missing.
|
|
20
|
+
*/
|
|
21
|
+
create(params: TaxDecisionCreateParams, options: RequestOptions & {
|
|
22
|
+
idempotencyKey: string;
|
|
23
|
+
}): Promise<TaxDecision>;
|
|
24
|
+
/**
|
|
25
|
+
* Read a decision back — typically after a payment capture, to check that the
|
|
26
|
+
* captured amount, currency and buyer match what was decided.
|
|
27
|
+
*/
|
|
28
|
+
retrieve(id: string): Promise<TaxDecision>;
|
|
29
|
+
/** Alias of {@link TaxDecisions.retrieve}, for consistency with the other resources. */
|
|
30
|
+
get(id: string): Promise<TaxDecision>;
|
|
31
|
+
}
|