order-management 0.0.17 → 0.0.19

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 (48) hide show
  1. package/.medusa/server/src/admin/index.js +644 -11
  2. package/.medusa/server/src/admin/index.mjs +644 -11
  3. package/.medusa/server/src/api/admin/swaps/[id]/approve/route.js +74 -0
  4. package/.medusa/server/src/api/admin/swaps/[id]/process-payment/route.js +152 -0
  5. package/.medusa/server/src/api/admin/swaps/[id]/reject/route.js +43 -0
  6. package/.medusa/server/src/api/admin/swaps/[id]/route.js +70 -0
  7. package/.medusa/server/src/api/admin/swaps/[id]/status/route.js +45 -0
  8. package/.medusa/server/src/api/admin/swaps/[id]/sync/route.js +148 -0
  9. package/.medusa/server/src/api/admin/swaps/route.js +51 -0
  10. package/.medusa/server/src/api/admin/swaps/validators.js +22 -0
  11. package/.medusa/server/src/api/store/guest-orders/[id]/invoice/route.js +17 -5
  12. package/.medusa/server/src/api/store/guest-orders/[id]/returns/route.js +17 -5
  13. package/.medusa/server/src/api/store/guest-orders/[id]/route.js +17 -5
  14. package/.medusa/server/src/api/store/guest-orders/route.js +17 -5
  15. package/.medusa/server/src/api/store/orders/[order_id]/swaps/route.js +107 -0
  16. package/.medusa/server/src/api/store/otp/request/route.js +12 -5
  17. package/.medusa/server/src/api/store/swaps/[id]/cancel/route.js +64 -0
  18. package/.medusa/server/src/api/store/swaps/[id]/route.js +112 -0
  19. package/.medusa/server/src/api/store/swaps/route.js +117 -0
  20. package/.medusa/server/src/helpers/index.js +18 -0
  21. package/.medusa/server/src/helpers/swaps.js +88 -0
  22. package/.medusa/server/src/modules/swap/index.js +13 -0
  23. package/.medusa/server/src/modules/swap/migrations/Migration20260121164326.js +49 -0
  24. package/.medusa/server/src/modules/swap/models/swap.js +21 -0
  25. package/.medusa/server/src/modules/swap/service.js +224 -0
  26. package/.medusa/server/src/services/otp-service.js +16 -5
  27. package/.medusa/server/src/subscribers/send-order-email.js +27 -8
  28. package/.medusa/server/src/workflows/index.js +8 -2
  29. package/.medusa/server/src/workflows/steps/swap/calculate-difference-step.js +56 -0
  30. package/.medusa/server/src/workflows/steps/swap/create-medusa-exchange-step.js +71 -0
  31. package/.medusa/server/src/workflows/steps/swap/create-medusa-return-step.js +79 -0
  32. package/.medusa/server/src/workflows/steps/swap/create-swap-step.js +29 -0
  33. package/.medusa/server/src/workflows/steps/swap/handle-payment-difference-step.js +102 -0
  34. package/.medusa/server/src/workflows/steps/swap/index.js +26 -0
  35. package/.medusa/server/src/workflows/steps/swap/retrieve-swap-step.js +26 -0
  36. package/.medusa/server/src/workflows/steps/swap/sync-medusa-status-step.js +132 -0
  37. package/.medusa/server/src/workflows/steps/swap/update-swap-status-step.js +25 -0
  38. package/.medusa/server/src/workflows/steps/swap/validate-eligibility-step.js +25 -0
  39. package/.medusa/server/src/workflows/steps/swap/validate-order-step.js +69 -0
  40. package/.medusa/server/src/workflows/steps/swap/validate-swap-items-step.js +41 -0
  41. package/.medusa/server/src/workflows/swaps/approve-swap-workflow.js +22 -0
  42. package/.medusa/server/src/workflows/swaps/create-swap-workflow.js +52 -0
  43. package/.medusa/server/src/workflows/swaps/execute-swap-workflow.js +36 -0
  44. package/.medusa/server/src/workflows/swaps/types.js +3 -0
  45. package/.medusa/server/src/workflows/swaps/update-swap-status-workflow.js +23 -0
  46. package/README.md +208 -0
  47. package/package.json +1 -1
  48. package/.medusa/server/src/utils/resolve-options.js +0 -101
package/README.md CHANGED
@@ -43,6 +43,7 @@ This starter is compatible with versions >= 2.4.0 of `@medusajs/medusa`.
43
43
  - **Order Confirmation Emails**: Automatically sends email notifications when orders are placed (with "Claim Order" support for registered users)
44
44
  - **Return Orders Admin Panel**: Complete return orders management section in the Medusa Admin Panel with list view, filtering, search, detail pages, and status management
45
45
  - **Guest Returns & Invoices**: Secure endpoints for guest users to initiate returns and download invoices
46
+ - **Customer-Initiated Swaps**: Complete swap/exchange functionality allowing customers to request item swaps directly from the storefront with full status lifecycle management
46
47
 
47
48
  ## Configuration
48
49
 
@@ -244,6 +245,213 @@ The following variables are available in order confirmation email templates:
244
245
  | `{{is_registered}}` | Whether the customer email has a registered account | `true` |
245
246
  | `{{claim_link}}` | Link for registered users to claim their guest order | `http://.../claim?order_id=...` |
246
247
 
248
+ ## Customer-Initiated Swaps
249
+
250
+ The plugin includes a complete swap/exchange feature that allows customers to request item swaps directly from the storefront. This extends Medusa v2's native OrderExchange functionality with customer-facing APIs and admin management tools.
251
+
252
+ ### Features
253
+
254
+ - **Customer-Initiated Swaps**: Customers can request swaps for their orders directly from the storefront
255
+ - **Complete Status Lifecycle**: Full status tracking from `requested` → `approved` → `return_started` → `return_shipped` → `return_received` → `new_items_shipped` → `completed`
256
+ - **Admin Management**: Complete admin panel for viewing, approving, rejecting, and managing swaps
257
+ - **Price Difference Calculation**: Automatic calculation of price differences between returned and new items
258
+ - **Status History**: Complete audit trail of all status changes
259
+
260
+ ### Swap Status Flow
261
+
262
+ ```
263
+ requested → approved → return_started → return_shipped → return_received → new_items_shipped → completed
264
+ ↘ rejected
265
+ ↘ cancelled (from any status except completed)
266
+ ```
267
+
268
+ ### Storefront API Endpoints
269
+
270
+ #### Create Swap Request
271
+
272
+ ```bash
273
+ POST /store/swaps
274
+ Authorization: Bearer <customer_token>
275
+ Content-Type: application/json
276
+
277
+ {
278
+ "order_id": "order_123",
279
+ "return_items": [
280
+ {
281
+ "id": "item_123",
282
+ "quantity": 1,
283
+ "reason": "Wrong size"
284
+ }
285
+ ],
286
+ "new_items": [
287
+ {
288
+ "variant_id": "variant_456",
289
+ "quantity": 1
290
+ }
291
+ ],
292
+ "reason": "Size exchange",
293
+ "note": "Please send size M instead"
294
+ }
295
+ ```
296
+
297
+ #### List Customer Swaps
298
+
299
+ ```bash
300
+ GET /store/swaps
301
+ Authorization: Bearer <customer_token>
302
+ ```
303
+
304
+ #### Get Swap Details
305
+
306
+ ```bash
307
+ GET /store/swaps/{swap_id}
308
+ Authorization: Bearer <customer_token>
309
+ ```
310
+
311
+ #### List Swaps for Order
312
+
313
+ ```bash
314
+ GET /store/orders/{order_id}/swaps
315
+ Authorization: Bearer <customer_token>
316
+ ```
317
+
318
+ #### Create Swap for Order
319
+
320
+ ```bash
321
+ POST /store/orders/{order_id}/swaps
322
+ Authorization: Bearer <customer_token>
323
+ Content-Type: application/json
324
+
325
+ {
326
+ "return_items": [
327
+ {
328
+ "id": "item_123",
329
+ "quantity": 1
330
+ }
331
+ ],
332
+ "new_items": [
333
+ {
334
+ "variant_id": "variant_456",
335
+ "quantity": 1
336
+ }
337
+ ],
338
+ "reason": "Size exchange"
339
+ }
340
+ ```
341
+
342
+ #### Cancel Swap
343
+
344
+ ```bash
345
+ POST /store/swaps/{swap_id}/cancel
346
+ Authorization: Bearer <customer_token>
347
+ ```
348
+
349
+ ### Admin API Endpoints
350
+
351
+ #### List All Swaps
352
+
353
+ ```bash
354
+ GET /admin/swaps?status=requested&order_id=order_123&limit=50&offset=0
355
+ ```
356
+
357
+ #### Get Swap Details
358
+
359
+ ```bash
360
+ GET /admin/swaps/{swap_id}
361
+ ```
362
+
363
+ #### Approve Swap
364
+
365
+ ```bash
366
+ POST /admin/swaps/{swap_id}/approve
367
+ ```
368
+
369
+ #### Reject Swap
370
+
371
+ ```bash
372
+ POST /admin/swaps/{swap_id}/reject
373
+ Content-Type: application/json
374
+
375
+ {
376
+ "reason": "Item out of stock"
377
+ }
378
+ ```
379
+
380
+ #### Update Swap Status
381
+
382
+ ```bash
383
+ POST /admin/swaps/{swap_id}/status
384
+ Content-Type: application/json
385
+
386
+ {
387
+ "status": "return_started",
388
+ "metadata": {
389
+ "return_label_id": "label_123"
390
+ }
391
+ }
392
+ ```
393
+
394
+ ### Admin UI
395
+
396
+ The plugin includes a complete admin UI for managing swaps:
397
+
398
+ - **Swaps List Page**: View all swaps with filtering by status, search by order ID, and pagination
399
+ - **Swap Detail Page**: View complete swap information including:
400
+ - Swap details (ID, status, dates, price difference)
401
+ - Original order information
402
+ - Return items list
403
+ - New items list
404
+ - Status history timeline
405
+ - Action buttons (approve/reject/update status)
406
+
407
+ Access the Swaps section from the Admin Panel sidebar.
408
+
409
+ ### Storefront Helpers
410
+
411
+ The plugin provides helper methods for easy integration:
412
+
413
+ ```typescript
414
+ import { createSwapRequest, getSwaps, getSwap, cancelSwap } from "order-management/helpers"
415
+
416
+ // Create a swap request
417
+ const swap = await createSwapRequest({
418
+ orderId: "order_123",
419
+ returnItems: [
420
+ { id: "item_123", quantity: 1, reason: "Wrong size" }
421
+ ],
422
+ newItems: [
423
+ { variant_id: "variant_456", quantity: 1 }
424
+ ],
425
+ reason: "Size exchange",
426
+ note: "Please send size M"
427
+ }, container)
428
+
429
+ // Get swaps for an order
430
+ const swaps = await getSwaps("order_123", container)
431
+
432
+ // Get a specific swap
433
+ const swapDetails = await getSwap("swap_123", container)
434
+
435
+ // Cancel a swap
436
+ const cancelledSwap = await cancelSwap("swap_123", container)
437
+ ```
438
+
439
+ ### Status Transitions
440
+
441
+ The swap status flow enforces valid transitions:
442
+
443
+ - **requested** → `approved`, `rejected`, `cancelled`
444
+ - **approved** → `return_started`, `cancelled`
445
+ - **return_started** → `return_shipped`, `cancelled`
446
+ - **return_shipped** → `return_received`, `cancelled`
447
+ - **return_received** → `new_items_shipped`, `cancelled`
448
+ - **new_items_shipped** → `completed`, `cancelled`
449
+ - **rejected** → (terminal state)
450
+ - **completed** → (terminal state)
451
+ - **cancelled** → (terminal state)
452
+
453
+ Invalid status transitions will be rejected with a descriptive error message.
454
+
247
455
  ## Getting Started
248
456
 
249
457
  Visit the [Quickstart Guide](https://docs.medusajs.com/learn/installation) to set up a server.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "order-management",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "A starter for Medusa plugins.",
5
5
  "author": "Medusa (https://medusajs.com)",
6
6
  "license": "MIT",
@@ -1,101 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractOrderManagementOptions = extractOrderManagementOptions;
4
- exports.resolveOrderManagementOptions = resolveOrderManagementOptions;
5
- /**
6
- * Extract plugin options from config module
7
- */
8
- function extractOrderManagementOptions(configModule) {
9
- if (!configModule || typeof configModule !== "object") {
10
- return undefined;
11
- }
12
- // Try from projectConfig.plugins first
13
- const plugins = configModule
14
- ?.projectConfig?.plugins ?? [];
15
- for (const plugin of plugins) {
16
- if (typeof plugin === "string") {
17
- if (plugin === "order-management") {
18
- return undefined;
19
- }
20
- continue;
21
- }
22
- if (plugin?.resolve === "order-management") {
23
- return plugin.options;
24
- }
25
- }
26
- // Try from direct config.plugins (fallback)
27
- const directPlugins = configModule?.plugins ?? [];
28
- for (const plugin of directPlugins) {
29
- if (typeof plugin === "string") {
30
- if (plugin === "order-management") {
31
- return undefined;
32
- }
33
- continue;
34
- }
35
- if (plugin?.resolve === "order-management") {
36
- return plugin.options;
37
- }
38
- }
39
- return undefined;
40
- }
41
- /**
42
- * Resolve plugin options with validation
43
- * Throws error if required options are missing
44
- */
45
- function resolveOrderManagementOptions(options) {
46
- if (!options) {
47
- throw new Error("order-management plugin options are required. Please configure the plugin in medusa-config.ts");
48
- }
49
- if (!options.storefrontUrl) {
50
- throw new Error("order-management: storefrontUrl is required");
51
- }
52
- if (!options.jwtSecret) {
53
- throw new Error("order-management: jwtSecret is required");
54
- }
55
- // Resolve email options
56
- const emailOptions = {
57
- orderConfirmTemplate: options.email?.orderConfirmTemplate ?? null,
58
- otpTemplate: options.email?.otpTemplate ?? null,
59
- };
60
- // Resolve SMTP config
61
- const smtpEnabled = options.smtp?.enabled ?? false;
62
- let smtpConfig;
63
- if (smtpEnabled) {
64
- if (!options.smtp?.host) {
65
- throw new Error("order-management: smtp.host is required when smtp.enabled is true");
66
- }
67
- if (!options.smtp?.port) {
68
- throw new Error("order-management: smtp.port is required when smtp.enabled is true");
69
- }
70
- if (!options.smtp?.auth?.user) {
71
- throw new Error("order-management: smtp.auth.user is required when smtp.enabled is true");
72
- }
73
- if (!options.smtp?.auth?.pass) {
74
- throw new Error("order-management: smtp.auth.pass is required when smtp.enabled is true");
75
- }
76
- smtpConfig = {
77
- enabled: true,
78
- host: options.smtp.host,
79
- port: options.smtp.port,
80
- secure: options.smtp.secure ?? true,
81
- auth: {
82
- user: options.smtp.auth.user,
83
- pass: options.smtp.auth.pass,
84
- },
85
- from: options.smtp.from || options.smtp.auth.user,
86
- };
87
- }
88
- else {
89
- smtpConfig = {
90
- enabled: false,
91
- secure: true,
92
- };
93
- }
94
- return {
95
- storefrontUrl: options.storefrontUrl,
96
- jwtSecret: options.jwtSecret,
97
- email: emailOptions,
98
- smtp: smtpConfig,
99
- };
100
- }
101
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1vcHRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3Jlc29sdmUtb3B0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUtBLHNFQTZDQztBQU1ELHNFQWlFQztBQXZIRDs7R0FFRztBQUNILFNBQWdCLDZCQUE2QixDQUMzQyxZQUFzQjtJQUV0QixJQUFJLENBQUMsWUFBWSxJQUFJLE9BQU8sWUFBWSxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3RELE9BQU8sU0FBUyxDQUFBO0lBQ2xCLENBQUM7SUFFRCx1Q0FBdUM7SUFDdkMsTUFBTSxPQUFPLEdBQUksWUFBNEQ7UUFDM0UsRUFBRSxhQUFhLEVBQUUsT0FBTyxJQUFJLEVBQUUsQ0FBQTtJQUVoQyxLQUFLLE1BQU0sTUFBTSxJQUFJLE9BQU8sRUFBRSxDQUFDO1FBQzdCLElBQUksT0FBTyxNQUFNLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDL0IsSUFBSSxNQUFNLEtBQUssa0JBQWtCLEVBQUUsQ0FBQztnQkFDbEMsT0FBTyxTQUFTLENBQUE7WUFDbEIsQ0FBQztZQUNELFNBQVE7UUFDVixDQUFDO1FBRUQsSUFBSyxNQUErQixFQUFFLE9BQU8sS0FBSyxrQkFBa0IsRUFBRSxDQUFDO1lBQ3JFLE9BQVEsTUFBcUQsQ0FBQyxPQUVqRCxDQUFBO1FBQ2YsQ0FBQztJQUNILENBQUM7SUFFRCw0Q0FBNEM7SUFDNUMsTUFBTSxhQUFhLEdBQUksWUFBd0MsRUFBRSxPQUFPLElBQUksRUFBRSxDQUFBO0lBRTlFLEtBQUssTUFBTSxNQUFNLElBQUksYUFBYSxFQUFFLENBQUM7UUFDbkMsSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUMvQixJQUFJLE1BQU0sS0FBSyxrQkFBa0IsRUFBRSxDQUFDO2dCQUNsQyxPQUFPLFNBQVMsQ0FBQTtZQUNsQixDQUFDO1lBQ0QsU0FBUTtRQUNWLENBQUM7UUFFRCxJQUFLLE1BQStCLEVB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