@thorprovider/adapters 4.2.0 → 4.3.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 (56) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/PROVIDER_IMPLEMENTATION_ANALYSIS.md +659 -0
  3. package/README.md +19 -0
  4. package/dist/index.d.mts +249 -2
  5. package/dist/index.d.ts +249 -2
  6. package/dist/index.js +1450 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +1448 -2
  9. package/dist/index.mjs.map +1 -1
  10. package/docs/RETRY_SOLUTION_SUMMARY.md +26 -11
  11. package/docs/authentication.md +115 -62
  12. package/docs/capabilities-system.md +34 -2
  13. package/docs/category-customization.md +37 -29
  14. package/docs/fetch-reliability-solution.md +127 -278
  15. package/docs/getting-started.md +43 -29
  16. package/docs/profile-management.md +68 -38
  17. package/docs/providers-overview.md +14 -0
  18. package/docs/services-overview.md +20 -2
  19. package/package.json +10 -6
  20. package/quick-reference.md +34 -0
  21. package/src/factory/index.ts +4 -0
  22. package/src/index.ts +3 -0
  23. package/src/providers/index.ts +3 -0
  24. package/src/providers/medusa/admin.test.ts +404 -0
  25. package/src/providers/medusa/admin.ts +442 -0
  26. package/src/providers/medusa/auth.test.ts +137 -0
  27. package/src/providers/medusa/auth.ts +96 -1
  28. package/src/providers/medusa/index.ts +21 -1
  29. package/src/providers/shopify/auth.ts +6 -0
  30. package/src/providers/stelorder/client.test-helpers.ts +3 -0
  31. package/src/providers/stelorder/client.test.ts +58 -0
  32. package/src/providers/stelorder/client.ts +326 -0
  33. package/src/providers/stelorder/index.test.ts +48 -0
  34. package/src/providers/stelorder/index.ts +685 -0
  35. package/src/providers/stelorder/live-smoke.test.ts +54 -0
  36. package/src/providers/stelorder/transforms.test.ts +103 -0
  37. package/src/providers/stelorder/transforms.ts +631 -0
  38. package/src/resilience/README.md +70 -48
  39. package/src/types/capabilities.types.ts +51 -0
  40. package/vitest.config.ts +13 -0
  41. package/vitest.setup.ts +6 -0
  42. package/.turbo/turbo-build.log +0 -18
  43. package/dist/.tsbuildinfo +0 -1
  44. package/src/core/errors.js +0 -161
  45. package/src/core/fetch-utils.js +0 -240
  46. package/src/core/logger.js +0 -63
  47. package/src/metadata/channel-scope-validation.js +0 -136
  48. package/src/metadata/channel-scope.js +0 -174
  49. package/src/metadata/index.js +0 -31
  50. package/src/providers/medusa/auth.js +0 -601
  51. package/src/providers/medusa/index.js +0 -2485
  52. package/src/providers/medusa/payment.js +0 -265
  53. package/src/providers/medusa/transforms.js +0 -505
  54. package/src/types/capabilities.types.js +0 -141
  55. package/src/types/provider.js +0 -9
  56. package/src/utils/payment-utils.js +0 -152
@@ -0,0 +1,631 @@
1
+ import type {
2
+ Collection,
3
+ Customer,
4
+ Address,
5
+ FulfillmentStatus,
6
+ Image,
7
+ InventoryLevel,
8
+ Money,
9
+ Order,
10
+ OrderItem,
11
+ OrderStatus,
12
+ PaymentMethod,
13
+ PaymentMethodType,
14
+ PaymentStatus,
15
+ Product,
16
+ ProductCategory,
17
+ ProductVariant,
18
+ ShippingMethod,
19
+ StockLocation,
20
+ } from '@thorprovider/types';
21
+
22
+ export interface StelResource {
23
+ id?: number;
24
+ path?: string;
25
+ deleted?: boolean;
26
+ 'utc-last-modification-date'?: string;
27
+ }
28
+
29
+ export interface StelItemImage {
30
+ 'item-image-path'?: string;
31
+ order?: number;
32
+ }
33
+
34
+ export interface StelItemRate {
35
+ 'rate-id'?: number;
36
+ 'rate-path'?: string;
37
+ price?: number;
38
+ }
39
+
40
+ export interface StelProductWarehouse {
41
+ 'warehouse-id'?: number;
42
+ 'warehouse-path'?: string;
43
+ 'minimum-stock'?: number;
44
+ 'real-stock'?: number;
45
+ 'virtual-stock'?: number;
46
+ location?: string;
47
+ }
48
+
49
+ export interface StelProduct extends StelResource {
50
+ name?: string;
51
+ description?: string;
52
+ reference?: string;
53
+ 'full-reference'?: string;
54
+ barcode?: string;
55
+ 'sales-price'?: number;
56
+ 'purchase-price'?: number;
57
+ 'product-category-id'?: number;
58
+ 'product-category-path'?: string;
59
+ 'real-stock'?: number;
60
+ 'virtual-stock'?: number;
61
+ 'minimum-stock'?: number;
62
+ 'stock-enabled'?: boolean;
63
+ inactive?: boolean;
64
+ promotional?: boolean;
65
+ 'creation-date'?: string;
66
+ 'item-images'?: StelItemImage[];
67
+ 'item-rates'?: StelItemRate[];
68
+ 'product-warehouses'?: StelProductWarehouse[];
69
+ }
70
+
71
+ export interface StelProductCategory extends StelResource {
72
+ name?: string;
73
+ description?: string;
74
+ slug?: string;
75
+ 'parent-category-id'?: number;
76
+ }
77
+
78
+ export interface StelClient extends StelResource {
79
+ name?: string;
80
+ 'business-name'?: string;
81
+ email?: string;
82
+ phone?: string;
83
+ mobile?: string;
84
+ 'rate-id'?: number;
85
+ 'payment-option-id'?: number;
86
+ 'payment-term-id'?: number;
87
+ 'delivery-option-id'?: number;
88
+ 'main-address'?: StelAddress;
89
+ }
90
+
91
+ export interface StelAddress extends StelResource {
92
+ name?: string;
93
+ 'first-name'?: string;
94
+ 'last-name'?: string;
95
+ company?: string;
96
+ 'address-data'?: string;
97
+ 'postal-code'?: string;
98
+ 'city-town'?: string;
99
+ province?: string;
100
+ 'country-code'?: string;
101
+ phone?: string;
102
+ email?: string;
103
+ }
104
+
105
+ export interface StelDocumentLine {
106
+ 'item-id'?: number;
107
+ 'item-name'?: string;
108
+ 'item-description'?: string;
109
+ 'item-base-price'?: number;
110
+ 'primary-tax-percentage'?: number;
111
+ 'discount-percentage'?: number;
112
+ units?: number;
113
+ 'line-type'?: 'ITEM' | 'DOCUMENT' | 'SECTION' | 'COMPONENT';
114
+ }
115
+
116
+ export interface StelSalesOrder extends StelResource {
117
+ reference?: string;
118
+ status?: string;
119
+ email?: string;
120
+ 'client-id'?: number;
121
+ 'payment-option-id'?: number;
122
+ 'payment-term-id'?: number;
123
+ 'delivery-option-id'?: number;
124
+ 'creation-date'?: string;
125
+ 'total-base'?: number;
126
+ 'total-tax'?: number;
127
+ total?: number;
128
+ 'document-lines'?: StelDocumentLine[];
129
+ }
130
+
131
+ export interface StelPaymentOption extends StelResource {
132
+ name?: string;
133
+ description?: string;
134
+ }
135
+
136
+ export interface StelPaymentTerm extends StelResource {
137
+ name?: string;
138
+ description?: string;
139
+ }
140
+
141
+ export interface StelDeliveryOption extends StelResource {
142
+ name?: string;
143
+ description?: string;
144
+ }
145
+
146
+ export interface StelWarehouse extends StelResource {
147
+ name?: string;
148
+ description?: string;
149
+ }
150
+
151
+ export function normalizeStelCollection<T>(payload: T | T[] | null | undefined): T[] {
152
+ if (!payload) {
153
+ return [];
154
+ }
155
+
156
+ return Array.isArray(payload) ? payload : [payload];
157
+ }
158
+
159
+ export function normalizeStelEntity<T>(payload: T | T[] | null | undefined): T | undefined {
160
+ if (!payload) {
161
+ return undefined;
162
+ }
163
+
164
+ return Array.isArray(payload) ? payload[0] : payload;
165
+ }
166
+
167
+ export function transformStelMoney(
168
+ amount: number | string | null | undefined,
169
+ currencyCode: string = 'USD',
170
+ ): Money {
171
+ const normalizedAmount = typeof amount === 'string' ? Number.parseFloat(amount) : amount ?? 0;
172
+
173
+ return {
174
+ amount: Number.isFinite(normalizedAmount) ? normalizedAmount.toFixed(2) : '0.00',
175
+ currencyCode: currencyCode.toUpperCase(),
176
+ };
177
+ }
178
+
179
+ export function transformStelImage(url: string | null | undefined, altText: string = ''): Image | undefined {
180
+ const normalizedUrl = url?.trim();
181
+
182
+ if (!normalizedUrl) {
183
+ return undefined;
184
+ }
185
+
186
+ return {
187
+ url: normalizedUrl,
188
+ altText,
189
+ width: 800,
190
+ height: 800,
191
+ };
192
+ }
193
+
194
+ export function normalizeStelDate(value: string | null | undefined): string {
195
+ if (!value) {
196
+ return new Date().toISOString();
197
+ }
198
+
199
+ const parsed = new Date(value);
200
+ return Number.isNaN(parsed.getTime()) ? new Date().toISOString() : parsed.toISOString();
201
+ }
202
+
203
+ export function extractStelIdFromPath(path: string | null | undefined): string | undefined {
204
+ if (!path) {
205
+ return undefined;
206
+ }
207
+
208
+ const match = path.match(/\/([^/]+)$/);
209
+ return match?.[1];
210
+ }
211
+
212
+ export function toStelStringId(value: string | number | null | undefined): string {
213
+ if (value === null || value === undefined) {
214
+ return '';
215
+ }
216
+
217
+ return String(value);
218
+ }
219
+
220
+ function slugify(value: string): string {
221
+ return value
222
+ .normalize('NFKD')
223
+ .replace(/[\u0300-\u036f]/g, '')
224
+ .toLowerCase()
225
+ .replace(/[^a-z0-9]+/g, '-')
226
+ .replace(/^-+|-+$/g, '');
227
+ }
228
+
229
+ export function buildStelHandle(...candidates: Array<string | number | null | undefined>): string {
230
+ for (const candidate of candidates) {
231
+ if (candidate === null || candidate === undefined) {
232
+ continue;
233
+ }
234
+
235
+ const normalized = slugify(String(candidate).trim());
236
+ if (normalized) {
237
+ return normalized;
238
+ }
239
+ }
240
+
241
+ return 'stel-item';
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Product transforms
246
+ // ---------------------------------------------------------------------------
247
+
248
+ function transformStelProductImages(product: StelProduct): Image[] {
249
+ const images: Image[] = [];
250
+
251
+ if (!product['item-images']) {
252
+ return images;
253
+ }
254
+
255
+ const sorted = [...product['item-images']].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
256
+
257
+ for (const img of sorted) {
258
+ const transformed = transformStelImage(img['item-image-path'], product.name ?? '');
259
+ if (transformed) {
260
+ images.push(transformed);
261
+ }
262
+ }
263
+
264
+ return images;
265
+ }
266
+
267
+ function deriveStelStockStatus(product: StelProduct): 'in_stock' | 'out_of_stock' | 'low_stock' {
268
+ if (!product['stock-enabled']) {
269
+ return 'in_stock';
270
+ }
271
+
272
+ const realStock = product['real-stock'] ?? 0;
273
+ const minimumStock = product['minimum-stock'] ?? 0;
274
+
275
+ if (realStock <= 0) {
276
+ return 'out_of_stock';
277
+ }
278
+
279
+ if (realStock <= minimumStock) {
280
+ return 'low_stock';
281
+ }
282
+
283
+ return 'in_stock';
284
+ }
285
+
286
+ /**
287
+ * Build a single synthetic variant from a STEL product.
288
+ * STEL products have no variant concept — every product is a single SKU.
289
+ */
290
+ function buildSyntheticVariant(product: StelProduct, currencyCode: string): ProductVariant {
291
+ const price = transformStelMoney(product['sales-price'], currencyCode);
292
+ const stockStatus = deriveStelStockStatus(product);
293
+ const quantityAvailable = product['stock-enabled'] ? (product['real-stock'] ?? 0) : undefined;
294
+ const image = product['item-images']?.[0]
295
+ ? transformStelImage(product['item-images'][0]['item-image-path'], product.name ?? '')
296
+ : undefined;
297
+
298
+ return {
299
+ id: toStelStringId(product.id),
300
+ title: 'Default',
301
+ sku: product.barcode ?? product['full-reference'] ?? undefined,
302
+ availableForSale: stockStatus !== 'out_of_stock',
303
+ selectedOptions: [],
304
+ price,
305
+ compareAtPrice: undefined,
306
+ quantityAvailable,
307
+ stockStatus,
308
+ image,
309
+ };
310
+ }
311
+
312
+ export function transformStelProduct(raw: StelProduct, currencyCode: string): Product {
313
+ const id = toStelStringId(raw.id);
314
+ const handle = buildStelHandle(raw['full-reference'], raw.reference, raw.name, raw.id);
315
+ const price = transformStelMoney(raw['sales-price'], currencyCode);
316
+ const images = transformStelProductImages(raw);
317
+ const variant = buildSyntheticVariant(raw, currencyCode);
318
+ const now = new Date().toISOString();
319
+
320
+ return {
321
+ id,
322
+ handle,
323
+ title: raw.name ?? '',
324
+ description: raw.description ?? '',
325
+ availableForSale: variant.availableForSale,
326
+ options: [],
327
+ variants: [variant],
328
+ featuredImage: images[0],
329
+ images,
330
+ priceRange: {
331
+ minVariantPrice: price,
332
+ maxVariantPrice: price,
333
+ },
334
+ productType: undefined,
335
+ vendor: undefined,
336
+ tags: raw.promotional ? ['promotional'] : [],
337
+ createdAt: normalizeStelDate(raw['creation-date']),
338
+ updatedAt: normalizeStelDate(raw['utc-last-modification-date']) || now,
339
+ metadata: {
340
+ stelFullReference: raw['full-reference'],
341
+ stelReference: raw.reference,
342
+ stelBarcode: raw.barcode,
343
+ stelCategoryId: raw['product-category-id'],
344
+ stelInactive: raw.inactive,
345
+ stelPromotional: raw.promotional,
346
+ stelPurchasePrice: raw['purchase-price'],
347
+ },
348
+ };
349
+ }
350
+
351
+ // ---------------------------------------------------------------------------
352
+ // Category transforms
353
+ // ---------------------------------------------------------------------------
354
+
355
+ export function transformStelCategory(raw: StelProductCategory): ProductCategory {
356
+ const id = toStelStringId(raw.id);
357
+ const handle = buildStelHandle(raw.slug, raw.name, raw.id);
358
+
359
+ return {
360
+ id,
361
+ name: raw.name ?? '',
362
+ handle,
363
+ description: raw.description ?? undefined,
364
+ parentCategoryId: raw['parent-category-id']
365
+ ? toStelStringId(raw['parent-category-id'])
366
+ : undefined,
367
+ metadata: {
368
+ stelSlug: raw.slug,
369
+ },
370
+ };
371
+ }
372
+
373
+ // ---------------------------------------------------------------------------
374
+ // Collection transforms (alias categories as collections)
375
+ // ---------------------------------------------------------------------------
376
+
377
+ export function transformStelCategoryToCollection(raw: StelProductCategory): Collection {
378
+ const id = toStelStringId(raw.id);
379
+ const handle = buildStelHandle(raw.slug, raw.name, raw.id);
380
+
381
+ return {
382
+ id,
383
+ handle,
384
+ title: raw.name ?? '',
385
+ description: raw.description ?? '',
386
+ path: `/collections/${handle}`,
387
+ updatedAt: normalizeStelDate(raw['utc-last-modification-date']),
388
+ };
389
+ }
390
+
391
+ // ---------------------------------------------------------------------------
392
+ // Inventory / warehouse transforms
393
+ // ---------------------------------------------------------------------------
394
+
395
+ export function transformStelWarehouseToStockLocation(raw: StelWarehouse): StockLocation {
396
+ return {
397
+ id: toStelStringId(raw.id),
398
+ name: raw.name ?? 'Warehouse',
399
+ };
400
+ }
401
+
402
+ export function transformStelProductWarehouseToInventoryLevel(
403
+ pw: StelProductWarehouse,
404
+ ): InventoryLevel {
405
+ return {
406
+ locationId: toStelStringId(pw['warehouse-id']),
407
+ locationName: pw['warehouse-path'] ?? `Warehouse ${pw['warehouse-id'] ?? 'unknown'}`,
408
+ availableQuantity: pw['real-stock'] ?? 0,
409
+ reservedQuantity: Math.max(0, (pw['virtual-stock'] ?? 0) - (pw['real-stock'] ?? 0)),
410
+ stockedQuantity: pw['virtual-stock'] ?? pw['real-stock'] ?? 0,
411
+ };
412
+ }
413
+
414
+ // ---------------------------------------------------------------------------
415
+ // Customer / Address transforms
416
+ // ---------------------------------------------------------------------------
417
+
418
+ function splitStelName(fullName: string | null | undefined): { firstName: string; lastName: string } {
419
+ if (!fullName?.trim()) {
420
+ return { firstName: '', lastName: '' };
421
+ }
422
+
423
+ const parts = fullName.trim().split(/\s+/);
424
+ const firstName = parts[0] ?? '';
425
+ const lastName = parts.length > 1 ? parts.slice(1).join(' ') : '';
426
+ return { firstName, lastName };
427
+ }
428
+
429
+ export function transformStelAddress(raw: StelAddress): Address {
430
+ const firstName = raw['first-name'] ?? '';
431
+ const lastName = raw['last-name'] ?? '';
432
+
433
+ // If first/last not present, attempt to split the 'name' field
434
+ const nameParts = !firstName && !lastName ? splitStelName(raw.name) : { firstName, lastName };
435
+
436
+ return {
437
+ id: raw.id ? toStelStringId(raw.id) : undefined,
438
+ firstName: nameParts.firstName,
439
+ lastName: nameParts.lastName,
440
+ company: raw.company ?? undefined,
441
+ address1: raw['address-data'] ?? '',
442
+ city: raw['city-town'] ?? '',
443
+ province: raw.province ?? undefined,
444
+ postalCode: raw['postal-code'] ?? '',
445
+ countryCode: raw['country-code'] ?? '',
446
+ phone: raw.phone ?? undefined,
447
+ isDefault: true,
448
+ metadata: {
449
+ stelEmail: raw.email,
450
+ },
451
+ };
452
+ }
453
+
454
+ export function transformStelClient(raw: StelClient): Customer {
455
+ const { firstName, lastName } = splitStelName(raw.name ?? raw['business-name']);
456
+
457
+ return {
458
+ id: toStelStringId(raw.id),
459
+ email: raw.email ?? '',
460
+ firstName: firstName || null,
461
+ lastName: lastName || null,
462
+ phone: raw.phone ?? raw.mobile ?? null,
463
+ hasAccount: true,
464
+ createdAt: normalizeStelDate(raw['utc-last-modification-date']),
465
+ updatedAt: normalizeStelDate(raw['utc-last-modification-date']),
466
+ metadata: {
467
+ stelBusinessName: raw['business-name'],
468
+ stelRateId: raw['rate-id'],
469
+ stelPaymentOptionId: raw['payment-option-id'],
470
+ stelPaymentTermId: raw['payment-term-id'],
471
+ stelDeliveryOptionId: raw['delivery-option-id'],
472
+ },
473
+ };
474
+ }
475
+
476
+ // ---------------------------------------------------------------------------
477
+ // Order transforms
478
+ // ---------------------------------------------------------------------------
479
+
480
+ function deriveStelOrderStatus(raw: StelSalesOrder): OrderStatus {
481
+ if (raw.deleted) {
482
+ return 'cancelled';
483
+ }
484
+
485
+ const status = (raw.status ?? '').toLowerCase();
486
+
487
+ if (status.includes('cancel') || status.includes('anulad')) {
488
+ return 'cancelled';
489
+ }
490
+ if (status.includes('complet') || status.includes('cerrad') || status.includes('settled')) {
491
+ return 'completed';
492
+ }
493
+ if (status.includes('process') || status.includes('confirm')) {
494
+ return 'processing';
495
+ }
496
+
497
+ return 'pending';
498
+ }
499
+
500
+ function deriveStelPaymentStatus(raw: StelSalesOrder): PaymentStatus {
501
+ const orderStatus = deriveStelOrderStatus(raw);
502
+
503
+ if (orderStatus === 'completed') {
504
+ return 'paid';
505
+ }
506
+ if (orderStatus === 'cancelled') {
507
+ return 'pending';
508
+ }
509
+
510
+ return 'pending';
511
+ }
512
+
513
+ function deriveStelFulfillmentStatus(raw: StelSalesOrder): FulfillmentStatus {
514
+ const orderStatus = deriveStelOrderStatus(raw);
515
+
516
+ if (orderStatus === 'completed') {
517
+ return 'fulfilled';
518
+ }
519
+
520
+ return 'unfulfilled';
521
+ }
522
+
523
+ function transformStelDocumentLine(
524
+ line: StelDocumentLine,
525
+ index: number,
526
+ currencyCode: string,
527
+ ): OrderItem {
528
+ const unitPrice = line['item-base-price'] ?? 0;
529
+ const quantity = line.units ?? 1;
530
+ const lineTotal = unitPrice * quantity;
531
+
532
+ return {
533
+ id: toStelStringId(line['item-id'] ?? index),
534
+ title: line['item-name'] ?? '',
535
+ variantTitle: undefined,
536
+ quantity,
537
+ price: transformStelMoney(unitPrice, currencyCode),
538
+ total: transformStelMoney(lineTotal, currencyCode),
539
+ sku: undefined,
540
+ productId: line['item-id'] ? toStelStringId(line['item-id']) : undefined,
541
+ };
542
+ }
543
+
544
+ export function transformStelSalesOrder(raw: StelSalesOrder, currencyCode: string): Order {
545
+ const id = toStelStringId(raw.id);
546
+ const now = new Date().toISOString();
547
+ const items = (raw['document-lines'] ?? [])
548
+ .filter((line) => line['line-type'] === 'ITEM' || !line['line-type'])
549
+ .map((line, i) => transformStelDocumentLine(line, i, currencyCode));
550
+
551
+ const status = deriveStelOrderStatus(raw);
552
+
553
+ return {
554
+ id,
555
+ orderNumber: raw.reference ?? id,
556
+ email: raw.email ?? '',
557
+ status,
558
+ paymentStatus: deriveStelPaymentStatus(raw),
559
+ fulfillmentStatus: deriveStelFulfillmentStatus(raw),
560
+ customerId: raw['client-id'] ? toStelStringId(raw['client-id']) : undefined,
561
+ items,
562
+ subtotal: transformStelMoney(raw['total-base'], currencyCode),
563
+ total: transformStelMoney(raw.total, currencyCode),
564
+ tax: raw['total-tax'] != null ? transformStelMoney(raw['total-tax'], currencyCode) : undefined,
565
+ createdAt: normalizeStelDate(raw['creation-date']),
566
+ updatedAt: normalizeStelDate(raw['utc-last-modification-date']) || now,
567
+ cancelledAt: status === 'cancelled' ? normalizeStelDate(raw['utc-last-modification-date']) : undefined,
568
+ completedAt: status === 'completed' ? normalizeStelDate(raw['utc-last-modification-date']) : undefined,
569
+ metadata: {
570
+ stelReference: raw.reference,
571
+ stelStatus: raw.status,
572
+ stelPaymentOptionId: raw['payment-option-id'],
573
+ stelPaymentTermId: raw['payment-term-id'],
574
+ stelDeliveryOptionId: raw['delivery-option-id'],
575
+ },
576
+ };
577
+ }
578
+
579
+ // ---------------------------------------------------------------------------
580
+ // Payment / delivery transforms
581
+ // ---------------------------------------------------------------------------
582
+
583
+ function inferPaymentMethodType(name: string | null | undefined): PaymentMethodType {
584
+ if (!name) {
585
+ return 'other';
586
+ }
587
+
588
+ const lower = name.toLowerCase();
589
+
590
+ if (lower.includes('transfer') || lower.includes('bank') || lower.includes('sepa') || lower.includes('iban')) {
591
+ return 'bank_transfer';
592
+ }
593
+ if (lower.includes('card') || lower.includes('tarjeta') || lower.includes('visa') || lower.includes('master')) {
594
+ return 'card';
595
+ }
596
+ if (lower.includes('paypal') || lower.includes('wallet') || lower.includes('apple') || lower.includes('google')) {
597
+ return 'wallet';
598
+ }
599
+ if (lower.includes('cash') || lower.includes('contrar') || lower.includes('efectivo') || lower.includes('cod')) {
600
+ return 'cash_on_delivery';
601
+ }
602
+ if (lower.includes('crypto') || lower.includes('bitcoin')) {
603
+ return 'crypto';
604
+ }
605
+
606
+ return 'other';
607
+ }
608
+
609
+ export function transformStelPaymentOption(raw: StelPaymentOption): PaymentMethod {
610
+ return {
611
+ id: toStelStringId(raw.id),
612
+ name: raw.name ?? 'Payment Option',
613
+ type: inferPaymentMethodType(raw.name),
614
+ isEnabled: !raw.deleted,
615
+ requiresSetup: false,
616
+ description: raw.description ?? undefined,
617
+ metadata: {
618
+ stelPath: raw.path,
619
+ source: 'paymentOption',
620
+ },
621
+ };
622
+ }
623
+
624
+ export function transformStelDeliveryOption(raw: StelDeliveryOption, currencyCode: string): ShippingMethod {
625
+ return {
626
+ id: toStelStringId(raw.id),
627
+ name: raw.name ?? 'Delivery Option',
628
+ price: transformStelMoney(0, currencyCode),
629
+ description: raw.description ?? undefined,
630
+ };
631
+ }