order-management 0.0.9 → 0.0.11
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 +542 -2
- package/.medusa/server/src/admin/index.mjs +542 -2
- package/.medusa/server/src/api/admin/returns/[id]/route.js +112 -0
- package/.medusa/server/src/api/admin/returns/[id]/status/route.js +118 -0
- package/.medusa/server/src/api/admin/returns/route.js +193 -0
- package/.medusa/server/src/api/admin/returns/validators.js +25 -0
- package/.medusa/server/src/subscribers/send-order-email.js +46 -131
- package/.medusa/server/src/types/order-management-options.js +3 -0
- package/.medusa/server/src/utils/resolve-options.js +55 -0
- package/.medusa/server/src/utils/template.js +78 -0
- package/README.md +142 -0
- package/package.json +1 -1
|
@@ -3,136 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.config = void 0;
|
|
4
4
|
exports.default = sendOrderEmailHandler;
|
|
5
5
|
const utils_1 = require("@medusajs/framework/utils");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
function formatAddress(address) {
|
|
10
|
-
const parts = [];
|
|
11
|
-
if (address.first_name || address.last_name) {
|
|
12
|
-
parts.push(`${address.first_name || ""} ${address.last_name || ""}`.trim());
|
|
13
|
-
}
|
|
14
|
-
if (address.address_1) {
|
|
15
|
-
parts.push(String(address.address_1));
|
|
16
|
-
}
|
|
17
|
-
if (address.address_2) {
|
|
18
|
-
parts.push(String(address.address_2));
|
|
19
|
-
}
|
|
20
|
-
if (address.city || address.province || address.postal_code) {
|
|
21
|
-
const cityParts = [
|
|
22
|
-
address.city,
|
|
23
|
-
address.province,
|
|
24
|
-
address.postal_code,
|
|
25
|
-
].filter(Boolean).map(String);
|
|
26
|
-
parts.push(cityParts.join(", "));
|
|
27
|
-
}
|
|
28
|
-
if (address.country_code) {
|
|
29
|
-
parts.push(String(address.country_code));
|
|
30
|
-
}
|
|
31
|
-
if (address.phone) {
|
|
32
|
-
parts.push(`Phone: ${address.phone}`);
|
|
33
|
-
}
|
|
34
|
-
return parts.length > 0 ? parts.join("<br>") : "No address provided";
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Format order data as HTML content
|
|
38
|
-
*/
|
|
39
|
-
function formatOrderDataAsHTML(orderData) {
|
|
40
|
-
const orderId = orderData.id || "N/A";
|
|
41
|
-
const orderStatus = orderData.status || "N/A";
|
|
42
|
-
const orderTotal = orderData.total || orderData.grand_total || "N/A";
|
|
43
|
-
const orderEmail = orderData.email || "N/A";
|
|
44
|
-
const orderDate = orderData.created_at || orderData.updated_at || new Date().toISOString();
|
|
45
|
-
// Format items if available
|
|
46
|
-
let itemsHTML = "";
|
|
47
|
-
if (Array.isArray(orderData.items)) {
|
|
48
|
-
itemsHTML = `
|
|
49
|
-
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
|
|
50
|
-
<thead>
|
|
51
|
-
<tr style="background-color: #f5f5f5;">
|
|
52
|
-
<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Item</th>
|
|
53
|
-
<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Quantity</th>
|
|
54
|
-
<th style="padding: 10px; text-align: right; border: 1px solid #ddd;">Price</th>
|
|
55
|
-
</tr>
|
|
56
|
-
</thead>
|
|
57
|
-
<tbody>
|
|
58
|
-
${orderData.items.map((item) => {
|
|
59
|
-
const itemData = item;
|
|
60
|
-
return `
|
|
61
|
-
<tr>
|
|
62
|
-
<td style="padding: 10px; border: 1px solid #ddd;">${itemData.title || itemData.variant_title || "Item"}</td>
|
|
63
|
-
<td style="padding: 10px; border: 1px solid #ddd;">${itemData.quantity || 0}</td>
|
|
64
|
-
<td style="padding: 10px; text-align: right; border: 1px solid #ddd;">${itemData.unit_price || itemData.total || 0}</td>
|
|
65
|
-
</tr>
|
|
66
|
-
`;
|
|
67
|
-
}).join("")}
|
|
68
|
-
</tbody>
|
|
69
|
-
</table>
|
|
70
|
-
`;
|
|
71
|
-
}
|
|
72
|
-
// Format addresses if available
|
|
73
|
-
let addressesHTML = "";
|
|
74
|
-
if (orderData.shipping_address || orderData.billing_address) {
|
|
75
|
-
addressesHTML = `
|
|
76
|
-
<div style="margin: 20px 0;">
|
|
77
|
-
${orderData.shipping_address ? `
|
|
78
|
-
<div style="margin-bottom: 15px;">
|
|
79
|
-
<h3 style="margin-bottom: 10px;">Shipping Address</h3>
|
|
80
|
-
<div style="padding: 10px; background-color: #f9f9f9; border: 1px solid #ddd;">
|
|
81
|
-
${formatAddress(orderData.shipping_address)}
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
` : ""}
|
|
85
|
-
${orderData.billing_address ? `
|
|
86
|
-
<div>
|
|
87
|
-
<h3 style="margin-bottom: 10px;">Billing Address</h3>
|
|
88
|
-
<div style="padding: 10px; background-color: #f9f9f9; border: 1px solid #ddd;">
|
|
89
|
-
${formatAddress(orderData.billing_address)}
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
` : ""}
|
|
93
|
-
</div>
|
|
94
|
-
`;
|
|
95
|
-
}
|
|
96
|
-
return `
|
|
97
|
-
<!DOCTYPE html>
|
|
98
|
-
<html>
|
|
99
|
-
<head>
|
|
100
|
-
<meta charset="UTF-8">
|
|
101
|
-
<style>
|
|
102
|
-
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
103
|
-
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
104
|
-
.header { background-color: #4CAF50; color: white; padding: 20px; text-align: center; }
|
|
105
|
-
.content { padding: 20px; background-color: #ffffff; }
|
|
106
|
-
.order-info { background-color: #f5f5f5; padding: 15px; margin: 15px 0; border-radius: 5px; }
|
|
107
|
-
.order-info p { margin: 5px 0; }
|
|
108
|
-
</style>
|
|
109
|
-
</head>
|
|
110
|
-
<body>
|
|
111
|
-
<div class="container">
|
|
112
|
-
<div class="header">
|
|
113
|
-
<h1>Order Information</h1>
|
|
114
|
-
</div>
|
|
115
|
-
<div class="content">
|
|
116
|
-
<div class="order-info">
|
|
117
|
-
<p><strong>Order ID:</strong> ${orderId}</p>
|
|
118
|
-
<p><strong>Status:</strong> ${orderStatus}</p>
|
|
119
|
-
<p><strong>Total:</strong> ${orderTotal}</p>
|
|
120
|
-
<p><strong>Email:</strong> ${orderEmail}</p>
|
|
121
|
-
<p><strong>Date:</strong> ${orderDate}</p>
|
|
122
|
-
</div>
|
|
123
|
-
${itemsHTML}
|
|
124
|
-
${addressesHTML}
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</body>
|
|
128
|
-
</html>
|
|
129
|
-
`;
|
|
130
|
-
}
|
|
6
|
+
const resolve_options_1 = require("../utils/resolve-options");
|
|
7
|
+
const template_1 = require("../utils/template");
|
|
131
8
|
/**
|
|
132
9
|
* Prepare email payload for notification service
|
|
133
10
|
*/
|
|
134
|
-
function prepareEmailPayload(to, subject,
|
|
135
|
-
const htmlContent = formatOrderDataAsHTML(orderData);
|
|
11
|
+
function prepareEmailPayload(to, subject, htmlContent) {
|
|
136
12
|
// Create plain text version from HTML
|
|
137
13
|
const textContent = htmlContent
|
|
138
14
|
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
|
@@ -149,7 +25,6 @@ function prepareEmailPayload(to, subject, orderData) {
|
|
|
149
25
|
template: htmlContent, // Some providers expect 'template' field
|
|
150
26
|
data: {
|
|
151
27
|
subject,
|
|
152
|
-
orderData,
|
|
153
28
|
html: htmlContent,
|
|
154
29
|
text: textContent,
|
|
155
30
|
},
|
|
@@ -158,10 +33,27 @@ function prepareEmailPayload(to, subject, orderData) {
|
|
|
158
33
|
}
|
|
159
34
|
/**
|
|
160
35
|
* Subscriber handler for order.placed event
|
|
161
|
-
*
|
|
36
|
+
* Sends email with order data when an order is placed (if template is configured)
|
|
162
37
|
*/
|
|
163
38
|
async function sendOrderEmailHandler({ event: { data }, container, }) {
|
|
164
39
|
try {
|
|
40
|
+
// Check if email template is configured
|
|
41
|
+
let templatePath = null;
|
|
42
|
+
try {
|
|
43
|
+
const configModule = container.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
|
|
44
|
+
const pluginOptions = (0, resolve_options_1.extractOrderManagementOptions)(configModule);
|
|
45
|
+
const options = (0, resolve_options_1.resolveOrderManagementOptions)(pluginOptions);
|
|
46
|
+
templatePath = options.email.template;
|
|
47
|
+
if (!templatePath) {
|
|
48
|
+
console.log("[Order Email Subscriber] Email template path not configured, skipping email");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// If config resolution fails, skip email sending
|
|
54
|
+
console.debug("[Order Email Subscriber] Could not resolve config, skipping email");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
165
57
|
// Extract order ID from event data
|
|
166
58
|
const orderId = data.id;
|
|
167
59
|
if (!orderId) {
|
|
@@ -207,6 +99,29 @@ async function sendOrderEmailHandler({ event: { data }, container, }) {
|
|
|
207
99
|
console.error(`[Order Email Subscriber] No valid email found for order ${orderId}`);
|
|
208
100
|
return;
|
|
209
101
|
}
|
|
102
|
+
// Prepare template data
|
|
103
|
+
const totalValue = orderData.total || orderData.grand_total;
|
|
104
|
+
const orderTotal = typeof totalValue === "number"
|
|
105
|
+
? totalValue
|
|
106
|
+
: typeof totalValue === "string"
|
|
107
|
+
? totalValue
|
|
108
|
+
: "N/A";
|
|
109
|
+
const templateData = {
|
|
110
|
+
order_id: orderData.id || "N/A",
|
|
111
|
+
order_status: orderData.status || "N/A",
|
|
112
|
+
order_total: orderTotal,
|
|
113
|
+
order_email: orderData.email || "N/A",
|
|
114
|
+
order_date: (orderData.created_at || orderData.updated_at || new Date().toISOString()),
|
|
115
|
+
order_items: orderData.items || [],
|
|
116
|
+
shipping_address: orderData.shipping_address || {},
|
|
117
|
+
billing_address: orderData.billing_address || {},
|
|
118
|
+
};
|
|
119
|
+
// Load and render template
|
|
120
|
+
const renderedTemplate = (0, template_1.loadAndRenderTemplate)(templatePath, templateData);
|
|
121
|
+
if (!renderedTemplate) {
|
|
122
|
+
console.error(`[Order Email Subscriber] Failed to load or render template from ${templatePath}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
210
125
|
// Resolve notification service
|
|
211
126
|
const notificationService = container.resolve(utils_1.Modules.NOTIFICATION);
|
|
212
127
|
if (!notificationService) {
|
|
@@ -215,7 +130,7 @@ async function sendOrderEmailHandler({ event: { data }, container, }) {
|
|
|
215
130
|
}
|
|
216
131
|
// Prepare email payload
|
|
217
132
|
const subject = "Order Confirmation";
|
|
218
|
-
const payload = prepareEmailPayload(email, subject,
|
|
133
|
+
const payload = prepareEmailPayload(email, subject, renderedTemplate);
|
|
219
134
|
// Send email
|
|
220
135
|
try {
|
|
221
136
|
if (typeof notificationService.createNotifications === "function") {
|
|
@@ -248,4 +163,4 @@ async function sendOrderEmailHandler({ event: { data }, container, }) {
|
|
|
248
163
|
exports.config = {
|
|
249
164
|
event: "order.placed",
|
|
250
165
|
};
|
|
251
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
166
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZC1vcmRlci1lbWFpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zdWJzY3JpYmVycy9zZW5kLW9yZGVyLWVtYWlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQWdFQSx3Q0FnSkM7QUEvTUQscURBQThFO0FBRTlFLDhEQUdpQztBQUNqQyxnREFBaUY7QUFPakY7O0dBRUc7QUFDSCxTQUFTLG1CQUFtQixDQUMxQixFQUFVLEVBQ1YsT0FBZSxFQUNmLFdBQW1CO0lBRW5CLHNDQUFzQztJQUN0QyxNQUFNLFdBQVcsR0FBRyxXQUFXO1NBQzVCLE9BQU8sQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLENBQUMsbUJBQW1CO1NBQzNDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUMsc0JBQXNCO1NBQzNDLElBQUksRUFBRSxDQUFBO0lBRVQsTUFBTSxPQUFPLEdBYVQ7UUFDRixFQUFFO1FBQ0YsT0FBTyxFQUFFLE9BQU87UUFDaEIsT0FBTztRQUNQLHVDQUF1QztRQUN2QyxJQUFJLEVBQUUsV0FBVztRQUNqQixJQUFJLEVBQUUsV0FBVztRQUNqQixJQUFJLEVBQUUsV0FBVyxFQUFFLHFDQUFxQztRQUN4RCxRQUFRLEVBQUUsV0FBVyxFQUFFLHlDQUF5QztRQUNoRSxJQUFJLEVBQUU7WUFDSixPQUFPO1lBQ1AsSUFBSSxFQUFFLFdBQVc7WUFDakIsSUFBSSxFQUFFLFdBQVc7U0FDbEI7S0FDRixDQUFBO0lBRUQsT0FBTyxPQUFPLENBQUE7QUFDaEIsQ0FBQztBQUVEOzs7R0FHRztBQUNZLEtBQUssVUFBVSxxQkFBcUIsQ0FBQyxFQUNsRCxLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsRUFDZixTQUFTLEdBQzRCO0lBQ3JDLElBQUksQ0FBQztRQUNILHdDQUF3QztRQUN4QyxJQUFJLFlBQVksR0FBa0IsSUFBSSxDQUFBO1FBQ3RDLElBQUksQ0FBQztZQUNILE1BQU0sWUFBWSxHQUFHLFNBQVMsQ0FB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
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JkZXItbWFuYWdlbWVudC1vcHRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3R5cGVzL29yZGVyLW1hbmFnZW1lbnQtb3B0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveOrderManagementOptions = resolveOrderManagementOptions;
|
|
4
|
+
exports.extractOrderManagementOptions = extractOrderManagementOptions;
|
|
5
|
+
const DEFAULT_OPTIONS = {
|
|
6
|
+
email: {
|
|
7
|
+
template: null,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Resolve and normalize plugin options with defaults
|
|
12
|
+
*/
|
|
13
|
+
function resolveOrderManagementOptions(options) {
|
|
14
|
+
if (!options) {
|
|
15
|
+
return DEFAULT_OPTIONS;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
email: {
|
|
19
|
+
template: options.email?.template ?? DEFAULT_OPTIONS.email.template,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract plugin options from config module
|
|
25
|
+
*/
|
|
26
|
+
function extractOrderManagementOptions(configModule) {
|
|
27
|
+
// Try from projectConfig.plugins first
|
|
28
|
+
const plugins = configModule?.projectConfig?.plugins ?? [];
|
|
29
|
+
for (const plugin of plugins) {
|
|
30
|
+
if (typeof plugin === "string") {
|
|
31
|
+
if (plugin === "order-management") {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (plugin?.resolve === "order-management") {
|
|
37
|
+
return plugin.options;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Try from direct config.plugins (fallback)
|
|
41
|
+
const directPlugins = configModule?.plugins ?? [];
|
|
42
|
+
for (const plugin of directPlugins) {
|
|
43
|
+
if (typeof plugin === "string") {
|
|
44
|
+
if (plugin === "order-management") {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (plugin?.resolve === "order-management") {
|
|
50
|
+
return plugin.options;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1vcHRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3Jlc29sdmUtb3B0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWlCQSxzRUFZQztBQUtELHNFQW9DQztBQTlERCxNQUFNLGVBQWUsR0FBbUM7SUFDdEQsS0FBSyxFQUFFO1FBQ0wsUUFBUSxFQUFFLElBQUk7S0FDZjtDQUNGLENBQUE7QUFFRDs7R0FFRztBQUNILFNBQWdCLDZCQUE2QixDQUMzQyxPQUE2QztJQUU3QyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixPQUFPLGVBQWUsQ0FBQTtJQUN4QixDQUFDO0lBRUQsT0FBTztRQUNMLEtBQUssRUFBRTtZQUNMLFFBQVEsRUFBRSxPQUFPLENBQUMsS0FBSyxFQUFFLFFBQVEsSUFBSSxlQUFlLENBQUMsS0FBSyxDQUFDLFFBQVE7U0FDcEU7S0FDRixDQUFBO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IsNkJBQTZCLENBQzNDLFlBQWtCO0lBRWxCLHVDQUF1QztJQUN2QyxNQUFNLE9BQU8sR0FBRyxZQUFZLEVBQUUsYUFBYSxFQUFFLE9BQU8sSUFBSSxFQUFFLENBQUE7SUFFMUQsS0FBSyxNQUFNLE1BQU0sSUFBSSxPQUFPLEVBQUUsQ0FBQztRQUM3QixJQUFJLE9BQU8sTUFBTSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQy9CLElBQUksTUFBTSxLQUFLLGtCQUFrQixFQUFFLENBQUM7Z0JBQ2xDLE9BQU8sRUFBRSxDQUFBO1lBQ1gsQ0FBQztZQUNELFNBQVE7UUFDVixDQUFDO1FBRUQsSUFBSSxNQUFNLEVBQUUsT0FBTyxLQUFLLGtCQUFrQixFQUFFLENBQUM7WUFDM0MsT0FBTyxNQUFNLENBQUMsT0FBdUMsQ0FBQTtRQUN2RCxDQUFDO0lBQ0gsQ0FBQztJQUVELDRDQUE0QztJQUM1QyxNQUFNLGFBQWEsR0FBSSxZQUFvQixFQUFFLE9BQU8sSUFBSSxFQUFFLENBQUE7SUFFMUQsS0FBSyxNQUFNLE1BQU0sSUFBSSxhQUFhLEVBQUUsQ0FBQztRQUNuQyxJQUFJLE9BQU8sTUFBTSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQy9CLElBQUksTUFBTSxLQUFLLGtCQUFrQixFQUFFLENBQUM7Z0JBQ2xDLE9BQU8sRUFBRSxDQUFBO1lBQ1gsQ0FBQztZQUNELFNBQVE7UUFDVixDQUFDO1FBRUQsSUFBSSxNQUFNLEVBQUUsT0FBTyxLQUFLLGtCQUFrQixFQUFFLENBQUM7WUFDM0MsT0FBTyxNQUFNLENBQUMsT0FBdUMsQ0FBQTtRQUN2RCxDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sU0FBUyxDQUFBO0FBQ2xCLENBQUMifQ==
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadAndRenderTemplate = loadAndRenderTemplate;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Load and render template from file path
|
|
41
|
+
* Supports HTML templates for email notifications
|
|
42
|
+
*/
|
|
43
|
+
function loadAndRenderTemplate(templatePath, data) {
|
|
44
|
+
try {
|
|
45
|
+
// Use the exact path provided (can be relative or absolute)
|
|
46
|
+
const fullPath = path.isAbsolute(templatePath)
|
|
47
|
+
? templatePath
|
|
48
|
+
: path.join(process.cwd(), templatePath);
|
|
49
|
+
if (!fs.existsSync(fullPath)) {
|
|
50
|
+
throw new Error(`Template file not found: ${fullPath}`);
|
|
51
|
+
}
|
|
52
|
+
let templateContent = fs.readFileSync(fullPath, "utf-8");
|
|
53
|
+
// Replace template variables {{variable_name}} with actual values
|
|
54
|
+
Object.keys(data).forEach((key) => {
|
|
55
|
+
const value = data[key];
|
|
56
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
57
|
+
if (value !== null && value !== undefined) {
|
|
58
|
+
if (typeof value === "object") {
|
|
59
|
+
templateContent = templateContent.replace(regex, JSON.stringify(value));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
templateContent = templateContent.replace(regex, String(value));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
templateContent = templateContent.replace(regex, "");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// Clean up any remaining placeholders
|
|
70
|
+
templateContent = templateContent.replace(/\{\{[\w]+\}\}/g, "");
|
|
71
|
+
return templateContent;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error(`[order-management] Failed to load template from ${templatePath}:`, error);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVtcGxhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvdGVtcGxhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFtQkEsc0RBMkNDO0FBOURELHVDQUF3QjtBQUN4QiwyQ0FBNEI7QUFjNUI7OztHQUdHO0FBQ0gsU0FBZ0IscUJBQXFCLENBQ25DLFlBQW9CLEVBQ3BCLElBQXVCO0lBRXZCLElBQUksQ0FBQztRQUNILDREQUE0RDtRQUM1RCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLFlBQVksQ0FBQztZQUM1QyxDQUFDLENBQUMsWUFBWTtZQUNkLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxZQUFZLENBQUMsQ0FBQTtRQUUxQyxJQUFJLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQzdCLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLFFBQVEsRUFBRSxDQUFDLENBQUE7UUFDekQsQ0FBQztRQUVELElBQUksZUFBZSxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFBO1FBRXhELGtFQUFrRTtRQUNsRSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFO1lBQ2hDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxHQUE4QixDQUFDLENBQUE7WUFDbEQsTUFBTSxLQUFLLEdBQUcsSUFBSSxNQUFNLENBQUMsU0FBUyxHQUFHLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQTtZQUVuRCxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksS0FBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUMxQyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO29CQUM5QixlQUFlLEdBQUcsZUFBZSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO2dCQUN6RSxDQUFDO3FCQUFNLENBQUM7b0JBQ04sZUFBZSxHQUFHLGVBQWUsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO2dCQUNqRSxDQUFDO1lBQ0gsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLGVBQWUsR0FBRyxlQUFlLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQTtZQUN0RCxDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUE7UUFFRixzQ0FBc0M7UUFDdEMsZUFBZSxHQUFHLGVBQWUsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFFL0QsT0FBTyxlQUFlLENBQUE7SUFDeEIsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixPQUFPLENBQUMsS0FBSyxDQUNYLG1EQUFtRCxZQUFZLEdBQUcsRUFDbEUsS0FBSyxDQUNOLENBQUE7UUFDRCxPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7QUFDSCxDQUFDIn0=
|
package/README.md
CHANGED
|
@@ -36,6 +36,148 @@
|
|
|
36
36
|
|
|
37
37
|
This starter is compatible with versions >= 2.4.0 of `@medusajs/medusa`.
|
|
38
38
|
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **Order Management**: Cancel and reorder functionality for orders
|
|
42
|
+
- **Order Confirmation Emails**: Automatically sends email notifications when orders are placed (requires template configuration)
|
|
43
|
+
- **Return Orders Admin Panel**: Complete return orders management section in the Medusa Admin Panel with list view, filtering, search, detail pages, and status management
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
The plugin can be configured in your `medusa-config.js` file:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
module.exports = defineConfig({
|
|
51
|
+
// ...
|
|
52
|
+
plugins: [
|
|
53
|
+
{
|
|
54
|
+
resolve: "order-management",
|
|
55
|
+
options: {
|
|
56
|
+
email: {
|
|
57
|
+
template: "src/templates/emails/order-confirmation.html", // Path to HTML template file
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Configuration Options
|
|
66
|
+
|
|
67
|
+
| Option | Type | Default | Description |
|
|
68
|
+
|--------|------|---------|-------------|
|
|
69
|
+
| `email.template` | `string \| null` | `null` | Path to HTML template file for order confirmation emails (relative to project root or absolute path) |
|
|
70
|
+
|
|
71
|
+
**Note**: Order confirmation emails are only sent if a template path is provided. If no template is configured, emails will not be sent.
|
|
72
|
+
|
|
73
|
+
## Return Orders Admin Panel
|
|
74
|
+
|
|
75
|
+
The plugin includes a dedicated section in the Medusa Admin Panel for managing customer return orders. This feature provides administrators with comprehensive tools to view, search, filter, and manage all return orders.
|
|
76
|
+
|
|
77
|
+
### Features
|
|
78
|
+
|
|
79
|
+
- **List View**: View all return orders in a table format with key information
|
|
80
|
+
- **Search**: Search returns by return ID, order ID, or customer email
|
|
81
|
+
- **Filtering**: Filter returns by status (requested, received, requires_action, completed, canceled)
|
|
82
|
+
- **Detail Pages**: View detailed information for each return order including:
|
|
83
|
+
- Return information (ID, status, refund amount, reason, note)
|
|
84
|
+
- Related order information (order ID, customer, totals)
|
|
85
|
+
- Return items with quantities
|
|
86
|
+
- Status history timeline
|
|
87
|
+
- Metadata
|
|
88
|
+
- **Status Management**: Update return status with validation and status history tracking
|
|
89
|
+
- **Pagination**: Load more returns with pagination support
|
|
90
|
+
|
|
91
|
+
### Accessing Return Orders
|
|
92
|
+
|
|
93
|
+
Once the plugin is installed and configured, you can access the Return Orders section from the Admin Panel sidebar. The section appears as "Return Orders" with a return icon.
|
|
94
|
+
|
|
95
|
+
### API Endpoints
|
|
96
|
+
|
|
97
|
+
The plugin provides the following admin API endpoints for return orders management:
|
|
98
|
+
|
|
99
|
+
- `GET /admin/returns` - List all return orders with filtering, search, and pagination
|
|
100
|
+
- `GET /admin/returns/:id` - Get detailed information for a specific return order
|
|
101
|
+
- `POST /admin/returns/:id/status` - Update the status of a return order
|
|
102
|
+
|
|
103
|
+
### Return Statuses
|
|
104
|
+
|
|
105
|
+
The plugin supports the following return statuses:
|
|
106
|
+
|
|
107
|
+
- `requested` - Return has been requested by the customer
|
|
108
|
+
- `received` - Return items have been received
|
|
109
|
+
- `requires_action` - Return requires manual intervention
|
|
110
|
+
- `completed` - Return has been completed
|
|
111
|
+
- `canceled` - Return has been canceled
|
|
112
|
+
|
|
113
|
+
Status updates are tracked in the return's metadata with timestamps and admin user IDs.
|
|
114
|
+
|
|
115
|
+
## Email Templates
|
|
116
|
+
|
|
117
|
+
The plugin supports custom HTML templates for order confirmation emails. Templates use variable replacement with `{{variable_name}}` syntax.
|
|
118
|
+
|
|
119
|
+
### Creating Templates
|
|
120
|
+
|
|
121
|
+
Create HTML template files for email notifications. Place them in your project, for example:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
your-project/
|
|
125
|
+
src/
|
|
126
|
+
templates/
|
|
127
|
+
emails/
|
|
128
|
+
order-confirmation.html
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Example Email Template** (`src/templates/emails/order-confirmation.html`):
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<!DOCTYPE html>
|
|
135
|
+
<html>
|
|
136
|
+
<head>
|
|
137
|
+
<meta charset="UTF-8">
|
|
138
|
+
<style>
|
|
139
|
+
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
140
|
+
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
141
|
+
.header { background-color: #4CAF50; color: white; padding: 20px; text-align: center; }
|
|
142
|
+
.content { padding: 20px; background-color: #ffffff; }
|
|
143
|
+
.order-info { background-color: #f5f5f5; padding: 15px; margin: 15px 0; border-radius: 5px; }
|
|
144
|
+
</style>
|
|
145
|
+
</head>
|
|
146
|
+
<body>
|
|
147
|
+
<div class="container">
|
|
148
|
+
<div class="header">
|
|
149
|
+
<h1>Order Confirmation</h1>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="content">
|
|
152
|
+
<div class="order-info">
|
|
153
|
+
<p><strong>Order ID:</strong> {{order_id}}</p>
|
|
154
|
+
<p><strong>Status:</strong> {{order_status}}</p>
|
|
155
|
+
<p><strong>Total:</strong> {{order_total}}</p>
|
|
156
|
+
<p><strong>Email:</strong> {{order_email}}</p>
|
|
157
|
+
<p><strong>Date:</strong> {{order_date}}</p>
|
|
158
|
+
</div>
|
|
159
|
+
<p>Thank you for your order!</p>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</body>
|
|
163
|
+
</html>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Template Variables
|
|
167
|
+
|
|
168
|
+
The following variables are available in order confirmation email templates:
|
|
169
|
+
|
|
170
|
+
| Variable | Description | Example |
|
|
171
|
+
|----------|-------------|---------|
|
|
172
|
+
| `{{order_id}}` | Order ID | `order_123` |
|
|
173
|
+
| `{{order_status}}` | Order status | `pending` |
|
|
174
|
+
| `{{order_total}}` | Order total amount | `99.99` |
|
|
175
|
+
| `{{order_email}}` | Customer email address | `customer@example.com` |
|
|
176
|
+
| `{{order_date}}` | Order creation date | `2024-01-15T10:30:00Z` |
|
|
177
|
+
| `{{order_items}}` | Order items array (JSON stringified) | `[{"title":"Product","quantity":1}]` |
|
|
178
|
+
| `{{shipping_address}}` | Shipping address object (JSON stringified) | `{"first_name":"John",...}` |
|
|
179
|
+
| `{{billing_address}}` | Billing address object (JSON stringified) | `{"first_name":"John",...}` |
|
|
180
|
+
|
|
39
181
|
## Getting Started
|
|
40
182
|
|
|
41
183
|
Visit the [Quickstart Guide](https://docs.medusajs.com/learn/installation) to set up a server.
|