@factpulse/sdk 3.0.36 → 4.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.
Files changed (58) hide show
  1. package/.openapi-generator/FILES +2 -2
  2. package/CHANGELOG.md +10 -13
  3. package/README.md +135 -148
  4. package/dist/esm/models/{facture-electronique-rest-api-schemas-ereporting-invoice-type-code.d.ts → facture-electronique-models-invoice-type-code.d.ts} +67 -8
  5. package/dist/esm/models/{facture-electronique-rest-api-schemas-ereporting-invoice-type-code.js → facture-electronique-models-invoice-type-code.js} +66 -7
  6. package/dist/esm/models/index.d.ts +1 -1
  7. package/dist/esm/models/index.js +1 -1
  8. package/dist/esm/models/invoice-input.d.ts +2 -2
  9. package/dist/esm/models/invoice-type-code.d.ts +6 -65
  10. package/dist/esm/models/invoice-type-code.js +6 -65
  11. package/dist/esm/models/recipient.d.ts +1 -1
  12. package/dist/esm/models/scheme-id.d.ts +7 -7
  13. package/dist/esm/models/scheme-id.js +7 -7
  14. package/dist/esm/models/simplified-invoice-data.d.ts +2 -2
  15. package/dist/esm/models/submit-complete-invoice-response.d.ts +1 -1
  16. package/dist/esm/models/supplier.d.ts +1 -1
  17. package/dist/esm/src/helpers/client.d.ts +43 -265
  18. package/dist/esm/src/helpers/client.js +196 -779
  19. package/dist/esm/src/helpers/index.d.ts +1 -2
  20. package/dist/esm/src/helpers/index.js +1 -3
  21. package/dist/models/{facture-electronique-rest-api-schemas-ereporting-invoice-type-code.d.ts → facture-electronique-models-invoice-type-code.d.ts} +67 -8
  22. package/dist/models/{facture-electronique-rest-api-schemas-ereporting-invoice-type-code.js → facture-electronique-models-invoice-type-code.js} +67 -8
  23. package/dist/models/index.d.ts +1 -1
  24. package/dist/models/index.js +1 -1
  25. package/dist/models/invoice-input.d.ts +2 -2
  26. package/dist/models/invoice-type-code.d.ts +6 -65
  27. package/dist/models/invoice-type-code.js +6 -65
  28. package/dist/models/recipient.d.ts +1 -1
  29. package/dist/models/scheme-id.d.ts +7 -7
  30. package/dist/models/scheme-id.js +7 -7
  31. package/dist/models/simplified-invoice-data.d.ts +2 -2
  32. package/dist/models/submit-complete-invoice-response.d.ts +1 -1
  33. package/dist/models/supplier.d.ts +1 -1
  34. package/dist/src/helpers/client.d.ts +43 -265
  35. package/dist/src/helpers/client.js +199 -823
  36. package/dist/src/helpers/index.d.ts +1 -2
  37. package/dist/src/helpers/index.js +2 -12
  38. package/docs/FactureElectroniqueModelsInvoiceTypeCode.md +39 -0
  39. package/docs/InvoiceInput.md +1 -1
  40. package/docs/InvoiceTypeCode.md +6 -28
  41. package/docs/Recipient.md +1 -1
  42. package/docs/SchemeID.md +4 -4
  43. package/docs/SimplifiedInvoiceData.md +1 -1
  44. package/docs/SubmitCompleteInvoiceResponse.md +2 -2
  45. package/docs/Supplier.md +1 -1
  46. package/models/{facture-electronique-rest-api-schemas-ereporting-invoice-type-code.ts → facture-electronique-models-invoice-type-code.ts} +67 -8
  47. package/models/index.ts +1 -1
  48. package/models/invoice-input.ts +2 -2
  49. package/models/invoice-type-code.ts +6 -65
  50. package/models/recipient.ts +1 -1
  51. package/models/scheme-id.ts +7 -7
  52. package/models/simplified-invoice-data.ts +2 -2
  53. package/models/submit-complete-invoice-response.ts +1 -1
  54. package/models/supplier.ts +1 -1
  55. package/package.json +1 -1
  56. package/src/helpers/client.ts +211 -834
  57. package/src/helpers/index.ts +1 -3
  58. package/docs/FactureElectroniqueRestApiSchemasEreportingInvoiceTypeCode.md +0 -17
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -45,850 +12,259 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
13
  };
47
14
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.FactPulseClient = void 0;
49
- exports.amount = amount;
50
- exports.invoiceTotals = invoiceTotals;
51
- exports.invoiceLine = invoiceLine;
52
- exports.vatLine = vatLine;
53
- exports.postalAddress = postalAddress;
54
- exports.electronicAddress = electronicAddress;
55
- exports.supplier = supplier;
56
- exports.recipient = recipient;
57
- exports.beneficiary = beneficiary;
58
- const axios_1 = __importDefault(require("axios"));
59
- const form_data_1 = __importDefault(require("form-data"));
60
- const fs = __importStar(require("fs"));
61
- const path = __importStar(require("path"));
62
- const exceptions_1 = require("./exceptions");
63
- /** Converts a value to an amount string for the API. */
64
- function amount(value) {
65
- if (value === null || value === undefined)
66
- return '0.00';
67
- if (typeof value === 'number')
68
- return value.toFixed(2);
69
- if (typeof value === 'string')
70
- return value;
71
- return '0.00';
72
- }
73
- /** Creates a simplified InvoiceTotals object. */
74
- function invoiceTotals(exclTax, vat, inclTax, amountDue, options) {
75
- const result = {
76
- totalNetAmount: amount(exclTax), vatAmount: amount(vat),
77
- totalGrossAmount: amount(inclTax), amountDue: amount(amountDue),
78
- };
79
- if ((options === null || options === void 0 ? void 0 : options.globalAllowanceAmount) !== undefined)
80
- result.globalAllowanceAmount = amount(options.globalAllowanceAmount);
81
- if ((options === null || options === void 0 ? void 0 : options.globalAllowanceReason) !== undefined)
82
- result.globalAllowanceReason = options.globalAllowanceReason;
83
- if ((options === null || options === void 0 ? void 0 : options.prepayment) !== undefined)
84
- result.prepayment = amount(options.prepayment);
85
- return result;
86
- }
87
- /** Creates an invoice line (aligned with InvoiceLine in models.py).
88
- * For VAT rate: either vatRate (code e.g.: "VAT20") or manualVatRate (value e.g.: 20.00) */
89
- function invoiceLine(lineNumber, itemName, quantity, unitNetPrice, lineNetAmount, options) {
90
- var _a, _b, _c;
91
- const result = {
92
- lineNumber, itemName, quantity: amount(quantity), unitNetPrice: amount(unitNetPrice),
93
- lineNetAmount: amount(lineNetAmount),
94
- vatCategory: (_a = options === null || options === void 0 ? void 0 : options.vatCategory) !== null && _a !== void 0 ? _a : 'S', unit: (_b = options === null || options === void 0 ? void 0 : options.unit) !== null && _b !== void 0 ? _b : 'LUMP_SUM',
95
- };
96
- // Either vatRate (code) or manualVatRate (value)
97
- if (options === null || options === void 0 ? void 0 : options.vatRate)
98
- result.vatRate = options.vatRate;
99
- else
100
- result.manualVatRate = amount((_c = options === null || options === void 0 ? void 0 : options.manualVatRate) !== null && _c !== void 0 ? _c : '20.00');
101
- if (options === null || options === void 0 ? void 0 : options.reference)
102
- result.reference = options.reference;
103
- if ((options === null || options === void 0 ? void 0 : options.lineAllowanceAmount) !== undefined)
104
- result.lineAllowanceAmount = amount(options.lineAllowanceAmount);
105
- if (options === null || options === void 0 ? void 0 : options.allowanceReasonCode)
106
- result.allowanceReasonCode = options.allowanceReasonCode;
107
- if (options === null || options === void 0 ? void 0 : options.allowanceReason)
108
- result.allowanceReason = options.allowanceReason;
109
- if (options === null || options === void 0 ? void 0 : options.periodStartDate)
110
- result.periodStartDate = options.periodStartDate;
111
- if (options === null || options === void 0 ? void 0 : options.periodEndDate)
112
- result.periodEndDate = options.periodEndDate;
113
- return result;
114
- }
115
- /** Creates a VAT line (aligned with VATLine in models.py).
116
- * For rate: either rate (code e.g.: "VAT20") or manualRate (value e.g.: 20.00) */
117
- function vatLine(taxableAmount, vatAmount, options) {
118
- var _a, _b;
119
- const result = {
120
- taxableAmount: amount(taxableAmount), vatAmount: amount(vatAmount), category: (_a = options === null || options === void 0 ? void 0 : options.category) !== null && _a !== void 0 ? _a : 'S',
121
- };
122
- // Either rate (code) or manualRate (value)
123
- if (options === null || options === void 0 ? void 0 : options.rate)
124
- result.rate = options.rate;
125
- else
126
- result.manualRate = amount((_b = options === null || options === void 0 ? void 0 : options.manualRate) !== null && _b !== void 0 ? _b : '20.00');
127
- return result;
128
- }
129
- /** Creates a postal address for the FactPulse API. */
130
- function postalAddress(lineOne, postalCode, city, options) {
131
- var _a;
132
- const result = { lineOne, postalCode, city, countryCode: (_a = options === null || options === void 0 ? void 0 : options.countryCode) !== null && _a !== void 0 ? _a : 'FR' };
133
- if (options === null || options === void 0 ? void 0 : options.lineTwo)
134
- result.lineTwo = options.lineTwo;
135
- if (options === null || options === void 0 ? void 0 : options.lineThree)
136
- result.lineThree = options.lineThree;
137
- return result;
138
- }
139
- /** Creates an electronic address for the FactPulse API. schemeId: "0009"=SIREN, "0225"=SIRET */
140
- function electronicAddress(identifier, schemeId = '0009') {
141
- return { identifier, schemeId };
142
- }
143
- /** Computes the French intra-community VAT number from a SIREN. */
144
- function computeVatIntra(siren) {
145
- if (siren.length !== 9 || !/^\d+$/.test(siren))
146
- return null;
147
- const cle = (12 + 3 * (parseInt(siren, 10) % 97)) % 97;
148
- return `FR${cle.toString().padStart(2, '0')}${siren}`;
149
- }
150
- /** Creates a supplier (issuer) with auto-computed SIREN, intra-EU VAT number and addresses. */
151
- function supplier(name, siret, addressLine1, postalCode, city, options) {
152
- var _a, _b, _c;
153
- const opts = options !== null && options !== void 0 ? options : {};
154
- const siren = (_a = opts.siren) !== null && _a !== void 0 ? _a : (siret.length === 14 ? siret.slice(0, 9) : undefined);
155
- const vatNumber = (_b = opts.vatNumber) !== null && _b !== void 0 ? _b : (siren ? computeVatIntra(siren) : null);
156
- const result = {
157
- name, supplierId: (_c = opts.supplierId) !== null && _c !== void 0 ? _c : 0, siret,
158
- electronicAddress: electronicAddress(siret, '0225'),
159
- postalAddress: postalAddress(addressLine1, postalCode, city, { countryCode: opts.countryCode, lineTwo: opts.addressLine2 }),
160
- };
161
- if (siren)
162
- result.siren = siren;
163
- if (vatNumber)
164
- result.vatNumber = vatNumber;
165
- if (opts.iban)
166
- result.iban = opts.iban;
167
- if (opts.supplierServiceId)
168
- result.supplierServiceId = opts.supplierServiceId;
169
- if (opts.supplierBankDetailsCode)
170
- result.supplierBankDetailsCode = opts.supplierBankDetailsCode;
171
- return result;
172
- }
173
- /** Creates a recipient (customer) with auto-computed SIREN and addresses. */
174
- function recipient(name, siret, addressLine1, postalCode, city, options) {
175
- var _a;
176
- const opts = options !== null && options !== void 0 ? options : {};
177
- const siren = (_a = opts.siren) !== null && _a !== void 0 ? _a : (siret.length === 14 ? siret.slice(0, 9) : undefined);
178
- const result = {
179
- name, siret,
180
- electronicAddress: electronicAddress(siret, '0225'),
181
- postalAddress: postalAddress(addressLine1, postalCode, city, { countryCode: opts.countryCode, lineTwo: opts.addressLine2 }),
182
- };
183
- if (siren)
184
- result.siren = siren;
185
- if (opts.executingServiceCode)
186
- result.executingServiceCode = opts.executingServiceCode;
187
- return result;
188
- }
15
+ exports.FactPulseClient = exports.FactPulseError = void 0;
189
16
  /**
190
- * Creates a beneficiary (factor) for factoring.
17
+ * FactPulse SDK - Thin HTTP wrapper with auto-polling.
191
18
  *
192
- * The beneficiary (BG-10 / PayeeTradeParty) is used when payment
193
- * must be made to a third party different from the supplier, typically
194
- * a factor (factoring company).
19
+ * Usage:
20
+ * const client = new FactPulseClient({ email: 'user@example.com', password: 'secret', clientUid: 'xxx' });
195
21
  *
196
- * For factored invoices, you also need to:
197
- * - Use a factored document type (393, 396, 501, 502, 472, 473)
198
- * - Add an ACC note with the subrogation mention
199
- * - The beneficiary's IBAN will be used for payment
22
+ * // POST /api/v1/processing/invoices/submit-complete-async
23
+ * const result = await client.post('processing/invoices/submit-complete-async', {
24
+ * invoiceData: {...},
25
+ * sourcePdf: Buffer.from(pdf).toString('base64'),
26
+ * destination: { type: 'afnor' }
27
+ * });
28
+ * const pdfBytes = result.content; // auto-decoded, auto-polled
200
29
  *
201
- * @param name Factor's business name (BT-59)
202
- * @param options Options: siret (BT-60), siren (BT-61), iban, bic
203
- * @returns Dict ready to be used in a factored invoice
204
- *
205
- * @example
206
- * const factor = beneficiary('FACTOR SAS', {
207
- * siret: '30000000700033',
208
- * iban: 'FR76 3000 4000 0500 0012 3456 789',
209
- * });
30
+ * // GET /api/v1/chorus-pro/structures/123
31
+ * const structure = await client.get('chorus-pro/structures/123');
210
32
  */
211
- function beneficiary(name, options) {
212
- var _a;
213
- const opts = options !== null && options !== void 0 ? options : {};
214
- // Auto-compute SIREN from SIRET
215
- const siren = (_a = opts.siren) !== null && _a !== void 0 ? _a : (opts.siret && opts.siret.length === 14 ? opts.siret.slice(0, 9) : undefined);
216
- const result = { name };
217
- if (opts.siret)
218
- result.siret = opts.siret;
219
- if (siren)
220
- result.siren = siren;
221
- if (opts.iban)
222
- result.iban = opts.iban;
223
- if (opts.bic)
224
- result.bic = opts.bic;
225
- return result;
33
+ const axios_1 = __importDefault(require("axios"));
34
+ class FactPulseError extends Error {
35
+ constructor(message, statusCode, details) {
36
+ super(message);
37
+ this.name = 'FactPulseError';
38
+ this.statusCode = statusCode;
39
+ this.details = details !== null && details !== void 0 ? details : [];
40
+ }
226
41
  }
227
- // =============================================================================
228
- // Main client
229
- // =============================================================================
42
+ exports.FactPulseError = FactPulseError;
230
43
  const DEFAULT_API_URL = 'https://factpulse.fr';
231
- const DEFAULT_POLLING_INTERVAL = 2000;
232
- const DEFAULT_POLLING_TIMEOUT = 120000;
233
- const DEFAULT_MAX_RETRIES = 1;
234
44
  class FactPulseClient {
235
45
  constructor(config) {
236
- var _a;
237
- this.accessToken = null;
238
- this.refreshToken = null;
239
- this.tokenExpiresAt = null;
240
- this.config = {
241
- email: config.email, password: config.password,
242
- apiUrl: (config.apiUrl || DEFAULT_API_URL).replace(/\/$/, ''),
243
- clientUid: config.clientUid || '',
244
- chorusCredentials: config.chorusCredentials,
245
- afnorCredentials: config.afnorCredentials,
246
- pollingInterval: config.pollingInterval || DEFAULT_POLLING_INTERVAL,
247
- pollingTimeout: config.pollingTimeout || DEFAULT_POLLING_TIMEOUT,
248
- maxRetries: (_a = config.maxRetries) !== null && _a !== void 0 ? _a : DEFAULT_MAX_RETRIES,
249
- };
250
- this.chorusCredentials = config.chorusCredentials;
251
- this.afnorCredentials = config.afnorCredentials;
252
- this.httpClient = axios_1.default.create({ timeout: 30000, headers: { 'Content-Type': 'application/json' } });
253
- }
254
- getChorusCredentialsForApi() {
255
- var _a;
256
- if (!this.chorusCredentials)
257
- return undefined;
258
- return {
259
- piste_client_id: this.chorusCredentials.pisteClientId,
260
- piste_client_secret: this.chorusCredentials.pisteClientSecret,
261
- chorus_pro_login: this.chorusCredentials.chorusProLogin,
262
- chorus_pro_password: this.chorusCredentials.chorusProPassword,
263
- sandbox: (_a = this.chorusCredentials.sandbox) !== null && _a !== void 0 ? _a : true,
264
- };
265
- }
266
- getAfnorCredentialsForApi() {
267
- if (!this.afnorCredentials)
268
- return undefined;
269
- return {
270
- client_id: this.afnorCredentials.clientId,
271
- client_secret: this.afnorCredentials.clientSecret,
272
- flow_service_url: this.afnorCredentials.flowServiceUrl,
273
- };
274
- }
275
- // Shorter aliases
276
- getChorusProCredentials() { return this.getChorusCredentialsForApi(); }
277
- getAfnorCredentials() { return this.getAfnorCredentialsForApi(); }
278
- obtainToken() {
279
- return __awaiter(this, void 0, void 0, function* () {
280
- var _a, _b;
281
- const payload = { username: this.config.email, password: this.config.password };
282
- if (this.config.clientUid)
283
- payload.client_uid = this.config.clientUid;
284
- try {
285
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/token/`, payload);
286
- return response.data;
287
- }
288
- catch (error) {
289
- const axiosError = error;
290
- throw new exceptions_1.FactPulseAuthError(`Unable to obtain JWT token: ${((_b = (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.detail) || axiosError.message}`);
291
- }
46
+ var _a, _b, _c;
47
+ this.token = null;
48
+ this.tokenExpiresAt = 0;
49
+ this.apiUrl = ((_a = config.apiUrl) !== null && _a !== void 0 ? _a : DEFAULT_API_URL).replace(/\/$/, '');
50
+ this.email = config.email;
51
+ this.password = config.password;
52
+ this.clientUid = config.clientUid;
53
+ this.timeout = (_b = config.timeout) !== null && _b !== void 0 ? _b : 60000;
54
+ this.pollingTimeout = (_c = config.pollingTimeout) !== null && _c !== void 0 ? _c : 120000;
55
+ this.httpClient = axios_1.default.create({
56
+ timeout: this.timeout,
57
+ validateStatus: () => true, // Handle all status codes manually
292
58
  });
293
59
  }
294
- ensureAuthenticated() {
295
- return __awaiter(this, arguments, void 0, function* (forceRefresh = false) {
296
- const now = Date.now();
297
- if (forceRefresh || !this.accessToken || (this.tokenExpiresAt && now >= this.tokenExpiresAt)) {
298
- const tokens = yield this.obtainToken();
299
- this.accessToken = tokens.access;
300
- this.refreshToken = tokens.refresh;
301
- this.tokenExpiresAt = now + 28 * 60 * 1000;
302
- }
60
+ /** POST request to /api/v1/{path} */
61
+ post(path, data) {
62
+ return __awaiter(this, void 0, void 0, function* () {
63
+ return this._doRequest('POST', path, data, true);
303
64
  });
304
65
  }
305
- resetAuth() { this.accessToken = this.refreshToken = null; this.tokenExpiresAt = null; }
306
- pollTask(taskId, timeout, interval) {
66
+ /** GET request to /api/v1/{path} */
67
+ get(path, params) {
307
68
  return __awaiter(this, void 0, void 0, function* () {
308
- var _a;
309
- const timeoutMs = timeout !== null && timeout !== void 0 ? timeout : this.config.pollingTimeout;
310
- const intervalMs = interval !== null && interval !== void 0 ? interval : this.config.pollingInterval;
311
- const startTime = Date.now();
312
- let currentInterval = intervalMs;
313
- while (true) {
314
- if (Date.now() - startTime > timeoutMs)
315
- throw new exceptions_1.FactPulsePollingTimeout(taskId, timeoutMs);
316
- yield this.ensureAuthenticated();
317
- try {
318
- const response = yield this.httpClient.get(`${this.config.apiUrl}/api/v1/processing/tasks/${taskId}/status`, {
319
- headers: { Authorization: `Bearer ${this.accessToken}` },
320
- });
321
- const { status, result } = response.data;
322
- if (status === 'SUCCESS')
323
- return result || {};
324
- if (status === 'FAILURE') {
325
- // Format AFNOR: errorMessage, details
326
- const failureResult = result;
327
- const errors = Array.isArray(result === null || result === void 0 ? void 0 : result.details) ? result.details.filter((e) => typeof e === 'object' && e !== null) : [];
328
- throw new exceptions_1.FactPulseValidationError(`Task ${taskId} failed: ${(result === null || result === void 0 ? void 0 : result.errorMessage) || 'Unknown error'}`, errors);
329
- }
330
- yield new Promise(resolve => setTimeout(resolve, currentInterval));
331
- currentInterval = Math.min(currentInterval * 1.5, 10000);
332
- }
333
- catch (error) {
334
- if (error instanceof exceptions_1.FactPulseValidationError || error instanceof exceptions_1.FactPulsePollingTimeout)
335
- throw error;
336
- const axiosError = error;
337
- if (((_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
338
- this.resetAuth();
339
- continue;
340
- }
341
- throw new exceptions_1.FactPulseValidationError(`API Error: ${axiosError.message}`);
342
- }
343
- }
69
+ return this._doRequest('GET', path, params, true);
344
70
  });
345
71
  }
346
- generateFacturx(invoiceData_1, pdfPath_1) {
347
- return __awaiter(this, arguments, void 0, function* (invoiceData, pdfPath, profile = 'EN16931', outputFormat = 'pdf', sync = true, timeout) {
348
- var _a, _b, _c, _d;
349
- const jsonData = typeof invoiceData === 'string' ? invoiceData : JSON.stringify(invoiceData);
350
- let taskId = null;
351
- for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
352
- yield this.ensureAuthenticated();
353
- const form = new form_data_1.default();
354
- form.append('invoice_data', Buffer.from(jsonData, 'utf-8'), { contentType: 'application/json' });
355
- form.append('profile', profile);
356
- form.append('output_format', outputFormat);
357
- form.append('source_pdf', fs.createReadStream(pdfPath), { filename: path.basename(pdfPath), contentType: 'application/pdf' });
358
- try {
359
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/processing/generate-invoice`, form, {
360
- headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: `Bearer ${this.accessToken}` }), timeout: 60000,
361
- });
362
- taskId = response.data.taskId;
363
- break;
364
- }
365
- catch (error) {
366
- const axiosError = error;
367
- if (((_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.status) === 401 && attempt < this.config.maxRetries) {
368
- this.resetAuth();
369
- continue;
370
- }
371
- // Extract error details from response body
372
- const responseData = (_b = axiosError.response) === null || _b === void 0 ? void 0 : _b.data;
373
- let errorMsg = `API Error (${((_c = axiosError.response) === null || _c === void 0 ? void 0 : _c.status) || 'unknown'}): ${axiosError.message}`;
374
- const errors = [];
375
- if (responseData) {
376
- // Format FastAPI/Pydantic: {"detail": [{"loc": [...], "msg": "...", "type": "..."}]}
377
- if (Array.isArray(responseData.detail)) {
378
- errorMsg = 'Validation error';
379
- for (const err of responseData.detail) {
380
- if (typeof err === 'object' && err !== null) {
381
- const loc = err.loc || [];
382
- errors.push({
383
- level: 'ERROR',
384
- item: loc.map(String).join(' -> '),
385
- reason: err.msg || String(err),
386
- source: 'validation',
387
- code: err.type,
388
- });
389
- }
390
- }
391
- }
392
- else if (typeof responseData.detail === 'string') {
393
- errorMsg = responseData.detail;
394
- }
395
- else if (responseData.errorMessage) {
396
- errorMsg = responseData.errorMessage;
397
- }
398
- }
399
- console.error(`API Error ${(_d = axiosError.response) === null || _d === void 0 ? void 0 : _d.status}:`, responseData);
400
- throw new exceptions_1.FactPulseValidationError(errorMsg, errors);
401
- }
402
- }
403
- if (!taskId)
404
- throw new exceptions_1.FactPulseValidationError("No task ID");
405
- if (!sync)
406
- return taskId;
407
- const result = yield this.pollTask(taskId, timeout);
408
- // Check for business error (task succeeded but business result is ERROR)
409
- if (result.status === 'ERROR') {
410
- const errorMsg = result.errorMessage || 'Business error';
411
- const errors = Array.isArray(result.details)
412
- ? result.details.filter((e) => typeof e === 'object' && e !== null)
413
- : [];
414
- throw new exceptions_1.FactPulseValidationError(errorMsg, errors);
415
- }
416
- if (result.content_b64)
417
- return Buffer.from(result.content_b64, 'base64');
418
- throw new exceptions_1.FactPulseValidationError('No content');
419
- });
72
+ // Dynamic endpoint builder (alternative syntax)
73
+ get processing() { return this._endpoint('processing'); }
74
+ get chorus_pro() { return this._endpoint('chorus-pro'); }
75
+ get afnor() { return this._endpoint('afnor'); }
76
+ _endpoint(path) {
77
+ return new Endpoint(this, path);
420
78
  }
421
- static formatAmount(m) {
422
- if (m === null || m === undefined)
423
- return '0.00';
424
- if (typeof m === 'number')
425
- return m.toFixed(2);
426
- if (typeof m === 'string')
427
- return m;
428
- return '0.00';
79
+ _request(method, path, data) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ return this._doRequest(method, path, data, true);
82
+ });
429
83
  }
430
- // =========================================================================
431
- // AFNOR - Authentication and internal helpers
432
- // =========================================================================
433
- /**
434
- * Retrieves AFNOR credentials (stored or zero-trust mode).
435
- * Zero-trust mode: Returns the afnorCredentials provided to the constructor.
436
- * Stored mode: Retrieves credentials via GET /api/v1/afnor/credentials.
437
- */
438
- getAfnorCredentialsInternal() {
84
+ _doRequest(method, path, data, retryAuth) {
439
85
  return __awaiter(this, void 0, void 0, function* () {
440
86
  var _a, _b;
441
- // Zero-trust mode: credentials provided to the constructor
442
- if (this.afnorCredentials) {
443
- return this.afnorCredentials;
444
- }
445
- // Stored mode: retrieve credentials via the API
446
- yield this.ensureAuthenticated();
87
+ yield this._ensureAuth();
88
+ const url = `${this.apiUrl}/api/v1/${path}`;
89
+ const headers = { Authorization: `Bearer ${this.token}` };
90
+ let response;
447
91
  try {
448
- const response = yield this.httpClient.get(`${this.config.apiUrl}/api/v1/afnor/credentials`, {
449
- headers: { Authorization: `Bearer ${this.accessToken}` },
450
- });
451
- const creds = response.data;
452
- return {
453
- flowServiceUrl: creds.flow_service_url,
454
- tokenUrl: creds.token_url,
455
- clientId: creds.client_id,
456
- clientSecret: creds.client_secret,
457
- directoryServiceUrl: creds.directory_service_url,
458
- };
92
+ response = method === 'GET'
93
+ ? yield this.httpClient.get(url, { headers, params: data })
94
+ : yield this.httpClient.post(url, data, { headers });
459
95
  }
460
- catch (error) {
461
- const axiosError = error;
462
- if (((_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.status) === 400) {
463
- const detail = (_b = axiosError.response.data) === null || _b === void 0 ? void 0 : _b.detail;
464
- if (typeof detail === 'object' && (detail === null || detail === void 0 ? void 0 : detail.error) === 'NO_CLIENT_UID') {
465
- throw new exceptions_1.FactPulseAuthError("No client_uid in JWT. To use AFNOR endpoints, either:\n" +
466
- "1. Generate a token with a client_uid (stored mode)\n" +
467
- "2. Provide AFNORCredentials to the client constructor (zero-trust mode)");
468
- }
96
+ catch (e) {
97
+ throw new FactPulseError(`Network error: ${e.message}`);
98
+ }
99
+ if (response.status === 401 && retryAuth) {
100
+ this._invalidateToken();
101
+ return this._doRequest(method, path, data, false);
102
+ }
103
+ const result = this._parseResponse(response.status, response.data);
104
+ // Auto-poll: support both taskId (camelCase) and task_id (snake_case)
105
+ if (result && typeof result === 'object') {
106
+ const r = result;
107
+ const taskId = (_a = r.taskId) !== null && _a !== void 0 ? _a : r.task_id;
108
+ if (taskId) {
109
+ return this._poll(taskId);
110
+ }
111
+ }
112
+ // Auto-decode: support both content_b64 and contentB64
113
+ if (result && typeof result === 'object') {
114
+ const r = result;
115
+ const b64Content = (_b = r.content_b64) !== null && _b !== void 0 ? _b : r.contentB64;
116
+ if (b64Content) {
117
+ r.content = Buffer.from(b64Content, 'base64');
118
+ delete r.content_b64;
119
+ delete r.contentB64;
469
120
  }
470
- throw new exceptions_1.FactPulseAuthError(`Failed to retrieve AFNOR credentials: ${axiosError.message}`);
471
121
  }
122
+ return result;
472
123
  });
473
124
  }
474
- /**
475
- * Obtains the AFNOR OAuth2 token and the PDP URL.
476
- * This method:
477
- * 1. Retrieves AFNOR credentials (stored or zero-trust mode)
478
- * 2. Performs AFNOR OAuth to obtain a token
479
- * 3. Returns the token and the PDP URL
480
- */
481
- getAfnorTokenAndUrl() {
482
- return __awaiter(this, void 0, void 0, function* () {
483
- // Step 1: Get AFNOR credentials
484
- const credentials = yield this.getAfnorCredentialsInternal();
485
- // Step 2: Perform AFNOR OAuth via the FactPulse proxy
486
- const oauthData = new URLSearchParams({
487
- grant_type: 'client_credentials',
488
- client_id: credentials.clientId,
489
- client_secret: credentials.clientSecret,
490
- });
491
- try {
492
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/afnor/oauth/token`, oauthData.toString(), {
493
- headers: {
494
- 'Content-Type': 'application/x-www-form-urlencoded',
495
- 'X-PDP-Token-URL': credentials.tokenUrl,
496
- },
497
- });
498
- const tokenData = response.data;
499
- if (!tokenData.access_token) {
500
- throw new exceptions_1.FactPulseAuthError('Invalid AFNOR OAuth2 response: access_token missing');
501
- }
502
- return {
503
- token: tokenData.access_token,
504
- pdpBaseUrl: credentials.flowServiceUrl,
505
- };
125
+ _parseResponse(status, data) {
126
+ if (status >= 200 && status < 300)
127
+ return data;
128
+ let msg = `HTTP ${status}`;
129
+ const details = [];
130
+ if (data && typeof data === 'object') {
131
+ const d = data;
132
+ if (Array.isArray(d.detail)) {
133
+ msg = 'Validation error: ' + d.detail.map((e) => {
134
+ var _a, _b, _c;
135
+ if (typeof e === 'object' && e !== null) {
136
+ const err = e;
137
+ details.push(e);
138
+ return `${(_b = (_a = err.loc) === null || _a === void 0 ? void 0 : _a.slice(-1)[0]) !== null && _b !== void 0 ? _b : '?'}: ${(_c = err.msg) !== null && _c !== void 0 ? _c : '?'}`;
139
+ }
140
+ return String(e);
141
+ }).join('; ');
506
142
  }
507
- catch (error) {
508
- const axiosError = error;
509
- throw new exceptions_1.FactPulseAuthError(`AFNOR OAuth2 failed: ${axiosError.message}`);
143
+ else if (typeof d.detail === 'string') {
144
+ msg = d.detail;
510
145
  }
511
- });
146
+ else if (typeof d.errorMessage === 'string') {
147
+ msg = d.errorMessage;
148
+ }
149
+ }
150
+ throw new FactPulseError(msg, status, details);
512
151
  }
513
- /**
514
- * Performs a request to the AFNOR API with auth and error handling.
515
- * IMPORTANT: This method uses the AFNOR OAuth token, NOT the FactPulse JWT!
516
- */
517
- makeAfnorRequest(method, endpoint, options) {
152
+ _poll(taskId) {
518
153
  return __awaiter(this, void 0, void 0, function* () {
519
- var _a, _b, _c, _d;
520
- // Obtenir le token AFNOR et l'URL de la PDP
521
- const { token: afnorToken, pdpBaseUrl } = yield this.getAfnorTokenAndUrl();
522
- const url = `${this.config.apiUrl}/api/v1/afnor${endpoint}`;
523
- // ALWAYS use the AFNOR token + X-PDP-Base-URL header
524
- const headers = {
525
- 'Authorization': `Bearer ${afnorToken}`,
526
- 'X-PDP-Base-URL': pdpBaseUrl,
527
- };
528
- try {
154
+ var _a, _b, _c;
155
+ const start = Date.now();
156
+ let interval = 1000;
157
+ while (true) {
158
+ const elapsed = Date.now() - start;
159
+ if (elapsed >= this.pollingTimeout) {
160
+ throw new FactPulseError(`Polling timeout after ${this.pollingTimeout}ms for task ${taskId}`);
161
+ }
162
+ yield this._ensureAuth();
529
163
  let response;
530
- if (options === null || options === void 0 ? void 0 : options.files) {
531
- response = yield this.httpClient.request({
532
- method,
533
- url,
534
- data: options.files,
535
- headers: Object.assign(Object.assign({}, options.files.getHeaders()), headers),
536
- params: options === null || options === void 0 ? void 0 : options.params,
537
- timeout: 60000,
538
- });
164
+ try {
165
+ response = yield this.httpClient.get(`${this.apiUrl}/api/v1/processing/tasks/${taskId}/status`, { headers: { Authorization: `Bearer ${this.token}` } });
539
166
  }
540
- else {
541
- response = yield this.httpClient.request({
542
- method,
543
- url,
544
- data: options === null || options === void 0 ? void 0 : options.data,
545
- headers: Object.assign(Object.assign({}, headers), { 'Content-Type': 'application/json' }),
546
- params: options === null || options === void 0 ? void 0 : options.params,
547
- timeout: 30000,
548
- });
167
+ catch (e) {
168
+ throw new FactPulseError(`Network error while polling: ${e.message}`);
549
169
  }
550
- return response.data;
551
- }
552
- catch (error) {
553
- const axiosError = error;
554
- const errorMsg = ((_b = (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.errorMessage) ||
555
- ((_d = (_c = axiosError.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.detail) ||
556
- axiosError.message;
557
- throw new exceptions_1.FactPulseValidationError(`AFNOR Error: ${errorMsg}`);
170
+ if (response.status === 401) {
171
+ this._invalidateToken();
172
+ continue;
173
+ }
174
+ const data = response.data;
175
+ const status = data.status;
176
+ if (status === 'SUCCESS') {
177
+ const result = ((_a = data.result) !== null && _a !== void 0 ? _a : {});
178
+ if (result.content_b64) {
179
+ result.content = Buffer.from(result.content_b64, 'base64');
180
+ delete result.content_b64;
181
+ }
182
+ return result;
183
+ }
184
+ if (status === 'FAILURE') {
185
+ const result = ((_b = data.result) !== null && _b !== void 0 ? _b : {});
186
+ throw new FactPulseError((_c = result.errorMessage) !== null && _c !== void 0 ? _c : 'Task failed', undefined, result.details);
187
+ }
188
+ yield new Promise(r => setTimeout(r, Math.min(interval, this.pollingTimeout - elapsed)));
189
+ interval = Math.min(interval * 1.5, 10000);
558
190
  }
559
191
  });
560
192
  }
561
- // ==================== AFNOR Directory ====================
562
- /** Gets a facility by SIRET in the AFNOR directory. */
563
- getSiretAfnor(siret) {
564
- return __awaiter(this, void 0, void 0, function* () {
565
- return this.makeAfnorRequest('GET', `/directory/v1/siret/code-insee:${siret}`);
566
- });
567
- }
568
- /** Gets a legal unit by SIREN in the AFNOR directory. */
569
- getSirenAfnor(siren) {
570
- return __awaiter(this, void 0, void 0, function* () {
571
- return this.makeAfnorRequest('GET', `/directory/v1/siren/code-insee:${siren}`);
572
- });
573
- }
574
- /** Searches for legal units (SIREN) in the AFNOR directory. */
575
- searchSirenAfnor() {
576
- return __awaiter(this, arguments, void 0, function* (options = {}) {
577
- const { filters = {}, limit = 25 } = options;
578
- return this.makeAfnorRequest('POST', '/directory/v1/siren/search', { data: { filters, limit } });
579
- });
580
- }
581
- /** Searches for routing codes in the AFNOR directory. */
582
- searchRoutingCodesAfnor() {
583
- return __awaiter(this, arguments, void 0, function* (options = {}) {
584
- const { filters = {}, limit = 25 } = options;
585
- return this.makeAfnorRequest('POST', '/directory/v1/routing-code/search', { data: { filters, limit } });
586
- });
587
- }
588
- /** Gets a routing code by SIRET and routing identifier. */
589
- getRoutingCodeAfnor(siret, routingIdentifier) {
193
+ _ensureAuth() {
590
194
  return __awaiter(this, void 0, void 0, function* () {
591
- return this.makeAfnorRequest('GET', `/directory/v1/routing-code/siret:${siret}/code:${routingIdentifier}`);
592
- });
593
- }
594
- // ==================== AFNOR Flow ====================
595
- /**
596
- * Submits an invoice to a PDP via the AFNOR API.
597
- * Authentication uses the AFNOR OAuth token (obtained automatically),
598
- * either via stored credentials (stored mode), or via the afnorCredentials
599
- * provided to the constructor (zero-trust mode).
600
- *
601
- * @param pdfBuffer Buffer of the Factur-X PDF to submit
602
- * @param flowName Flow name (e.g.: "Invoice INV-2025-001")
603
- * @param options Options: trackingId, flowSyntax (CII/UBL), flowProfile
604
- */
605
- submitInvoiceAfnor(pdfBuffer_1, flowName_1) {
606
- return __awaiter(this, arguments, void 0, function* (pdfBuffer, flowName, options = {}) {
607
- const { trackingId, flowSyntax = 'CII', flowProfile = 'EN16931' } = options;
608
- // Compute SHA-256
609
- const crypto = require('crypto');
610
- const sha256 = crypto.createHash('sha256').update(pdfBuffer).digest('hex');
611
- // Prepare flowInfo
612
- const flowInfo = { name: flowName, flowSyntax, flowProfile, sha256 };
613
- if (trackingId)
614
- flowInfo.trackingId = trackingId;
615
- const form = new form_data_1.default();
616
- form.append('file', pdfBuffer, { filename: 'facture.pdf', contentType: 'application/pdf' });
617
- form.append('flowInfo', JSON.stringify(flowInfo), { contentType: 'application/json' });
618
- return this.makeAfnorRequest('POST', '/flow/v1/flows', { files: form });
619
- });
620
- }
621
- searchFlowsAfnor() {
622
- return __awaiter(this, arguments, void 0, function* (criteria = {}) {
623
- var _a, _b;
624
- const searchBody = {
625
- offset: (_a = criteria.offset) !== null && _a !== void 0 ? _a : 0,
626
- limit: (_b = criteria.limit) !== null && _b !== void 0 ? _b : 25,
627
- where: {},
628
- };
629
- if (criteria.trackingId)
630
- searchBody.where.trackingId = criteria.trackingId;
631
- if (criteria.status)
632
- searchBody.where.status = criteria.status;
633
- return this.makeAfnorRequest('POST', '/flow/v1/flows/search', { data: searchBody });
195
+ if (Date.now() >= this.tokenExpiresAt) {
196
+ yield this._refreshToken();
197
+ }
634
198
  });
635
199
  }
636
- downloadFlowAfnor(flowId) {
200
+ _refreshToken() {
637
201
  return __awaiter(this, void 0, void 0, function* () {
638
- // For downloading, we need to handle the response type differently
639
- const { token: afnorToken, pdpBaseUrl } = yield this.getAfnorTokenAndUrl();
640
- const url = `${this.config.apiUrl}/api/v1/afnor/flow/v1/flows/${flowId}`;
641
- const response = yield this.httpClient.get(url, {
642
- headers: {
643
- 'Authorization': `Bearer ${afnorToken}`,
644
- 'X-PDP-Base-URL': pdpBaseUrl,
645
- },
646
- responseType: 'arraybuffer',
647
- });
648
- return Buffer.from(response.data);
649
- });
650
- }
651
- /**
652
- * Retrieves JSON metadata of an incoming flow (supplier invoice).
653
- * Downloads an incoming flow from the AFNOR PDP and extracts invoice
654
- * metadata into a unified JSON format. Supports Factur-X, CII and UBL.
655
- *
656
- * Note: This endpoint uses FactPulse JWT authentication (not AFNOR OAuth).
657
- * The FactPulse server handles calling the PDP with stored credentials.
658
- *
659
- * @param flowId Flow identifier (UUID)
660
- * @param includeDocument If true, includes the original document encoded in base64
661
- * @returns Invoice metadata (supplier, amounts, dates, etc.)
662
- *
663
- * @example
664
- * const invoice = await client.getIncomingInvoiceAfnor("550e8400-...");
665
- * console.log(`Supplier: ${invoice.supplier.name}`);
666
- * console.log(`Total incl. tax: ${invoice.total_incl_tax} ${invoice.currency}`);
667
- */
668
- getIncomingInvoiceAfnor(flowId_1) {
669
- return __awaiter(this, arguments, void 0, function* (flowId, includeDocument = false) {
670
- var _a, _b;
671
- yield this.ensureAuthenticated();
672
- const url = `${this.config.apiUrl}/api/v1/afnor/incoming-flows/${flowId}`;
673
- const params = {};
674
- if (includeDocument) {
675
- params.include_document = 'true';
676
- }
202
+ let response;
677
203
  try {
678
- const response = yield this.httpClient.get(url, {
679
- headers: { 'Authorization': `Bearer ${this.accessToken}` },
680
- params: Object.keys(params).length > 0 ? params : undefined,
681
- timeout: 60000,
204
+ response = yield this.httpClient.post(`${this.apiUrl}/api/token/`, {
205
+ username: this.email,
206
+ password: this.password,
207
+ client_uid: this.clientUid,
682
208
  });
683
- return response.data;
684
209
  }
685
- catch (error) {
686
- const axiosError = error;
687
- throw new exceptions_1.FactPulseValidationError(`Incoming flow error: ${((_b = (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.detail) || axiosError.message}`);
210
+ catch (e) {
211
+ throw new FactPulseError(`Auth network error: ${e.message}`);
688
212
  }
689
- });
690
- }
691
- healthcheckAfnor() {
692
- return __awaiter(this, void 0, void 0, function* () {
693
- return this.makeAfnorRequest('GET', '/flow/v1/healthcheck');
694
- });
695
- }
696
- // ==================== Chorus Pro ====================
697
- rechercherStructureChorus(criteria) {
698
- return __awaiter(this, void 0, void 0, function* () {
699
- yield this.ensureAuthenticated();
700
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus-pro/structures/rechercher`, criteria, {
701
- headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
702
- });
703
- return response.data;
704
- });
705
- }
706
- consulterStructureChorus(idStructureCpp) {
707
- return __awaiter(this, void 0, void 0, function* () {
708
- yield this.ensureAuthenticated();
709
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus-pro/structures/consulter`, { id_structure_cpp: idStructureCpp }, {
710
- headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
711
- });
712
- return response.data;
713
- });
714
- }
715
- /**
716
- * Lists the services of a Chorus Pro structure.
717
- * @param idStructureCpp Chorus Pro ID of the structure
718
- * @returns Object with listeServices, total, codeRetour, libelle
719
- */
720
- listerServicesStructureChorus(idStructureCpp) {
721
- return __awaiter(this, void 0, void 0, function* () {
722
- yield this.ensureAuthenticated();
723
- const response = yield this.httpClient.get(`${this.config.apiUrl}/api/v1/chorus-pro/structures/${idStructureCpp}/services`, {
724
- headers: { Authorization: `Bearer ${this.accessToken}` },
725
- });
726
- return response.data;
727
- });
728
- }
729
- obtenirIdChorusDepuisSiret(siret) {
730
- return __awaiter(this, void 0, void 0, function* () {
731
- const results = yield this.rechercherStructureChorus({ identifiant_structure: siret, type_identifiant: 'SIRET' });
732
- if (results.length > 0 && results[0].id_structure_cpp)
733
- return results[0].id_structure_cpp;
734
- return null;
735
- });
736
- }
737
- submitInvoiceChorus(invoiceData) {
738
- return __awaiter(this, void 0, void 0, function* () {
739
- yield this.ensureAuthenticated();
740
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus/factures/soumettre`, invoiceData, {
741
- headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
742
- });
743
- return response.data;
744
- });
745
- }
746
- lookupInvoiceChorus(invoiceIdCpp) {
747
- return __awaiter(this, void 0, void 0, function* () {
748
- yield this.ensureAuthenticated();
749
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/chorus/factures/consulter`, { identifiant_facture_cpp: invoiceIdCpp }, {
750
- headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
751
- });
752
- return response.data;
753
- });
754
- }
755
- // ==================== Validation ====================
756
- /**
757
- * Validates a Factur-X PDF.
758
- * @param pdfBuffer - PDF content as Buffer
759
- * @param options - Validation options
760
- * @param options.profile - Factur-X profile (MINIMUM, BASIC, EN16931, EXTENDED). If not specified, auto-detected.
761
- * @param options.useVerapdf - Enable strict PDF/A validation with VeraPDF (default: false)
762
- */
763
- validateFacturxPdf(pdfBuffer_1) {
764
- return __awaiter(this, arguments, void 0, function* (pdfBuffer, options = {}) {
765
- var _a;
766
- yield this.ensureAuthenticated();
767
- const form = new form_data_1.default();
768
- form.append('pdf_file', pdfBuffer, { filename: 'facture.pdf', contentType: 'application/pdf' });
769
- if (options.profile) {
770
- form.append('profile', options.profile);
213
+ if (response.status !== 200) {
214
+ throw new FactPulseError(`Authentication failed: HTTP ${response.status}`, response.status);
215
+ }
216
+ const data = response.data;
217
+ if (!data.access) {
218
+ throw new FactPulseError('Invalid auth response');
771
219
  }
772
- form.append('use_verapdf', String((_a = options.useVerapdf) !== null && _a !== void 0 ? _a : false));
773
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/processing/validate-facturx-pdf`, form, {
774
- headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: `Bearer ${this.accessToken}` }),
775
- });
776
- return response.data;
220
+ this.token = data.access;
221
+ this.tokenExpiresAt = Date.now() + 28 * 60 * 1000;
777
222
  });
778
223
  }
779
- validateFacturxXml(xmlContent_1) {
780
- return __awaiter(this, arguments, void 0, function* (xmlContent, profile = 'EN16931') {
781
- yield this.ensureAuthenticated();
782
- const form = new form_data_1.default();
783
- form.append('xml_file', Buffer.from(xmlContent, 'utf-8'), { filename: 'facture.xml', contentType: 'application/xml' });
784
- form.append('profile', profile);
785
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/processing/validate-xml`, form, {
786
- headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: `Bearer ${this.accessToken}` }),
787
- });
788
- return response.data;
789
- });
224
+ _invalidateToken() {
225
+ this.tokenExpiresAt = 0;
790
226
  }
791
- validatePdfSignature(pdfBuffer) {
227
+ }
228
+ exports.FactPulseClient = FactPulseClient;
229
+ class Endpoint {
230
+ constructor(client, path) {
231
+ this.client = client;
232
+ this.path = path;
233
+ }
234
+ // Chain path segments: client.processing.invoices → processing/invoices
235
+ _child(segment) {
236
+ return new Endpoint(this.client, `${this.path}/${segment.replace(/_/g, '-')}`);
237
+ }
238
+ // Dynamic property access for path building
239
+ get invoices() { return this._child('invoices'); }
240
+ get structures() { return this._child('structures'); }
241
+ get tasks() { return this._child('tasks'); }
242
+ get validate_facturx_pdf() { return this._child('validate-facturx-pdf'); }
243
+ get submit_complete_async() { return this._child('submit-complete-async'); }
244
+ // POST request
245
+ call(data) {
792
246
  return __awaiter(this, void 0, void 0, function* () {
793
- yield this.ensureAuthenticated();
794
- const form = new form_data_1.default();
795
- form.append('pdf_file', pdfBuffer, { filename: 'document.pdf', contentType: 'application/pdf' });
796
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/processing/validate-pdf-signature`, form, {
797
- headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: `Bearer ${this.accessToken}` }),
798
- });
799
- return response.data;
800
- });
801
- }
802
- // ==================== Signature ====================
803
- /**
804
- * Signs a PDF with the server-configured certificate (via JWT client_uid).
805
- * The certificate must be previously configured in Django Admin.
806
- */
807
- signPdf(pdfBuffer_1) {
808
- return __awaiter(this, arguments, void 0, function* (pdfBuffer, options = {}) {
809
- yield this.ensureAuthenticated();
810
- const form = new form_data_1.default();
811
- form.append('pdf_file', pdfBuffer, { filename: 'document.pdf', contentType: 'application/pdf' });
812
- if (options.reason)
813
- form.append('reason', options.reason);
814
- if (options.location)
815
- form.append('location', options.location);
816
- if (options.contact)
817
- form.append('contact', options.contact);
818
- if (options.usePadesLt !== undefined)
819
- form.append('use_pades_lt', String(options.usePadesLt));
820
- if (options.useTimestamp !== undefined)
821
- form.append('use_timestamp', String(options.useTimestamp));
822
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/processing/sign-pdf`, form, {
823
- headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: `Bearer ${this.accessToken}` }),
824
- });
825
- // The API returns JSON with pdf_signe_base64
826
- const data = response.data;
827
- if (data.pdf_signe_base64) {
828
- return Buffer.from(data.pdf_signe_base64, 'base64');
829
- }
830
- throw new Error('Invalid signature response');
247
+ return this.client._request('POST', this.path, data);
831
248
  });
832
249
  }
833
- /**
834
- * Generates a test certificate (NOT FOR PRODUCTION).
835
- * The certificate must then be configured in Django Admin.
836
- */
837
- generateTestCertificate() {
838
- return __awaiter(this, arguments, void 0, function* (options = {}) {
839
- yield this.ensureAuthenticated();
840
- const response = yield this.httpClient.post(`${this.config.apiUrl}/api/v1/processing/generate-test-certificate`, {
841
- cn: options.cn || 'Test Organisation',
842
- organisation: options.organisation || 'Test Organisation',
843
- email: options.email || 'test@example.com',
844
- validity_days: options.validityDays || 365,
845
- key_size: options.keySize || 2048,
846
- }, {
847
- headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' },
848
- });
849
- return response.data;
250
+ // GET request
251
+ get(params) {
252
+ return __awaiter(this, void 0, void 0, function* () {
253
+ return this.client._request('GET', this.path, params);
850
254
  });
851
255
  }
852
- // ==================== Workflow complet ====================
853
- /**
854
- * Complete workflow: generation + validation + signature + AFNOR submission.
855
- * Note: Signature uses the server-configured certificate (via JWT client_uid).
856
- * @param invoiceData Invoice data
857
- * @param pdfPath Path to the source PDF
858
- * @param options Workflow options
859
- * @returns Result with pdfBuffer, validation, signature and afnor
860
- */
861
- generateCompleteFacturx(invoiceData_1, pdfPath_1) {
862
- return __awaiter(this, arguments, void 0, function* (invoiceData, pdfPath, options = {}) {
863
- const { profile = 'EN16931', validate = true, sign = false, submitAfnor = false, afnorFlowName, afnorTrackingId, timeout, } = options;
864
- const result = { pdfBuffer: Buffer.alloc(0) };
865
- // 1. Generation
866
- const pdfBuffer = yield this.generateFacturx(invoiceData, pdfPath, profile, 'pdf', true, timeout);
867
- result.pdfBuffer = pdfBuffer;
868
- // 2. Validation
869
- if (validate) {
870
- const validation = yield this.validateFacturxPdf(pdfBuffer, { profile });
871
- result.validation = validation;
872
- if (!validation.isCompliant) {
873
- return result;
256
+ }
257
+ // Proxy to allow dynamic path segments
258
+ const EndpointProxy = new Proxy(Endpoint, {
259
+ construct(target, args) {
260
+ const instance = new target(args[0], args[1]);
261
+ return new Proxy(instance, {
262
+ get(obj, prop) {
263
+ if (typeof prop === 'string' && !prop.startsWith('_') && !(prop in obj)) {
264
+ return new EndpointProxy(obj['client'], `${obj['path']}/${prop.replace(/_/g, '-')}`);
874
265
  }
266
+ return obj[prop];
875
267
  }
876
- // 3. Signature (uses the server-configured certificate)
877
- if (sign) {
878
- const signedPdf = yield this.signPdf(result.pdfBuffer);
879
- result.pdfBuffer = signedPdf;
880
- result.signature = { signed: true };
881
- }
882
- // 4. AFNOR submission
883
- if (submitAfnor) {
884
- const invoiceNumber = (invoiceData.invoiceNumber || invoiceData.invoice_number || 'INVOICE');
885
- const flowName = afnorFlowName || `Invoice ${invoiceNumber}`;
886
- const trackingId = afnorTrackingId || invoiceNumber;
887
- const afnorResult = yield this.submitInvoiceAfnor(result.pdfBuffer, flowName, { trackingId });
888
- result.afnor = afnorResult;
889
- }
890
- return result;
891
268
  });
892
269
  }
893
- }
894
- exports.FactPulseClient = FactPulseClient;
270
+ });