ecomcoder-cli 1.3.1 → 1.3.3

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 (73) hide show
  1. package/dist/cli.js +21 -2
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/discount/create-basic.d.ts +6 -0
  4. package/dist/commands/discount/create-basic.d.ts.map +1 -0
  5. package/dist/commands/discount/create-basic.js +156 -0
  6. package/dist/commands/discount/create-basic.js.map +1 -0
  7. package/dist/commands/discount/create-bxgy.d.ts +17 -0
  8. package/dist/commands/discount/create-bxgy.d.ts.map +1 -0
  9. package/dist/commands/discount/create-bxgy.js +230 -0
  10. package/dist/commands/discount/create-bxgy.js.map +1 -0
  11. package/dist/commands/discount/create-shipping.d.ts +17 -0
  12. package/dist/commands/discount/create-shipping.d.ts.map +1 -0
  13. package/dist/commands/discount/create-shipping.js +170 -0
  14. package/dist/commands/discount/create-shipping.js.map +1 -0
  15. package/dist/commands/discount/delete.d.ts +6 -0
  16. package/dist/commands/discount/delete.d.ts.map +1 -0
  17. package/dist/commands/discount/delete.js +45 -0
  18. package/dist/commands/discount/delete.js.map +1 -0
  19. package/dist/commands/discount/index.d.ts +10 -0
  20. package/dist/commands/discount/index.d.ts.map +1 -0
  21. package/dist/commands/discount/index.js +98 -0
  22. package/dist/commands/discount/index.js.map +1 -0
  23. package/dist/commands/discount/list.d.ts +6 -0
  24. package/dist/commands/discount/list.d.ts.map +1 -0
  25. package/dist/commands/discount/list.js +84 -0
  26. package/dist/commands/discount/list.js.map +1 -0
  27. package/dist/commands/discount/queries.d.ts +14 -0
  28. package/dist/commands/discount/queries.d.ts.map +1 -0
  29. package/dist/commands/discount/queries.js +574 -0
  30. package/dist/commands/discount/queries.js.map +1 -0
  31. package/dist/commands/discount/service.d.ts +31 -0
  32. package/dist/commands/discount/service.d.ts.map +1 -0
  33. package/dist/commands/discount/service.js +684 -0
  34. package/dist/commands/discount/service.js.map +1 -0
  35. package/dist/commands/discount/types.d.ts +228 -0
  36. package/dist/commands/discount/types.d.ts.map +1 -0
  37. package/dist/commands/discount/types.js +5 -0
  38. package/dist/commands/discount/types.js.map +1 -0
  39. package/dist/commands/discount/utils.d.ts +51 -0
  40. package/dist/commands/discount/utils.d.ts.map +1 -0
  41. package/dist/commands/discount/utils.js +153 -0
  42. package/dist/commands/discount/utils.js.map +1 -0
  43. package/dist/commands/product/__tests__/update-description.test.js +1 -0
  44. package/dist/commands/product/__tests__/update-description.test.js.map +1 -1
  45. package/dist/commands/product/create-variants.d.ts +8 -0
  46. package/dist/commands/product/create-variants.d.ts.map +1 -0
  47. package/dist/commands/product/create-variants.js +160 -0
  48. package/dist/commands/product/create-variants.js.map +1 -0
  49. package/dist/commands/product/delete-variants.d.ts +8 -0
  50. package/dist/commands/product/delete-variants.d.ts.map +1 -0
  51. package/dist/commands/product/delete-variants.js +105 -0
  52. package/dist/commands/product/delete-variants.js.map +1 -0
  53. package/dist/commands/product/index.d.ts.map +1 -1
  54. package/dist/commands/product/index.js +30 -0
  55. package/dist/commands/product/index.js.map +1 -1
  56. package/dist/commands/product/queries.d.ts +12 -2
  57. package/dist/commands/product/queries.d.ts.map +1 -1
  58. package/dist/commands/product/queries.js +62 -0
  59. package/dist/commands/product/queries.js.map +1 -1
  60. package/dist/commands/product/service.d.ts +41 -1
  61. package/dist/commands/product/service.d.ts.map +1 -1
  62. package/dist/commands/product/service.js +159 -1
  63. package/dist/commands/product/service.js.map +1 -1
  64. package/dist/commands/product/types.d.ts +78 -0
  65. package/dist/commands/product/types.d.ts.map +1 -1
  66. package/dist/commands/product/update-title.d.ts +8 -0
  67. package/dist/commands/product/update-title.d.ts.map +1 -0
  68. package/dist/commands/product/update-title.js +94 -0
  69. package/dist/commands/product/update-title.js.map +1 -0
  70. package/dist/commands/product/utils.d.ts.map +1 -1
  71. package/dist/commands/product/utils.js +1 -0
  72. package/dist/commands/product/utils.js.map +1 -1
  73. package/package.json +2 -1
@@ -0,0 +1,684 @@
1
+ /**
2
+ * Discount Service Layer
3
+ * Business logic for discount operations
4
+ */
5
+ import { shopifyGraphQL } from '../../lib/shopify-client.js';
6
+ import { DISCOUNT_CODE_BASIC_CREATE, DISCOUNT_AUTOMATIC_BASIC_CREATE, DISCOUNT_CODE_BXGY_CREATE, DISCOUNT_AUTOMATIC_BXGY_CREATE, DISCOUNT_CODE_FREE_SHIPPING_CREATE, DISCOUNT_AUTOMATIC_FREE_SHIPPING_CREATE, CODE_DISCOUNT_NODES, AUTOMATIC_DISCOUNT_NODES, DISCOUNT_CODE_DELETE, DISCOUNT_AUTOMATIC_DELETE } from './queries.js';
7
+ import { validateDiscountValue, validateDiscountCode, parseDateTimeInput, formatDiscountCode, formatUserErrors, formatDiscountForDisplay, buildDiscountQuery } from './utils.js';
8
+ /**
9
+ * Create a basic discount (percentage or fixed amount)
10
+ */
11
+ export async function createBasicDiscount(input, credentials) {
12
+ // Validate discount value
13
+ const valueValidation = validateDiscountValue(input.type, input.value);
14
+ if (!valueValidation.valid) {
15
+ return { success: false, error: valueValidation.error };
16
+ }
17
+ // Validate code (required for code discounts)
18
+ if (!input.automatic) {
19
+ if (!input.code) {
20
+ return { success: false, error: 'Discount code is required for code-based discounts' };
21
+ }
22
+ const codeValidation = validateDiscountCode(input.code);
23
+ if (!codeValidation.valid) {
24
+ return { success: false, error: codeValidation.error };
25
+ }
26
+ }
27
+ // Validate dates
28
+ const startsAtValidation = parseDateTimeInput(input.startsAt);
29
+ if (!startsAtValidation.valid) {
30
+ return { success: false, error: `Start date: ${startsAtValidation.error}` };
31
+ }
32
+ let endsAt;
33
+ if (input.endsAt) {
34
+ const endsAtValidation = parseDateTimeInput(input.endsAt);
35
+ if (!endsAtValidation.valid) {
36
+ return { success: false, error: `End date: ${endsAtValidation.error}` };
37
+ }
38
+ endsAt = endsAtValidation.value;
39
+ }
40
+ // Build customerGets input
41
+ const customerGets = {
42
+ items: {},
43
+ value: {}
44
+ };
45
+ // Set items based on target
46
+ if (input.target === 'all') {
47
+ customerGets.items.all = true;
48
+ }
49
+ else if (input.target === 'collections' && input.targetIds) {
50
+ customerGets.items.collections = {
51
+ add: input.targetIds
52
+ };
53
+ }
54
+ else if (input.target === 'products' && input.targetIds) {
55
+ customerGets.items.products = {
56
+ add: input.targetIds
57
+ };
58
+ }
59
+ else {
60
+ return {
61
+ success: false,
62
+ error: `Invalid target: ${input.target}. Must specify targetIds for collections/products`
63
+ };
64
+ }
65
+ // Set value based on type
66
+ if (input.type === 'percentage') {
67
+ customerGets.value.percentage = parseFloat(input.value.toString());
68
+ }
69
+ else {
70
+ customerGets.value.discountAmount = {
71
+ amount: input.value.toString(),
72
+ appliesOnEachItem: false
73
+ };
74
+ }
75
+ // Build minimum requirement
76
+ let minimumRequirement = undefined;
77
+ if (input.minSubtotal) {
78
+ minimumRequirement = {
79
+ subtotal: {
80
+ greaterThanOrEqualToSubtotal: input.minSubtotal
81
+ }
82
+ };
83
+ }
84
+ else if (input.minQuantity) {
85
+ minimumRequirement = {
86
+ quantity: {
87
+ greaterThanOrEqualToQuantity: input.minQuantity
88
+ }
89
+ };
90
+ }
91
+ // Build combinesWith
92
+ const combinesWith = input.combinesWith || {
93
+ orderDiscounts: true,
94
+ productDiscounts: true,
95
+ shippingDiscounts: true
96
+ };
97
+ // Build context (customer targeting)
98
+ // Context is REQUIRED - must specify all customers or specific segments
99
+ const context = input.customerSegments && input.customerSegments.length > 0
100
+ ? {
101
+ customerSegments: {
102
+ add: input.customerSegments
103
+ }
104
+ }
105
+ : undefined; // Will be handled separately for code vs automatic
106
+ // Choose mutation based on automatic flag
107
+ if (input.automatic) {
108
+ // Automatic discount (context not needed for automatic)
109
+ const variables = {
110
+ automaticBasicDiscount: {
111
+ title: input.title,
112
+ startsAt: startsAtValidation.value,
113
+ endsAt,
114
+ customerGets,
115
+ minimumRequirement,
116
+ combinesWith
117
+ }
118
+ };
119
+ const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_BASIC_CREATE, variables);
120
+ // Check for GraphQL errors
121
+ if (response.errors) {
122
+ return {
123
+ success: false,
124
+ error: response.errors.map(e => e.message).join('; ')
125
+ };
126
+ }
127
+ if (!response.data) {
128
+ return { success: false, error: 'No data returned from Shopify API' };
129
+ }
130
+ if (response.data.discountAutomaticBasicCreate.userErrors.length > 0) {
131
+ return {
132
+ success: false,
133
+ error: formatUserErrors(response.data.discountAutomaticBasicCreate.userErrors)
134
+ };
135
+ }
136
+ const node = response.data.discountAutomaticBasicCreate.automaticDiscountNode;
137
+ if (!node) {
138
+ return { success: false, error: 'Failed to create automatic discount' };
139
+ }
140
+ return {
141
+ success: true,
142
+ data: {
143
+ id: node.id,
144
+ ...node.automaticDiscount
145
+ }
146
+ };
147
+ }
148
+ else {
149
+ // Code discount - context is REQUIRED
150
+ const discountContext = context || {
151
+ all: "ALL" // Enum value for "all customers"
152
+ };
153
+ const variables = {
154
+ basicCodeDiscount: {
155
+ title: input.title,
156
+ code: formatDiscountCode(input.code),
157
+ startsAt: startsAtValidation.value,
158
+ endsAt,
159
+ customerGets,
160
+ minimumRequirement,
161
+ combinesWith,
162
+ context: discountContext,
163
+ usageLimit: input.usageLimit,
164
+ appliesOncePerCustomer: input.appliesOncePerCustomer
165
+ }
166
+ };
167
+ const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_BASIC_CREATE, variables);
168
+ // Check for GraphQL errors
169
+ if (response.errors) {
170
+ return {
171
+ success: false,
172
+ error: response.errors.map(e => e.message).join('; ')
173
+ };
174
+ }
175
+ if (!response.data) {
176
+ return { success: false, error: 'No data returned from Shopify API' };
177
+ }
178
+ if (response.data.discountCodeBasicCreate.userErrors.length > 0) {
179
+ return {
180
+ success: false,
181
+ error: formatUserErrors(response.data.discountCodeBasicCreate.userErrors)
182
+ };
183
+ }
184
+ const node = response.data.discountCodeBasicCreate.codeDiscountNode;
185
+ if (!node) {
186
+ return { success: false, error: 'Failed to create code discount' };
187
+ }
188
+ return {
189
+ success: true,
190
+ data: {
191
+ id: node.id,
192
+ ...node.codeDiscount
193
+ }
194
+ };
195
+ }
196
+ }
197
+ /**
198
+ * List discounts with filtering
199
+ */
200
+ export async function listDiscounts(input, credentials) {
201
+ const limit = input.limit || 50;
202
+ const query = buildDiscountQuery(input.status, input.query);
203
+ const results = [];
204
+ try {
205
+ // Fetch code discounts
206
+ if (input.type === 'code' || input.type === 'all' || !input.type) {
207
+ const variables = {
208
+ first: limit,
209
+ query: query || undefined,
210
+ after: input.after
211
+ };
212
+ const response = await shopifyGraphQL(credentials, CODE_DISCOUNT_NODES, variables);
213
+ if (response.errors) {
214
+ return {
215
+ success: false,
216
+ error: response.errors.map(e => e.message).join('; ')
217
+ };
218
+ }
219
+ if (!response.data) {
220
+ return { success: false, error: 'No data returned from Shopify API' };
221
+ }
222
+ const codeDiscounts = response.data.codeDiscountNodes.edges.map((edge) => formatDiscountForDisplay(edge.node, false));
223
+ results.push(...codeDiscounts);
224
+ }
225
+ // Fetch automatic discounts
226
+ if (input.type === 'automatic' || input.type === 'all' || !input.type) {
227
+ const variables = {
228
+ first: limit,
229
+ query: query || undefined,
230
+ after: input.after
231
+ };
232
+ const response = await shopifyGraphQL(credentials, AUTOMATIC_DISCOUNT_NODES, variables);
233
+ if (response.errors) {
234
+ return {
235
+ success: false,
236
+ error: response.errors.map(e => e.message).join('; ')
237
+ };
238
+ }
239
+ if (!response.data) {
240
+ return { success: false, error: 'No data returned from Shopify API' };
241
+ }
242
+ const automaticDiscounts = response.data.automaticDiscountNodes.edges.map((edge) => formatDiscountForDisplay(edge.node, true));
243
+ results.push(...automaticDiscounts);
244
+ }
245
+ return {
246
+ success: true,
247
+ data: {
248
+ discounts: results,
249
+ count: results.length
250
+ }
251
+ };
252
+ }
253
+ catch (error) {
254
+ return {
255
+ success: false,
256
+ error: error.message || 'Failed to list discounts'
257
+ };
258
+ }
259
+ }
260
+ /**
261
+ * Delete a discount by ID
262
+ */
263
+ export async function deleteDiscount(discountId, credentials) {
264
+ // Determine if it's a code or automatic discount based on GID
265
+ const isCodeDiscount = discountId.includes('DiscountCodeNode');
266
+ const isAutomaticDiscount = discountId.includes('DiscountAutomaticNode');
267
+ if (!isCodeDiscount && !isAutomaticDiscount) {
268
+ return {
269
+ success: false,
270
+ error: 'Invalid discount ID: must be a DiscountCodeNode or DiscountAutomaticNode GID'
271
+ };
272
+ }
273
+ try {
274
+ if (isCodeDiscount) {
275
+ const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_DELETE, { id: discountId });
276
+ if (response.errors) {
277
+ return {
278
+ success: false,
279
+ error: response.errors.map(e => e.message).join('; ')
280
+ };
281
+ }
282
+ if (!response.data) {
283
+ return { success: false, error: 'No data returned from Shopify API' };
284
+ }
285
+ if (response.data.discountCodeDelete.userErrors.length > 0) {
286
+ return {
287
+ success: false,
288
+ error: formatUserErrors(response.data.discountCodeDelete.userErrors)
289
+ };
290
+ }
291
+ return {
292
+ success: true,
293
+ data: {
294
+ deletedId: response.data.discountCodeDelete.deletedCodeDiscountId
295
+ }
296
+ };
297
+ }
298
+ else {
299
+ const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_DELETE, { id: discountId });
300
+ if (response.errors) {
301
+ return {
302
+ success: false,
303
+ error: response.errors.map(e => e.message).join('; ')
304
+ };
305
+ }
306
+ if (!response.data) {
307
+ return { success: false, error: 'No data returned from Shopify API' };
308
+ }
309
+ if (response.data.discountAutomaticDelete.userErrors.length > 0) {
310
+ return {
311
+ success: false,
312
+ error: formatUserErrors(response.data.discountAutomaticDelete.userErrors)
313
+ };
314
+ }
315
+ return {
316
+ success: true,
317
+ data: {
318
+ deletedId: response.data.discountAutomaticDelete.deletedAutomaticDiscountId
319
+ }
320
+ };
321
+ }
322
+ }
323
+ catch (error) {
324
+ return {
325
+ success: false,
326
+ error: error.message || 'Failed to delete discount'
327
+ };
328
+ }
329
+ }
330
+ /**
331
+ * Create a BXGY (Buy X Get Y) discount
332
+ * Single Responsibility: Handles BXGY discount creation logic
333
+ * Dependency Inversion: Depends on ShopifyCredentials abstraction
334
+ */
335
+ export async function createBxgyDiscount(input, credentials) {
336
+ // Validate code (required for code discounts)
337
+ if (!input.automatic) {
338
+ if (!input.code) {
339
+ return { success: false, error: 'Discount code is required for code-based BXGY discounts' };
340
+ }
341
+ const codeValidation = validateDiscountCode(input.code);
342
+ if (!codeValidation.valid) {
343
+ return { success: false, error: codeValidation.error };
344
+ }
345
+ }
346
+ else if (input.code) {
347
+ return { success: false, error: 'Automatic discounts cannot have codes' };
348
+ }
349
+ // Validate dates
350
+ const startsAtValidation = parseDateTimeInput(input.startsAt);
351
+ if (!startsAtValidation.valid) {
352
+ return { success: false, error: `Start date: ${startsAtValidation.error}` };
353
+ }
354
+ let endsAt;
355
+ if (input.endsAt) {
356
+ const endsAtValidation = parseDateTimeInput(input.endsAt);
357
+ if (!endsAtValidation.valid) {
358
+ return { success: false, error: `End date: ${endsAtValidation.error}` };
359
+ }
360
+ endsAt = endsAtValidation.value;
361
+ }
362
+ // Validate customerBuys has either quantity or amount
363
+ const hasQuantity = input.customerBuys.value.quantity !== undefined;
364
+ const hasAmount = input.customerBuys.value.amount !== undefined;
365
+ if (!hasQuantity && !hasAmount) {
366
+ return {
367
+ success: false,
368
+ error: 'customerBuys must specify either quantity or amount requirement'
369
+ };
370
+ }
371
+ if (hasQuantity && hasAmount) {
372
+ return {
373
+ success: false,
374
+ error: 'customerBuys cannot specify both quantity and amount - choose one'
375
+ };
376
+ }
377
+ // Validate customerGets has discountOnQuantity
378
+ if (!input.customerGets.value.discountOnQuantity) {
379
+ return {
380
+ success: false,
381
+ error: 'customerGets must specify discountOnQuantity with quantity and effect'
382
+ };
383
+ }
384
+ try {
385
+ if (!input.automatic) {
386
+ // Code-based BXGY discount
387
+ const discountContext = {
388
+ all: "ALL" // Apply to all customers
389
+ };
390
+ const variables = {
391
+ bxgyCodeDiscount: {
392
+ title: input.title,
393
+ code: formatDiscountCode(input.code),
394
+ startsAt: startsAtValidation.value,
395
+ endsAt,
396
+ customerBuys: input.customerBuys,
397
+ customerGets: input.customerGets,
398
+ usesPerOrderLimit: input.usesPerOrderLimit,
399
+ combinesWith: input.combinesWith || {
400
+ orderDiscounts: false,
401
+ productDiscounts: false,
402
+ shippingDiscounts: false
403
+ },
404
+ context: discountContext
405
+ }
406
+ };
407
+ const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_BXGY_CREATE, variables);
408
+ // Check for GraphQL errors
409
+ if (response.errors) {
410
+ return {
411
+ success: false,
412
+ error: response.errors.map(e => e.message).join('; ')
413
+ };
414
+ }
415
+ if (!response.data) {
416
+ return { success: false, error: 'No data returned from Shopify API' };
417
+ }
418
+ if (response.data.discountCodeBxgyCreate.userErrors.length > 0) {
419
+ return {
420
+ success: false,
421
+ error: formatUserErrors(response.data.discountCodeBxgyCreate.userErrors)
422
+ };
423
+ }
424
+ const node = response.data.discountCodeBxgyCreate.codeDiscountNode;
425
+ if (!node) {
426
+ return { success: false, error: 'Failed to create BXGY code discount' };
427
+ }
428
+ return {
429
+ success: true,
430
+ data: {
431
+ id: node.id,
432
+ title: node.codeDiscount.title,
433
+ code: node.codeDiscount.codes?.edges?.[0]?.node.code,
434
+ type: 'bxgy',
435
+ automatic: false,
436
+ status: node.codeDiscount.status,
437
+ startsAt: node.codeDiscount.startsAt,
438
+ endsAt: node.codeDiscount.endsAt,
439
+ summary: node.codeDiscount.summary
440
+ }
441
+ };
442
+ }
443
+ else {
444
+ // Automatic BXGY discount
445
+ const variables = {
446
+ automaticBxgyDiscount: {
447
+ title: input.title,
448
+ startsAt: startsAtValidation.value,
449
+ endsAt,
450
+ customerBuys: input.customerBuys,
451
+ customerGets: input.customerGets,
452
+ usesPerOrderLimit: input.usesPerOrderLimit,
453
+ combinesWith: input.combinesWith || {
454
+ orderDiscounts: false,
455
+ productDiscounts: false,
456
+ shippingDiscounts: false
457
+ }
458
+ }
459
+ };
460
+ const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_BXGY_CREATE, variables);
461
+ // Check for GraphQL errors
462
+ if (response.errors) {
463
+ return {
464
+ success: false,
465
+ error: response.errors.map(e => e.message).join('; ')
466
+ };
467
+ }
468
+ if (!response.data) {
469
+ return { success: false, error: 'No data returned from Shopify API' };
470
+ }
471
+ if (response.data.discountAutomaticBxgyCreate.userErrors.length > 0) {
472
+ return {
473
+ success: false,
474
+ error: formatUserErrors(response.data.discountAutomaticBxgyCreate.userErrors)
475
+ };
476
+ }
477
+ const node = response.data.discountAutomaticBxgyCreate.automaticDiscountNode;
478
+ if (!node) {
479
+ return { success: false, error: 'Failed to create automatic BXGY discount' };
480
+ }
481
+ return {
482
+ success: true,
483
+ data: {
484
+ id: node.id,
485
+ title: node.automaticDiscount.title,
486
+ type: 'bxgy',
487
+ automatic: true,
488
+ status: node.automaticDiscount.status,
489
+ startsAt: node.automaticDiscount.startsAt,
490
+ endsAt: node.automaticDiscount.endsAt,
491
+ summary: node.automaticDiscount.summary
492
+ }
493
+ };
494
+ }
495
+ }
496
+ catch (error) {
497
+ return {
498
+ success: false,
499
+ error: error.message || 'Failed to create BXGY discount'
500
+ };
501
+ }
502
+ }
503
+ /**
504
+ * Create a free shipping discount
505
+ * Single Responsibility: Handles free shipping discount creation logic
506
+ * Dependency Inversion: Depends on ShopifyCredentials abstraction
507
+ */
508
+ export async function createFreeShippingDiscount(input, credentials) {
509
+ // Validate code (required for code discounts)
510
+ if (!input.automatic) {
511
+ if (!input.code) {
512
+ return { success: false, error: 'Discount code is required for code-based free shipping discounts' };
513
+ }
514
+ const codeValidation = validateDiscountCode(input.code);
515
+ if (!codeValidation.valid) {
516
+ return { success: false, error: codeValidation.error };
517
+ }
518
+ }
519
+ else if (input.code) {
520
+ return { success: false, error: 'Automatic discounts cannot have codes' };
521
+ }
522
+ // Validate dates
523
+ const startsAtValidation = parseDateTimeInput(input.startsAt);
524
+ if (!startsAtValidation.valid) {
525
+ return { success: false, error: `Start date: ${startsAtValidation.error}` };
526
+ }
527
+ let endsAt;
528
+ if (input.endsAt) {
529
+ const endsAtValidation = parseDateTimeInput(input.endsAt);
530
+ if (!endsAtValidation.valid) {
531
+ return { success: false, error: `End date: ${endsAtValidation.error}` };
532
+ }
533
+ endsAt = endsAtValidation.value;
534
+ }
535
+ // Build minimum requirement (optional)
536
+ let minimumRequirement = undefined;
537
+ if (input.minimumRequirement) {
538
+ if (input.minimumRequirement.subtotal) {
539
+ minimumRequirement = {
540
+ subtotal: {
541
+ greaterThanOrEqualToSubtotal: input.minimumRequirement.subtotal
542
+ }
543
+ };
544
+ }
545
+ else if (input.minimumRequirement.quantity) {
546
+ minimumRequirement = {
547
+ quantity: {
548
+ greaterThanOrEqualToQuantity: input.minimumRequirement.quantity
549
+ }
550
+ };
551
+ }
552
+ }
553
+ // Build destination (default to all countries)
554
+ let destination = { all: true };
555
+ if (input.destinationSelection) {
556
+ if (input.destinationSelection.countries && input.destinationSelection.countries.length > 0) {
557
+ destination = {
558
+ countries: {
559
+ add: input.destinationSelection.countries
560
+ }
561
+ };
562
+ }
563
+ else if (input.destinationSelection.all) {
564
+ destination = { all: true };
565
+ }
566
+ }
567
+ try {
568
+ if (!input.automatic) {
569
+ // Code-based free shipping discount
570
+ const discountContext = {
571
+ all: "ALL" // Apply to all customers
572
+ };
573
+ const variables = {
574
+ freeShippingCodeDiscount: {
575
+ title: input.title,
576
+ code: formatDiscountCode(input.code),
577
+ startsAt: startsAtValidation.value,
578
+ endsAt,
579
+ minimumRequirement,
580
+ destination,
581
+ combinesWith: input.combinesWith || {
582
+ orderDiscounts: false,
583
+ productDiscounts: false,
584
+ shippingDiscounts: false
585
+ },
586
+ context: discountContext
587
+ }
588
+ };
589
+ const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_FREE_SHIPPING_CREATE, variables);
590
+ // Check for GraphQL errors
591
+ if (response.errors) {
592
+ return {
593
+ success: false,
594
+ error: response.errors.map(e => e.message).join('; ')
595
+ };
596
+ }
597
+ if (!response.data) {
598
+ return { success: false, error: 'No data returned from Shopify API' };
599
+ }
600
+ if (response.data.discountCodeFreeShippingCreate.userErrors.length > 0) {
601
+ return {
602
+ success: false,
603
+ error: formatUserErrors(response.data.discountCodeFreeShippingCreate.userErrors)
604
+ };
605
+ }
606
+ const node = response.data.discountCodeFreeShippingCreate.codeDiscountNode;
607
+ if (!node) {
608
+ return { success: false, error: 'Failed to create free shipping code discount' };
609
+ }
610
+ return {
611
+ success: true,
612
+ data: {
613
+ id: node.id,
614
+ title: node.codeDiscount.title,
615
+ code: node.codeDiscount.codes?.edges?.[0]?.node.code,
616
+ type: 'free_shipping',
617
+ automatic: false,
618
+ status: node.codeDiscount.status,
619
+ startsAt: node.codeDiscount.startsAt,
620
+ endsAt: node.codeDiscount.endsAt,
621
+ summary: node.codeDiscount.summary
622
+ }
623
+ };
624
+ }
625
+ else {
626
+ // Automatic free shipping discount
627
+ const variables = {
628
+ freeShippingAutomaticDiscount: {
629
+ title: input.title,
630
+ startsAt: startsAtValidation.value,
631
+ endsAt,
632
+ minimumRequirement,
633
+ destination,
634
+ combinesWith: input.combinesWith || {
635
+ orderDiscounts: false,
636
+ productDiscounts: false,
637
+ shippingDiscounts: false
638
+ }
639
+ }
640
+ };
641
+ const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_FREE_SHIPPING_CREATE, variables);
642
+ // Check for GraphQL errors
643
+ if (response.errors) {
644
+ return {
645
+ success: false,
646
+ error: response.errors.map(e => e.message).join('; ')
647
+ };
648
+ }
649
+ if (!response.data) {
650
+ return { success: false, error: 'No data returned from Shopify API' };
651
+ }
652
+ if (response.data.discountAutomaticFreeShippingCreate.userErrors.length > 0) {
653
+ return {
654
+ success: false,
655
+ error: formatUserErrors(response.data.discountAutomaticFreeShippingCreate.userErrors)
656
+ };
657
+ }
658
+ const node = response.data.discountAutomaticFreeShippingCreate.automaticDiscountNode;
659
+ if (!node) {
660
+ return { success: false, error: 'Failed to create automatic free shipping discount' };
661
+ }
662
+ return {
663
+ success: true,
664
+ data: {
665
+ id: node.id,
666
+ title: node.automaticDiscount.title,
667
+ type: 'free_shipping',
668
+ automatic: true,
669
+ status: node.automaticDiscount.status,
670
+ startsAt: node.automaticDiscount.startsAt,
671
+ endsAt: node.automaticDiscount.endsAt,
672
+ summary: node.automaticDiscount.summary
673
+ }
674
+ };
675
+ }
676
+ }
677
+ catch (error) {
678
+ return {
679
+ success: false,
680
+ error: error.message || 'Failed to create free shipping discount'
681
+ };
682
+ }
683
+ }
684
+ //# sourceMappingURL=service.js.map