@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 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
- const invoice = await facturino.invoices.create({
22
- customerId: 'cus_xxx',
23
- buyer: {
24
- companyName: 'Acme SAS',
25
- siret: '55208131766522',
26
- address: { line1: '10 rue de la Paix', postalCode: '75002', city: 'Paris', country: 'FR' },
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
- description: 'Consulting',
30
- quantity: '1', // decimal string
31
- unit: 'flat_rate',
32
- unitPrice: 10000, // 100.00 EUR (centimes)
33
- vatRate: 2000, // 20.00% (centièmes de pourcent)
34
- vatCode: 'S',
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
- dates: { issued: '2026-07-01', due: '2026-07-31' },
37
- payment: { terms: 'Paiement à 30 jours', termsDays: 30, method: 'transfer', latePaymentRate: '10.00', collectionFee: '40.00' },
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
- const finalized = await facturino.invoices.finalize(invoice.id)
62
+ // 4. Choose your collection flow — see the two variants below.
63
+ ```
41
64
 
42
- // One-shot: pass `autoFinalize: true` (and optionally
43
- // `autoSend: { email: true, pa: true }`) to `invoices.create(...)` to
44
- // finalize — and deliver by email and/or to the PA — in a single call.
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-03-01',
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(params)
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(params)
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(params)
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
- await facturino.invoices.create(params, { idempotencyKey: 'unique-id' })
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
@@ -1,5 +1,5 @@
1
1
  import type { FacturinoConfig, RequestOptions } from './types.js';
2
- export declare const VERSION = "1.1.0";
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;
@@ -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-03-01';
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 = '1.1.0';
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 = {}) {
@@ -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
+ }