@rovela-ai/sdk 0.19.7 → 0.20.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 (96) hide show
  1. package/dist/admin/api/index.d.ts +2 -2
  2. package/dist/admin/api/index.d.ts.map +1 -1
  3. package/dist/admin/api/index.js +2 -2
  4. package/dist/admin/api/index.js.map +1 -1
  5. package/dist/admin/api/orders.d.ts +49 -0
  6. package/dist/admin/api/orders.d.ts.map +1 -1
  7. package/dist/admin/api/orders.js +293 -30
  8. package/dist/admin/api/orders.js.map +1 -1
  9. package/dist/admin/api/settings.d.ts.map +1 -1
  10. package/dist/admin/api/settings.js +6 -1
  11. package/dist/admin/api/settings.js.map +1 -1
  12. package/dist/admin/api/shipping.d.ts +49 -0
  13. package/dist/admin/api/shipping.d.ts.map +1 -1
  14. package/dist/admin/api/shipping.js +144 -0
  15. package/dist/admin/api/shipping.js.map +1 -1
  16. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  17. package/dist/admin/components/OrderDetails.js +69 -1
  18. package/dist/admin/components/OrderDetails.js.map +1 -1
  19. package/dist/admin/components/ShippingSettings.d.ts.map +1 -1
  20. package/dist/admin/components/ShippingSettings.js +313 -134
  21. package/dist/admin/components/ShippingSettings.js.map +1 -1
  22. package/dist/admin/components/UsersTable.d.ts.map +1 -1
  23. package/dist/admin/components/UsersTable.js +65 -8
  24. package/dist/admin/components/UsersTable.js.map +1 -1
  25. package/dist/admin/types.d.ts +13 -0
  26. package/dist/admin/types.d.ts.map +1 -1
  27. package/dist/checkout/api/colissimo-relays.d.ts +24 -0
  28. package/dist/checkout/api/colissimo-relays.d.ts.map +1 -0
  29. package/dist/checkout/api/colissimo-relays.js +60 -0
  30. package/dist/checkout/api/colissimo-relays.js.map +1 -0
  31. package/dist/checkout/api/manual.d.ts.map +1 -1
  32. package/dist/checkout/api/manual.js +21 -5
  33. package/dist/checkout/api/manual.js.map +1 -1
  34. package/dist/checkout/api/shipping.d.ts.map +1 -1
  35. package/dist/checkout/api/shipping.js +60 -82
  36. package/dist/checkout/api/shipping.js.map +1 -1
  37. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  38. package/dist/checkout/components/CheckoutFlow.js +142 -3
  39. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  40. package/dist/checkout/components/ShippingOptions.d.ts +8 -1
  41. package/dist/checkout/components/ShippingOptions.d.ts.map +1 -1
  42. package/dist/checkout/components/ShippingOptions.js +40 -11
  43. package/dist/checkout/components/ShippingOptions.js.map +1 -1
  44. package/dist/checkout/components/colissimo-logo.d.ts +12 -0
  45. package/dist/checkout/components/colissimo-logo.d.ts.map +1 -0
  46. package/dist/checkout/components/colissimo-logo.js +12 -0
  47. package/dist/checkout/components/colissimo-logo.js.map +1 -0
  48. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  49. package/dist/checkout/server/create-checkout-session.js +55 -13
  50. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  51. package/dist/checkout/server/handle-webhook.js +7 -3
  52. package/dist/checkout/server/handle-webhook.js.map +1 -1
  53. package/dist/checkout/server/order-service.d.ts.map +1 -1
  54. package/dist/checkout/server/order-service.js +25 -0
  55. package/dist/checkout/server/order-service.js.map +1 -1
  56. package/dist/checkout/server/pricing.d.ts +20 -0
  57. package/dist/checkout/server/pricing.d.ts.map +1 -1
  58. package/dist/checkout/server/pricing.js +51 -5
  59. package/dist/checkout/server/pricing.js.map +1 -1
  60. package/dist/checkout/types.d.ts +57 -1
  61. package/dist/checkout/types.d.ts.map +1 -1
  62. package/dist/core/db/queries.d.ts +157 -35
  63. package/dist/core/db/queries.d.ts.map +1 -1
  64. package/dist/core/db/queries.js +223 -1
  65. package/dist/core/db/queries.js.map +1 -1
  66. package/dist/core/db/schema.d.ts +323 -5
  67. package/dist/core/db/schema.d.ts.map +1 -1
  68. package/dist/core/db/schema.js +70 -0
  69. package/dist/core/db/schema.js.map +1 -1
  70. package/dist/core/i18n/messages/en.d.ts.map +1 -1
  71. package/dist/core/i18n/messages/en.js +9 -0
  72. package/dist/core/i18n/messages/en.js.map +1 -1
  73. package/dist/core/i18n/messages/fr.d.ts.map +1 -1
  74. package/dist/core/i18n/messages/fr.js +9 -0
  75. package/dist/core/i18n/messages/fr.js.map +1 -1
  76. package/dist/shipping/colissimo.d.ts +166 -0
  77. package/dist/shipping/colissimo.d.ts.map +1 -0
  78. package/dist/shipping/colissimo.js +424 -0
  79. package/dist/shipping/colissimo.js.map +1 -0
  80. package/dist/shipping/index.d.ts +4 -1
  81. package/dist/shipping/index.d.ts.map +1 -1
  82. package/dist/shipping/index.js +3 -1
  83. package/dist/shipping/index.js.map +1 -1
  84. package/docs/changelog/epoch-02-platform-commerce.md +11 -0
  85. package/docs/changelog/epoch-06-mobile-integrity.md +29 -1
  86. package/docs/changelog/epoch-07-shipping-carriers.md +125 -0
  87. package/package.json +6 -1
  88. package/templates/store-template/.claude/skills/build-feature/SKILL.md +2 -0
  89. package/templates/store-template/.claude/skills/manage-products/SKILL.md +1 -1
  90. package/templates/store-template/.claude/skills/override-sdk-component/SKILL.md +7 -0
  91. package/templates/store-template/app/api/admin/orders/[id]/colissimo-defaults/route.ts +6 -0
  92. package/templates/store-template/app/api/admin/orders/[id]/label/route.ts +6 -0
  93. package/templates/store-template/app/api/admin/orders/[id]/ship-with-colissimo/route.ts +6 -0
  94. package/templates/store-template/app/api/admin/shipping/colissimo/route.ts +10 -0
  95. package/templates/store-template/app/api/checkout/colissimo-relays/route.ts +4 -0
  96. package/templates/store-template/package.json +1 -1
@@ -0,0 +1,424 @@
1
+ /**
2
+ * @rovela/sdk/shipping/colissimo
3
+ *
4
+ * La Poste Colissimo integration (France-first carrier):
5
+ * - SLS (Simple Label Solution) label generation — home delivery (DOM/DOS)
6
+ * and pickup-point relay delivery (A2P/BPR).
7
+ * - Point Retrait pickup-point search (findRDVPointRetraitAcheminement).
8
+ *
9
+ * Uses direct fetch calls — no SDK dependency. Credentials (contract number +
10
+ * password) are store-level secrets read server-side from store_settings;
11
+ * they are BODY-borne on Colissimo's APIs and must never reach the client.
12
+ *
13
+ * Load-bearing protocol notes (verified against the official SLS spec):
14
+ * - generateLabel responds with `multipart/mixed` (MTOM-style): a JSON part
15
+ * (Content-ID <jsonInfos>) + the raw label bytes (Content-ID <label>).
16
+ * The body MUST be read as a Buffer (label bytes are binary) and the JSON
17
+ * part must be parsed EVEN ON HTTP 4xx — Colissimo returns its error
18
+ * catalog inside the same multipart wrapper.
19
+ * - Colissimo has NO rate-quoting API: checkout prices stay on the store's
20
+ * manual zone rates; this module only does labels + pickup points.
21
+ * - There is no sandbox: `checkGenerateLabel` validates without creating a
22
+ * parcel, and real labels are free until physically scanned.
23
+ * - Parcel-number ranges recycle after ~18 months — the order reference is
24
+ * always stamped on the label (senderParcelRef/orderNumber) and the order
25
+ * row remains the durable key.
26
+ * - Point Retrait: empty-valued params break the call — omit unused params
27
+ * entirely. `shippingDate` is dd/MM/yyyy there while SLS `depositDate` is
28
+ * yyyy-MM-dd.
29
+ *
30
+ * API docs: https://www.colissimo.entreprise.laposte.fr/en/system-integration
31
+ */
32
+ import { findSettings } from '../core/db/queries';
33
+ const SLS_BASE_URL = 'https://ws.colissimo.fr/sls-ws/SlsServiceWSRest/2.0';
34
+ const PDR_BASE_URL = 'https://ws.colissimo.fr/pointretrait-ws-cxf/rest/v2/pointretrait';
35
+ /** Thrown when Colissimo returns an ERROR message or the call fails. */
36
+ export class ColissimoError extends Error {
37
+ messages;
38
+ constructor(message, messages = []) {
39
+ super(message);
40
+ this.name = 'ColissimoError';
41
+ this.messages = messages;
42
+ }
43
+ }
44
+ // =============================================================================
45
+ // Credentials
46
+ // =============================================================================
47
+ /**
48
+ * Get Colissimo credentials from store settings (DB only — the contract is
49
+ * merchant-specific, there is no platform fallback).
50
+ */
51
+ export async function getColissimoCredentials() {
52
+ const settings = await findSettings().catch(() => null);
53
+ const contractNumber = settings?.colissimoContractNumber;
54
+ const password = settings?.colissimoPassword;
55
+ if (!contractNumber || !password) {
56
+ throw new ColissimoError('Colissimo is not configured. Add your contract number and password in Settings > Shipping.');
57
+ }
58
+ return { contractNumber, password };
59
+ }
60
+ // =============================================================================
61
+ // Error catalog — friendly messages for the codes merchants actually hit
62
+ // =============================================================================
63
+ const FRIENDLY_ERRORS = {
64
+ '30000': 'Invalid Colissimo contract number or password.',
65
+ '30001': 'Invalid Colissimo contract number or password.',
66
+ '30007': 'This Colissimo account is inactive. Contact Colissimo support.',
67
+ '30015': 'This shipping product is not valid for this parcel.',
68
+ '30109': 'The recipient mobile number is missing or invalid (required for pickup-point delivery).',
69
+ '30220': 'The recipient phone number is required for this destination.',
70
+ '30300': 'The parcel weight is missing.',
71
+ '30301': 'The parcel weight is invalid (0.01–30 kg, 2 decimals).',
72
+ '30700': 'This shipping product is not enabled on your Colissimo contract.',
73
+ '30008': 'This shipping product is not enabled on your Colissimo contract.',
74
+ '30009': 'This shipping product is not enabled on your Colissimo contract.',
75
+ '30002': 'The deposit date must be today or later.',
76
+ };
77
+ function friendlyError(messages) {
78
+ const firstError = messages.find((m) => m.type === 'ERROR');
79
+ if (!firstError)
80
+ return 'Colissimo request failed';
81
+ const friendly = FRIENDLY_ERRORS[firstError.id];
82
+ return friendly
83
+ ? `${friendly} (Colissimo ${firstError.id})`
84
+ : `Colissimo error ${firstError.id}: ${firstError.messageContent}`;
85
+ }
86
+ /**
87
+ * Parse an SLS response body. Handles both `multipart/mixed` (success — JSON
88
+ * infos part + binary label part) and plain JSON (checkGenerateLabel, some
89
+ * errors). Exported for tests.
90
+ *
91
+ * Buffer↔latin1 round-trips are byte-exact (1 char = 1 byte), so the parts
92
+ * are located on a latin1 view and the label bytes recovered losslessly.
93
+ */
94
+ export function parseSlsResponse(body, contentType) {
95
+ const result = { messages: [], parcelNumber: null, labelBytes: null };
96
+ const readJsonPart = (jsonText) => {
97
+ try {
98
+ const parsed = JSON.parse(jsonText);
99
+ result.messages = parsed.messages ?? [];
100
+ result.parcelNumber = parsed.labelV2Response?.parcelNumber ?? null;
101
+ }
102
+ catch {
103
+ // Leave messages empty — the caller surfaces a generic error.
104
+ }
105
+ };
106
+ const boundaryMatch = /boundary="?([^";]+)"?/i.exec(contentType);
107
+ if (!boundaryMatch) {
108
+ readJsonPart(body.toString('utf8'));
109
+ return result;
110
+ }
111
+ const text = body.toString('latin1');
112
+ const parts = text.split(`--${boundaryMatch[1]}`);
113
+ for (const part of parts) {
114
+ const headerEnd = part.indexOf('\r\n\r\n');
115
+ if (headerEnd === -1)
116
+ continue;
117
+ const headers = part.slice(0, headerEnd).toLowerCase();
118
+ let content = part.slice(headerEnd + 4);
119
+ // Strip the trailing CRLF that precedes the next boundary marker.
120
+ if (content.endsWith('\r\n'))
121
+ content = content.slice(0, -2);
122
+ if (headers.includes('application/json') || headers.includes('<jsoninfos>')) {
123
+ // Reinterpret as UTF-8 (French accented messages).
124
+ readJsonPart(Buffer.from(content, 'latin1').toString('utf8'));
125
+ }
126
+ else if (headers.includes('<label>') || headers.includes('octet-stream')) {
127
+ result.labelBytes = Buffer.from(content, 'latin1');
128
+ }
129
+ }
130
+ return result;
131
+ }
132
+ // =============================================================================
133
+ // SLS letter construction
134
+ // =============================================================================
135
+ function buildLetter(params) {
136
+ const { credentials, productCode, weightKg, orderRef, commercialName, sender, addressee, pickupLocationId, format, } = params;
137
+ const isRelay = productCode === 'A2P' || productCode === 'BPR';
138
+ const today = new Date();
139
+ const depositDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
140
+ // Omit optional fields entirely rather than sending them empty.
141
+ const compact = (obj) => {
142
+ const out = {};
143
+ for (const [k, v] of Object.entries(obj)) {
144
+ if (v !== undefined && v !== null && v !== '')
145
+ out[k] = v;
146
+ }
147
+ return out;
148
+ };
149
+ return {
150
+ contractNumber: credentials.contractNumber,
151
+ password: credentials.password,
152
+ outputFormat: { x: 0, y: 0, outputPrintingType: format },
153
+ letter: {
154
+ service: compact({
155
+ productCode,
156
+ depositDate,
157
+ orderNumber: orderRef,
158
+ // Shown in customer SMS/email notifications — mandatory for relay.
159
+ commercialName,
160
+ }),
161
+ parcel: compact({
162
+ // 2 decimals max per the SLS spec.
163
+ weight: Math.round(weightKg * 100) / 100,
164
+ // Must be present for relay products and ABSENT for home delivery.
165
+ pickupLocationId: isRelay ? pickupLocationId : undefined,
166
+ }),
167
+ sender: {
168
+ senderParcelRef: orderRef,
169
+ address: compact({
170
+ companyName: sender.companyName,
171
+ line2: sender.line2,
172
+ line3: sender.line3,
173
+ zipCode: sender.zipCode,
174
+ city: sender.city,
175
+ countryCode: sender.countryCode,
176
+ email: sender.email,
177
+ phoneNumber: sender.phoneNumber,
178
+ }),
179
+ },
180
+ addressee: {
181
+ addresseeParcelRef: orderRef,
182
+ address: compact({
183
+ firstName: addressee.firstName,
184
+ lastName: addressee.lastName,
185
+ line2: addressee.line2,
186
+ line3: addressee.line3,
187
+ zipCode: addressee.zipCode,
188
+ city: addressee.city,
189
+ countryCode: addressee.countryCode,
190
+ phoneNumber: addressee.phoneNumber,
191
+ mobileNumber: addressee.mobileNumber,
192
+ email: addressee.email,
193
+ }),
194
+ },
195
+ },
196
+ };
197
+ }
198
+ // =============================================================================
199
+ // Public API — validation
200
+ // =============================================================================
201
+ /**
202
+ * Validate Colissimo credentials via `checkGenerateLabel` (pure validation —
203
+ * no parcel is created, nothing is billed). Never throws.
204
+ */
205
+ export async function validateColissimoCredentials(contractNumber, password) {
206
+ try {
207
+ const body = buildLetter({
208
+ credentials: { contractNumber, password },
209
+ check: true,
210
+ productCode: 'DOM',
211
+ weightKg: 1,
212
+ orderRef: 'ROVELA-VALIDATE',
213
+ commercialName: 'Validation',
214
+ sender: {
215
+ companyName: 'Validation', line2: '10 rue de Rivoli',
216
+ zipCode: '75004', city: 'Paris', countryCode: 'FR',
217
+ },
218
+ addressee: {
219
+ firstName: 'Jean', lastName: 'Dupont', line2: '5 avenue Jean Jaures',
220
+ zipCode: '69007', city: 'Lyon', countryCode: 'FR', mobileNumber: '0612345678',
221
+ },
222
+ format: 'PDF_A4_300dpi',
223
+ });
224
+ const response = await fetch(`${SLS_BASE_URL}/checkGenerateLabel`, {
225
+ method: 'POST',
226
+ headers: { 'Content-Type': 'application/json' },
227
+ body: JSON.stringify(body),
228
+ });
229
+ const parsed = parseSlsResponse(Buffer.from(await response.arrayBuffer()), response.headers.get('content-type') || '');
230
+ const hasError = parsed.messages.some((m) => m.type === 'ERROR');
231
+ if (!hasError && response.ok)
232
+ return { valid: true };
233
+ return { valid: false, error: friendlyError(parsed.messages) };
234
+ }
235
+ catch (error) {
236
+ return {
237
+ valid: false,
238
+ error: error instanceof Error ? error.message : 'Network error contacting Colissimo',
239
+ };
240
+ }
241
+ }
242
+ // =============================================================================
243
+ // Public API — label generation
244
+ // =============================================================================
245
+ /**
246
+ * Generate a Colissimo shipping label (SLS `generateLabel`).
247
+ *
248
+ * IMPORTANT: every successful call creates a REAL parcel number on the
249
+ * merchant's contract (free until physically scanned). Labels cannot be
250
+ * re-downloaded from Colissimo — the caller MUST persist `labelBytes`.
251
+ */
252
+ export async function generateColissimoLabel(params) {
253
+ const credentials = await getColissimoCredentials();
254
+ const format = params.format || 'PDF_A4_300dpi';
255
+ const isRelay = params.productCode === 'A2P' || params.productCode === 'BPR';
256
+ if (isRelay && !params.pickupLocationId) {
257
+ throw new ColissimoError('A pickup point is required for relay delivery.');
258
+ }
259
+ if (isRelay && !params.addressee.mobileNumber) {
260
+ throw new ColissimoError('A French mobile number is required for pickup-point delivery (Colissimo sends the customer an SMS).');
261
+ }
262
+ if (!(params.weightKg >= 0.01 && params.weightKg <= 30)) {
263
+ throw new ColissimoError(`Parcel weight must be between 0.01 and 30 kg (got ${params.weightKg} kg). Split heavier orders into multiple parcels.`);
264
+ }
265
+ const body = buildLetter({ credentials, check: false, format, ...params });
266
+ const response = await fetch(`${SLS_BASE_URL}/generateLabel`, {
267
+ method: 'POST',
268
+ headers: { 'Content-Type': 'application/json' },
269
+ body: JSON.stringify(body),
270
+ });
271
+ // Parse the body even on 4xx — the error catalog rides the same wrapper.
272
+ const parsed = parseSlsResponse(Buffer.from(await response.arrayBuffer()), response.headers.get('content-type') || '');
273
+ const hasError = parsed.messages.some((m) => m.type === 'ERROR');
274
+ if (hasError || !response.ok) {
275
+ throw new ColissimoError(friendlyError(parsed.messages), parsed.messages);
276
+ }
277
+ if (!parsed.parcelNumber || !parsed.labelBytes || parsed.labelBytes.length === 0) {
278
+ throw new ColissimoError('Colissimo returned no label. Please try again.', parsed.messages);
279
+ }
280
+ return {
281
+ parcelNumber: parsed.parcelNumber,
282
+ labelBytes: parsed.labelBytes,
283
+ format,
284
+ warnings: parsed.messages.filter((m) => m.type !== 'ERROR'),
285
+ };
286
+ }
287
+ /** Public tracking page for a Colissimo parcel (no API key needed). */
288
+ export function colissimoTrackingUrl(parcelNumber) {
289
+ return `https://www.laposte.fr/outils/suivre-vos-envois?code=${encodeURIComponent(parcelNumber)}`;
290
+ }
291
+ function toRelayPoint(raw) {
292
+ const closed = (h) => !h || h === '00:00-00:00';
293
+ const hours = [
294
+ raw.horairesOuvertureLundi,
295
+ raw.horairesOuvertureMardi,
296
+ raw.horairesOuvertureMercredi,
297
+ raw.horairesOuvertureJeudi,
298
+ raw.horairesOuvertureVendredi,
299
+ raw.horairesOuvertureSamedi,
300
+ raw.horairesOuvertureDimanche,
301
+ ].map((h) => (closed(h) ? null : h));
302
+ return {
303
+ id: raw.identifiant,
304
+ name: raw.nom,
305
+ type: raw.typeDePoint,
306
+ line1: raw.adresse1 || '',
307
+ line2: [raw.adresse2, raw.adresse3].filter(Boolean).join(' ') || null,
308
+ postalCode: raw.codePostal || '',
309
+ city: raw.localite || '',
310
+ country: (raw.codePays || 'FR').toUpperCase(),
311
+ distanceMeters: typeof raw.distanceEnMetre === 'number' ? raw.distanceEnMetre : null,
312
+ latitude: raw.coordGeolocalisationLatitude || null,
313
+ longitude: raw.coordGeolocalisationLongitude || null,
314
+ maxWeightGrams: typeof raw.poidsMaxi === 'number' ? raw.poidsMaxi : null,
315
+ /** Mon..Sun; null = closed that day. */
316
+ openingHours: hours,
317
+ };
318
+ }
319
+ /** Relay point types we support in v1 (map cleanly to A2P/BPR products). */
320
+ const SUPPORTED_RELAY_TYPES = new Set(['A2P', 'BPR', 'CDI', 'ACP', 'CMT', 'PCS', 'BDP']);
321
+ /** Map a relay point's type to the SLS product code for label generation. */
322
+ export function relayTypeToProductCode(type) {
323
+ // Post-office family (bureau de poste / centre de distribution / agence).
324
+ if (type === 'BPR' || type === 'CDI' || type === 'ACP')
325
+ return 'BPR';
326
+ // Merchant relays + lockers.
327
+ return 'A2P';
328
+ }
329
+ /**
330
+ * Search pickup points around an address (findRDVPointRetraitAcheminement).
331
+ * Weight is passed in GRAMS (points enforce per-point maximums).
332
+ */
333
+ export async function searchRelayPoints(params) {
334
+ const credentials = await getColissimoCredentials();
335
+ const d = new Date(Date.now() + 24 * 3600 * 1000);
336
+ const shippingDate = `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;
337
+ // Empty-valued params break the call — build the body without them.
338
+ const body = {
339
+ accountNumber: credentials.contractNumber,
340
+ password: credentials.password,
341
+ zipCode: params.zipCode,
342
+ city: params.city,
343
+ countryCode: (params.countryCode || 'FR').toUpperCase(),
344
+ weight: String(Math.max(1, Math.round(params.weightGrams ?? 1000))),
345
+ shippingDate,
346
+ filterRelay: '1',
347
+ requestId: `rovela-${Date.now()}`,
348
+ lang: 'FR',
349
+ };
350
+ if (params.address)
351
+ body.address = params.address;
352
+ const response = await fetch(`${PDR_BASE_URL}/findRDVPointRetraitAcheminement`, {
353
+ method: 'POST',
354
+ headers: { 'Content-Type': 'application/json' },
355
+ body: JSON.stringify(body),
356
+ });
357
+ if (!response.ok) {
358
+ throw new ColissimoError(`Colissimo pickup-point search failed (${response.status})`);
359
+ }
360
+ const data = (await response.json());
361
+ if (data.errorCode && data.errorCode !== 0) {
362
+ throw new ColissimoError(`Colissimo pickup-point search failed: ${data.errorMessage || data.errorCode}`);
363
+ }
364
+ return (data.listePointRetraitAcheminement ?? [])
365
+ .filter((p) => p?.identifiant && SUPPORTED_RELAY_TYPES.has(p.typeDePoint))
366
+ .map(toRelayPoint);
367
+ }
368
+ /**
369
+ * Fetch one pickup point by id (findPointRetraitAcheminementByID) — the
370
+ * server-side re-verification used by the pricing core at quote time.
371
+ * Returns null when the point is unknown/unavailable.
372
+ */
373
+ export async function getRelayPointById(id) {
374
+ try {
375
+ const credentials = await getColissimoCredentials();
376
+ const d = new Date(Date.now() + 24 * 3600 * 1000);
377
+ const date = `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;
378
+ // NOTE: this endpoint REQUIRES `date` (error 106 without it) and takes
379
+ // `langue` — its sibling search endpoint takes `lang`; `lang` here → 500.
380
+ const body = {
381
+ accountNumber: credentials.contractNumber,
382
+ password: credentials.password,
383
+ id,
384
+ date,
385
+ langue: 'FR',
386
+ };
387
+ const response = await fetch(`${PDR_BASE_URL}/findPointRetraitAcheminementByID`, {
388
+ method: 'POST',
389
+ headers: { 'Content-Type': 'application/json' },
390
+ body: JSON.stringify(body),
391
+ });
392
+ if (!response.ok)
393
+ return null;
394
+ const data = (await response.json());
395
+ if (data.errorCode && data.errorCode !== 0)
396
+ return null;
397
+ const raw = data.pointRetraitAcheminement;
398
+ if (!raw?.identifiant)
399
+ return null;
400
+ return toRelayPoint(raw);
401
+ }
402
+ catch {
403
+ return null;
404
+ }
405
+ }
406
+ // =============================================================================
407
+ // Helpers shared with the admin fulfillment surface
408
+ // =============================================================================
409
+ /** French mobile check (Colissimo requires 06/07 mobiles for relay SMS). */
410
+ export function isFrenchMobile(phone) {
411
+ if (!phone)
412
+ return false;
413
+ const digits = phone.replace(/[\s.\-()]/g, '');
414
+ return /^(?:\+33|0033|0)[67]\d{8}$/.test(digits);
415
+ }
416
+ /** Normalize a French mobile to the 10-digit national form Colissimo expects. */
417
+ export function normalizeFrenchMobile(phone) {
418
+ const digits = phone.replace(/[\s.\-()]/g, '');
419
+ const m = /^(?:\+33|0033)([67]\d{8})$/.exec(digits);
420
+ if (m)
421
+ return `0${m[1]}`;
422
+ return digits;
423
+ }
424
+ //# sourceMappingURL=colissimo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colissimo.js","sourceRoot":"","sources":["../../src/shipping/colissimo.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,10 @@
2
2
  * @rovela/sdk/shipping
3
3
  *
4
4
  * Shipping module for carrier integrations.
5
- * Currently supports Shippo for real-time carrier rates and label purchases.
5
+ * - Shippo: real-time carrier rates and label purchases.
6
+ * - Colissimo (La Poste): label generation + pickup-point (relay) delivery.
6
7
  */
7
8
  export { validateShippoKey, getShippoRates, purchaseShippoLabel, shippoRatesToStripeOptions, } from './shippo';
9
+ export { validateColissimoCredentials, generateColissimoLabel, searchRelayPoints, getRelayPointById, relayTypeToProductCode, colissimoTrackingUrl, isFrenchMobile, normalizeFrenchMobile, parseSlsResponse, ColissimoError, } from './colissimo';
10
+ export type { ColissimoCredentials, ColissimoSenderAddress, ColissimoAddressee, ColissimoProductCode, ColissimoLabelFormat, ColissimoLabelResult, ColissimoValidationResult, ColissimoMessage, } from './colissimo';
8
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shipping/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shipping/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,UAAU,CAAA;AAEjB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,aAAa,CAAA"}
@@ -2,7 +2,9 @@
2
2
  * @rovela/sdk/shipping
3
3
  *
4
4
  * Shipping module for carrier integrations.
5
- * Currently supports Shippo for real-time carrier rates and label purchases.
5
+ * - Shippo: real-time carrier rates and label purchases.
6
+ * - Colissimo (La Poste): label generation + pickup-point (relay) delivery.
6
7
  */
7
8
  export { validateShippoKey, getShippoRates, purchaseShippoLabel, shippoRatesToStripeOptions, } from './shippo';
9
+ export { validateColissimoCredentials, generateColissimoLabel, searchRelayPoints, getRelayPointById, relayTypeToProductCode, colissimoTrackingUrl, isFrenchMobile, normalizeFrenchMobile, parseSlsResponse, ColissimoError, } from './colissimo';
8
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shipping/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shipping/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,UAAU,CAAA;AAEjB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,GACf,MAAM,aAAa,CAAA"}
@@ -41,6 +41,17 @@ the NEW SURFACES below make it reachable.
41
41
  2. **Native discounts replace Stripe coupons as the merchant surface.**
42
42
  Legacy Stripe-coupon helpers still exist (feature-card usage) but the
43
43
  admin Discounts manager + `/api/checkout/discount` are the system now.
44
+ MERCHANT-DATA HAZARD: codes issued as Stripe promotion codes (newsletter
45
+ welcome flows, coupon feature cards) do NOT validate in the native
46
+ discount box — customers holding them get "invalid code". Audit for a
47
+ Stripe-code issuer (grep for `createCouponWithCode` /
48
+ `createBulkPromotionCodes` / promotion-code inserts) and, if found, emit a
49
+ merchant follow-up: backfill outstanding codes into the `discounts` table
50
+ (idempotent INSERTs mirroring the issuer's terms) or keep
51
+ `store_settings.promotion_codes_enabled = true` so Stripe's own field
52
+ still accepts them during the transition. Rewire the issuer itself to the
53
+ native engine (`createDiscount` from `@rovela-ai/sdk/core/server`,
54
+ 0.19.7+).
44
55
  3. **`orders.payment_provider`** (default 'stripe') exists — custom order
45
56
  readers must not assume every order is a Stripe order (manual methods
46
57
  create `pending` orders with `manual_*` idempotency keys).
@@ -1,4 +1,4 @@
1
- # E6 Mobile + integrity — 0.19.0..0.19.7
1
+ # E6 Mobile + integrity — 0.19.0..0.19.8
2
2
 
3
3
  ## What landed
4
4
  The save-grammar completion across every admin manager, full mobile
@@ -146,7 +146,35 @@ store code that mints codes programmatically (newsletter welcome flows).
146
146
  `OrderTimeline` and `invoice.shipTo`.
147
147
  - Cart/checkout pages follow the hydration contract above.
148
148
 
149
+ ## 0.19.8 — Users row-actions menu portal
150
+
151
+ ### SDK-internal (free via pin bump)
152
+ The `/admin/users` row-actions (⋯) menu portals to `<body>` — it was the one
153
+ menu still rendered in place inside the table's `overflow-x-auto` wrapper,
154
+ which clips vertically too (per CSS spec, auto on one axis forces auto on the
155
+ other), so the LAST rows' menus were cut off at the table edge. It now uses
156
+ the same mechanics AdminSelect adopted in 0.19.7 (`.admin-select-portal`
157
+ fixed positioning, flip-above near the viewport bottom, scroll/resize
158
+ re-anchoring, Escape closes). No API change, no new exports.
159
+
160
+ Same pass fixed the menu's dead hover states: items referenced
161
+ `--admin-background-hover`, a variable the theme never defined (invalid CSS,
162
+ so hovering did nothing except on destructive items). They now use
163
+ `--admin-background-subtle` — the item-hover token AdminSelect,
164
+ DiscountsManager and RecentOrders already use. There is no
165
+ `--admin-background-hover`; never reference it.
166
+
167
+ RULE for custom admin code: any menu or dropdown anchored inside a table
168
+ wrapper or any `overflow` ancestor MUST portal to `<body>` — AdminSelect and
169
+ the UsersTable RowActions menu are the two reference implementations. An
170
+ in-place `position: absolute` menu inside a scroller is always a latent
171
+ clipping bug.
172
+
149
173
  ## Verify
174
+ - `/admin/users` with enough rows to reach the viewport bottom: the LAST
175
+ row's ⋯ menu opens fully visible (flipped above the trigger), actions
176
+ fire, click-outside and Escape close it, and it tracks the row while
177
+ scrolling.
150
178
  - Resize the admin to 375px: drawer opens from the hamburger, closes on
151
179
  scrim/Escape; a delete dialog docks as a bottom sheet; Products table
152
180
  scrolls itself (page never scrolls horizontally); Recent Orders shows