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.
- package/.medusa/server/src/admin/index.js +644 -11
- package/.medusa/server/src/admin/index.mjs +644 -11
- package/.medusa/server/src/api/admin/swaps/[id]/approve/route.js +74 -0
- package/.medusa/server/src/api/admin/swaps/[id]/process-payment/route.js +152 -0
- package/.medusa/server/src/api/admin/swaps/[id]/reject/route.js +43 -0
- package/.medusa/server/src/api/admin/swaps/[id]/route.js +70 -0
- package/.medusa/server/src/api/admin/swaps/[id]/status/route.js +45 -0
- package/.medusa/server/src/api/admin/swaps/[id]/sync/route.js +148 -0
- package/.medusa/server/src/api/admin/swaps/route.js +51 -0
- package/.medusa/server/src/api/admin/swaps/validators.js +22 -0
- package/.medusa/server/src/api/store/guest-orders/[id]/invoice/route.js +17 -5
- package/.medusa/server/src/api/store/guest-orders/[id]/returns/route.js +17 -5
- package/.medusa/server/src/api/store/guest-orders/[id]/route.js +17 -5
- package/.medusa/server/src/api/store/guest-orders/route.js +17 -5
- package/.medusa/server/src/api/store/orders/[order_id]/swaps/route.js +107 -0
- package/.medusa/server/src/api/store/otp/request/route.js +12 -5
- package/.medusa/server/src/api/store/swaps/[id]/cancel/route.js +64 -0
- package/.medusa/server/src/api/store/swaps/[id]/route.js +112 -0
- package/.medusa/server/src/api/store/swaps/route.js +117 -0
- package/.medusa/server/src/helpers/index.js +18 -0
- package/.medusa/server/src/helpers/swaps.js +88 -0
- package/.medusa/server/src/modules/swap/index.js +13 -0
- package/.medusa/server/src/modules/swap/migrations/Migration20260121164326.js +49 -0
- package/.medusa/server/src/modules/swap/models/swap.js +21 -0
- package/.medusa/server/src/modules/swap/service.js +224 -0
- package/.medusa/server/src/services/otp-service.js +16 -5
- package/.medusa/server/src/subscribers/send-order-email.js +27 -8
- package/.medusa/server/src/workflows/index.js +8 -2
- package/.medusa/server/src/workflows/steps/swap/calculate-difference-step.js +56 -0
- package/.medusa/server/src/workflows/steps/swap/create-medusa-exchange-step.js +71 -0
- package/.medusa/server/src/workflows/steps/swap/create-medusa-return-step.js +79 -0
- package/.medusa/server/src/workflows/steps/swap/create-swap-step.js +29 -0
- package/.medusa/server/src/workflows/steps/swap/handle-payment-difference-step.js +102 -0
- package/.medusa/server/src/workflows/steps/swap/index.js +26 -0
- package/.medusa/server/src/workflows/steps/swap/retrieve-swap-step.js +26 -0
- package/.medusa/server/src/workflows/steps/swap/sync-medusa-status-step.js +132 -0
- package/.medusa/server/src/workflows/steps/swap/update-swap-status-step.js +25 -0
- package/.medusa/server/src/workflows/steps/swap/validate-eligibility-step.js +25 -0
- package/.medusa/server/src/workflows/steps/swap/validate-order-step.js +69 -0
- package/.medusa/server/src/workflows/steps/swap/validate-swap-items-step.js +41 -0
- package/.medusa/server/src/workflows/swaps/approve-swap-workflow.js +22 -0
- package/.medusa/server/src/workflows/swaps/create-swap-workflow.js +52 -0
- package/.medusa/server/src/workflows/swaps/execute-swap-workflow.js +36 -0
- package/.medusa/server/src/workflows/swaps/types.js +3 -0
- package/.medusa/server/src/workflows/swaps/update-swap-status-workflow.js +23 -0
- package/README.md +208 -0
- package/package.json +1 -1
- 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,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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
|