@legenki/print2medusa 0.4.0 → 0.5.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.
- package/.medusa/server/src/admin/index.js +37 -1
- package/.medusa/server/src/admin/index.mjs +37 -1
- package/.medusa/server/src/utils/costs.js +109 -0
- package/.medusa/server/src/utils/order-state.js +5 -1
- package/.medusa/server/src/workflows/create-printful-order.js +23 -1
- package/README.md +22 -0
- package/package.json +1 -1
|
@@ -4,6 +4,13 @@ const adminSdk = require("@medusajs/admin-sdk");
|
|
|
4
4
|
const ui = require("@medusajs/ui");
|
|
5
5
|
const react = require("react");
|
|
6
6
|
const ATTENTION_STATES = /* @__PURE__ */ new Set(["failed", "canceled", "onhold"]);
|
|
7
|
+
const formatMinor = (amount, currency) => {
|
|
8
|
+
if (typeof amount !== "number" || !Number.isFinite(amount)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const code = typeof currency === "string" ? currency.toUpperCase() : "";
|
|
12
|
+
return `${(amount / 100).toFixed(2)}${code ? ` ${code}` : ""}`;
|
|
13
|
+
};
|
|
7
14
|
const PrintfulOrderWidget = ({ data }) => {
|
|
8
15
|
const metadata = data.metadata ?? {};
|
|
9
16
|
const printfulOrderId = metadata.printful_order_id;
|
|
@@ -14,6 +21,19 @@ const PrintfulOrderWidget = ({ data }) => {
|
|
|
14
21
|
const statusUpdatedAt = metadata.printful_status_updated_at;
|
|
15
22
|
const shipments = metadata.printful_shipments ?? [];
|
|
16
23
|
const isAttention = status ? ATTENTION_STATES.has(status) : false;
|
|
24
|
+
const costTotal = formatMinor(
|
|
25
|
+
metadata.printful_cost_total,
|
|
26
|
+
metadata.printful_cost_currency
|
|
27
|
+
);
|
|
28
|
+
const retailTotal = formatMinor(
|
|
29
|
+
metadata.printful_retail_total,
|
|
30
|
+
metadata.printful_retail_currency
|
|
31
|
+
);
|
|
32
|
+
const margin = formatMinor(
|
|
33
|
+
metadata.printful_margin,
|
|
34
|
+
metadata.printful_cost_currency
|
|
35
|
+
);
|
|
36
|
+
const currencyMismatch = costTotal !== null && retailTotal !== null && margin === null;
|
|
17
37
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
18
38
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
19
39
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Printful" }),
|
|
@@ -49,7 +69,23 @@ const PrintfulOrderWidget = ({ data }) => {
|
|
|
49
69
|
"Last synced: ",
|
|
50
70
|
new Date(statusUpdatedAt).toLocaleString()
|
|
51
71
|
] }) : null
|
|
52
|
-
] })
|
|
72
|
+
] }),
|
|
73
|
+
costTotal ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
|
|
74
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Economics" }),
|
|
75
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
|
|
76
|
+
"Printful cost: ",
|
|
77
|
+
costTotal
|
|
78
|
+
] }),
|
|
79
|
+
retailTotal ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
|
|
80
|
+
"Retail: ",
|
|
81
|
+
retailTotal
|
|
82
|
+
] }) : null,
|
|
83
|
+
margin ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", children: [
|
|
84
|
+
"Margin: ",
|
|
85
|
+
margin
|
|
86
|
+
] }) : null,
|
|
87
|
+
currencyMismatch ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Margin unavailable — Printful billed in a different currency than the order." }) : null
|
|
88
|
+
] }) : null
|
|
53
89
|
] });
|
|
54
90
|
};
|
|
55
91
|
adminSdk.defineWidgetConfig({
|
|
@@ -3,6 +3,13 @@ import { defineWidgetConfig } from "@medusajs/admin-sdk";
|
|
|
3
3
|
import { Container, Heading, Badge, Text, Button, toast } from "@medusajs/ui";
|
|
4
4
|
import { useState, useEffect } from "react";
|
|
5
5
|
const ATTENTION_STATES = /* @__PURE__ */ new Set(["failed", "canceled", "onhold"]);
|
|
6
|
+
const formatMinor = (amount, currency) => {
|
|
7
|
+
if (typeof amount !== "number" || !Number.isFinite(amount)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const code = typeof currency === "string" ? currency.toUpperCase() : "";
|
|
11
|
+
return `${(amount / 100).toFixed(2)}${code ? ` ${code}` : ""}`;
|
|
12
|
+
};
|
|
6
13
|
const PrintfulOrderWidget = ({ data }) => {
|
|
7
14
|
const metadata = data.metadata ?? {};
|
|
8
15
|
const printfulOrderId = metadata.printful_order_id;
|
|
@@ -13,6 +20,19 @@ const PrintfulOrderWidget = ({ data }) => {
|
|
|
13
20
|
const statusUpdatedAt = metadata.printful_status_updated_at;
|
|
14
21
|
const shipments = metadata.printful_shipments ?? [];
|
|
15
22
|
const isAttention = status ? ATTENTION_STATES.has(status) : false;
|
|
23
|
+
const costTotal = formatMinor(
|
|
24
|
+
metadata.printful_cost_total,
|
|
25
|
+
metadata.printful_cost_currency
|
|
26
|
+
);
|
|
27
|
+
const retailTotal = formatMinor(
|
|
28
|
+
metadata.printful_retail_total,
|
|
29
|
+
metadata.printful_retail_currency
|
|
30
|
+
);
|
|
31
|
+
const margin = formatMinor(
|
|
32
|
+
metadata.printful_margin,
|
|
33
|
+
metadata.printful_cost_currency
|
|
34
|
+
);
|
|
35
|
+
const currencyMismatch = costTotal !== null && retailTotal !== null && margin === null;
|
|
16
36
|
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
17
37
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
18
38
|
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Printful" }),
|
|
@@ -48,7 +68,23 @@ const PrintfulOrderWidget = ({ data }) => {
|
|
|
48
68
|
"Last synced: ",
|
|
49
69
|
new Date(statusUpdatedAt).toLocaleString()
|
|
50
70
|
] }) : null
|
|
51
|
-
] })
|
|
71
|
+
] }),
|
|
72
|
+
costTotal ? /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
|
|
73
|
+
/* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Economics" }),
|
|
74
|
+
/* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
|
|
75
|
+
"Printful cost: ",
|
|
76
|
+
costTotal
|
|
77
|
+
] }),
|
|
78
|
+
retailTotal ? /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
|
|
79
|
+
"Retail: ",
|
|
80
|
+
retailTotal
|
|
81
|
+
] }) : null,
|
|
82
|
+
margin ? /* @__PURE__ */ jsxs(Text, { size: "small", children: [
|
|
83
|
+
"Margin: ",
|
|
84
|
+
margin
|
|
85
|
+
] }) : null,
|
|
86
|
+
currencyMismatch ? /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Margin unavailable — Printful billed in a different currency than the order." }) : null
|
|
87
|
+
] }) : null
|
|
52
88
|
] });
|
|
53
89
|
};
|
|
54
90
|
defineWidgetConfig({
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MARGIN_KEY = exports.RETAIL_TOTAL_KEY = exports.RETAIL_CURRENCY_KEY = exports.COST_TOTAL_KEY = exports.COST_CURRENCY_KEY = void 0;
|
|
4
|
+
exports.toMinorUnits = toMinorUnits;
|
|
5
|
+
exports.planCostMetadata = planCostMetadata;
|
|
6
|
+
/**
|
|
7
|
+
* Printful reports money as JSON numbers in major units (12.34), and Medusa
|
|
8
|
+
* stores integer minor units (1234). Everything crossing that boundary goes
|
|
9
|
+
* through here.
|
|
10
|
+
*
|
|
11
|
+
* `undefined` means "we could not trust this value" and is deliberately
|
|
12
|
+
* distinct from `0`. Reporting an unparseable cost as zero would show the
|
|
13
|
+
* owner a 100% margin on an order that in fact cost them money.
|
|
14
|
+
*/
|
|
15
|
+
function toMinorUnits(value) {
|
|
16
|
+
if (value === null || value === undefined || value === "") {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
let n;
|
|
20
|
+
if (typeof value === "number") {
|
|
21
|
+
n = value;
|
|
22
|
+
}
|
|
23
|
+
else if (typeof value === "string") {
|
|
24
|
+
// Number() rejects trailing garbage that parseFloat would silently accept:
|
|
25
|
+
// Number("4.99abc") is NaN, parseFloat("4.99abc") is 4.99.
|
|
26
|
+
n = Number(value);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (!Number.isFinite(n)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return Math.round(n * 100);
|
|
35
|
+
}
|
|
36
|
+
exports.COST_CURRENCY_KEY = "printful_cost_currency";
|
|
37
|
+
exports.COST_TOTAL_KEY = "printful_cost_total";
|
|
38
|
+
exports.RETAIL_CURRENCY_KEY = "printful_retail_currency";
|
|
39
|
+
exports.RETAIL_TOTAL_KEY = "printful_retail_total";
|
|
40
|
+
exports.MARGIN_KEY = "printful_margin";
|
|
41
|
+
/** Cost fields copied verbatim, minus `currency` and `total` which are special. */
|
|
42
|
+
const COST_FIELDS = [
|
|
43
|
+
"subtotal",
|
|
44
|
+
"discount",
|
|
45
|
+
"shipping",
|
|
46
|
+
"digitization",
|
|
47
|
+
"additional_fee",
|
|
48
|
+
"fulfillment_fee",
|
|
49
|
+
"retail_delivery_fee",
|
|
50
|
+
"tax",
|
|
51
|
+
"vat",
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Build the order-metadata patch describing what an order cost and what it
|
|
55
|
+
* earned. Shaped like `planOrderStateActions` so the webhook workflow can
|
|
56
|
+
* merge it the same way.
|
|
57
|
+
*
|
|
58
|
+
* Every amount is in minor units. A field that could not be parsed is omitted
|
|
59
|
+
* rather than defaulted, so a partial Printful response never turns into a
|
|
60
|
+
* confidently wrong margin.
|
|
61
|
+
*/
|
|
62
|
+
function planCostMetadata(order) {
|
|
63
|
+
const metadata = {};
|
|
64
|
+
const costs = order.costs;
|
|
65
|
+
const retail = order.retail_costs;
|
|
66
|
+
if (!costs && !retail) {
|
|
67
|
+
return metadata;
|
|
68
|
+
}
|
|
69
|
+
const costCurrency = costs?.currency?.toLowerCase();
|
|
70
|
+
const retailCurrency = retail?.currency?.toLowerCase();
|
|
71
|
+
if (costs) {
|
|
72
|
+
if (costCurrency) {
|
|
73
|
+
metadata[exports.COST_CURRENCY_KEY] = costCurrency;
|
|
74
|
+
}
|
|
75
|
+
const total = toMinorUnits(costs.total);
|
|
76
|
+
if (total !== undefined) {
|
|
77
|
+
metadata[exports.COST_TOTAL_KEY] = total;
|
|
78
|
+
}
|
|
79
|
+
for (const field of COST_FIELDS) {
|
|
80
|
+
const value = toMinorUnits(costs[field]);
|
|
81
|
+
if (value !== undefined && value !== 0) {
|
|
82
|
+
metadata[`printful_cost_${field}`] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (retail) {
|
|
87
|
+
if (retailCurrency) {
|
|
88
|
+
metadata[exports.RETAIL_CURRENCY_KEY] = retailCurrency;
|
|
89
|
+
}
|
|
90
|
+
const total = toMinorUnits(retail.total);
|
|
91
|
+
if (total !== undefined) {
|
|
92
|
+
metadata[exports.RETAIL_TOTAL_KEY] = total;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Margin only where the subtraction is meaningful. Converting between
|
|
96
|
+
// currencies would need an exchange rate this plugin does not have, and a
|
|
97
|
+
// margin built on a guessed rate is worse than no margin at all.
|
|
98
|
+
const costTotal = metadata[exports.COST_TOTAL_KEY];
|
|
99
|
+
const retailTotal = metadata[exports.RETAIL_TOTAL_KEY];
|
|
100
|
+
if (typeof costTotal === "number" &&
|
|
101
|
+
typeof retailTotal === "number" &&
|
|
102
|
+
costCurrency &&
|
|
103
|
+
retailCurrency &&
|
|
104
|
+
costCurrency === retailCurrency) {
|
|
105
|
+
metadata[exports.MARGIN_KEY] = retailTotal - costTotal;
|
|
106
|
+
}
|
|
107
|
+
return metadata;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29zdHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvY29zdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBV0Esb0NBdUJDO0FBOEJELDRDQXdEQztBQXRIRDs7Ozs7Ozs7R0FRRztBQUNILFNBQWdCLFlBQVksQ0FDMUIsS0FBeUM7SUFFekMsSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLEtBQUssS0FBSyxTQUFTLElBQUksS0FBSyxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQzFELE9BQU8sQ0FBQyxDQUFBO0lBQ1YsQ0FBQztJQUVELElBQUksQ0FBUyxDQUFBO0lBQ2IsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUM5QixDQUFDLEdBQUcsS0FBSyxDQUFBO0lBQ1gsQ0FBQztTQUFNLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDckMsMkVBQTJFO1FBQzNFLDJEQUEyRDtRQUMzRCxDQUFDLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQ25CLENBQUM7U0FBTSxDQUFDO1FBQ04sT0FBTyxTQUFTLENBQUE7SUFDbEIsQ0FBQztJQUVELElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDeEIsT0FBTyxTQUFTLENBQUE7SUFDbEIsQ0FBQztJQUVELE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUE7QUFDNUIsQ0FBQztBQUVZLFFBQUEsaUJBQWlCLEdBQUcsd0JBQXdCLENBQUE7QUFDNUMsUUFBQSxjQUFjLEdBQUcscUJBQXFCLENBQUE7QUFDdEMsUUFBQSxtQkFBbUIsR0FBRywwQkFBMEIsQ0FBQTtBQUNoRCxRQUFBLGdCQUFnQixHQUFHLHVCQUF1QixDQUFBO0FBQzFDLFFBQUEsVUFBVSxHQUFHLGlCQUFpQixDQUFBO0FBRTNDLG1GQUFtRjtBQUNuRixNQUFNLFdB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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.planOrderStateActions = planOrderStateActions;
|
|
4
|
+
const costs_1 = require("./costs");
|
|
4
5
|
/** Printful states that mean a human should look at the order. */
|
|
5
6
|
const ATTENTION_STATES = new Set(["failed", "canceled", "onhold"]);
|
|
6
7
|
/**
|
|
@@ -35,6 +36,9 @@ function planOrderStateActions(order, recordedShipmentIds) {
|
|
|
35
36
|
return {
|
|
36
37
|
shipments: allShipments.filter((s) => !recorded.has(s.printful_shipment_id)),
|
|
37
38
|
metadata: {
|
|
39
|
+
// Spread first so a status key always wins a name collision rather than
|
|
40
|
+
// being silently overwritten by a cost key.
|
|
41
|
+
...(0, costs_1.planCostMetadata)(order),
|
|
38
42
|
printful_order_id: String(order.id),
|
|
39
43
|
printful_status: order.status,
|
|
40
44
|
printful_status_updated_at: new Date().toISOString(),
|
|
@@ -55,4 +59,4 @@ function planOrderStateActions(order, recordedShipmentIds) {
|
|
|
55
59
|
needsAttention: ATTENTION_STATES.has(order.status),
|
|
56
60
|
};
|
|
57
61
|
}
|
|
58
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
62
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JkZXItc3RhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvb3JkZXItc3RhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUE4Q0Esc0RBaURDO0FBOUZELG1DQUEwQztBQThCMUMsa0VBQWtFO0FBQ2xFLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxRQUFRLEVBQUUsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUE7QUFFbEU7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxTQUFnQixxQkFBcUIsQ0FDbkMsS0FBb0IsRUFDcEIsbUJBQTZCO0lBRTdCLE1BQU0sUUFBUSxHQUFHLElBQUksR0FBRyxDQUFDLG1CQUFtQixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFBO0lBRXpELE1BQU0sWUFBWSxHQUFzQixDQUFDLEtBQUssQ0FBQyxTQUFTLElBQUksRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQzFFLG9CQUFvQixFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ2xDLE9BQU8sRUFBRSxDQUFDLENBQUMsT0FBTztRQUNsQixPQUFPLEVBQUUsQ0FBQyxDQUFDLE9BQU87UUFDbEIsZUFBZSxFQUFFLENBQUMsQ0FBQyxlQUFlO1FBQ2xDLFlBQVksRUFBRSxDQUFDLENBQUMsWUFBWTtRQUM1QixTQUFTLEVBQUUsQ0FBQyxDQUFDLFNBQVM7UUFDdEIsVUFBVSxFQUFFLENBQUMsQ0FBQyxVQUFVO1FBQ3hCLDBFQUEwRTtRQUMxRSwyREFBMkQ7UUFDM0QsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQzFCLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQztZQUMxQixRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUM7U0FDN0IsQ0FBQyxDQUFDO0tBQ0osQ0FBQyxDQUFDLENBQUE7SUFFSCxPQUFPO1FBQ0wsU0FBUyxFQUFFLFlBQVksQ0FBQyxNQUFNLENBQzVCLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLENBQzdDO1FBQ0QsUUFBUSxFQUFFO1lBQ1Isd0VBQXdFO1lBQ3hFLDRDQUE0QztZQUM1QyxHQUFHLElBQUEsd0JBQWdCLEVBQUMsS0FBSyxDQUFDO1lBQzFCLGlCQUFpQixFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ25DLGVBQWUsRUFBRSxLQUFLLENBQUMsTUFBTTtZQUM3QiwwQkFBMEIsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRTtZQUNwRCxrQkFBa0IsRUFBRSxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dCQUMzQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLG9CQUFvQjtnQkFDMUIsT0FBTyxFQUFFLENBQUMsQ0FBQyxPQUFPO2dCQUNsQixPQUFPLEVBQUUsQ0FBQyxDQUFDLE9BQU87Z0JBQ2xCLGVBQWUsRUFBRSxDQUFDLENBQUMsZUFBZTtnQkFDbEMsWUFBWSxFQUFFLENBQUMsQ0FBQyxZQUFZO2dCQUM1QixTQUFTLEVBQUUsQ0FBQyxDQUFDLFNBQVM7Z0JBQ3RCLHdFQUF3RTtnQkFDeEUsc0VBQXNFO2dCQUN0RSxvRUFBb0U7Z0JBQ3BFLGdFQUFnRTtnQkFDaEUsVUFBVSxFQUFFLENBQUMsQ0FBQyxVQUFVLElBQUksS0FBSzthQUNsQyxDQUFDLENBQUM7U0FDSjtRQUNELGNBQWMsRUFBRSxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQztLQUNuRCxDQUFBO0FBQ0gsQ0FBQyJ9
|
|
@@ -5,9 +5,11 @@ const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
|
5
5
|
const utils_1 = require("@medusajs/framework/utils");
|
|
6
6
|
const printful_1 = require("../modules/printful");
|
|
7
7
|
const mappers_1 = require("../utils/mappers");
|
|
8
|
+
const costs_1 = require("../utils/costs");
|
|
8
9
|
const createPrintfulOrderStep = (0, workflows_sdk_1.createStep)("printful-create-order", async (input, { container }) => {
|
|
9
10
|
const printful = container.resolve(printful_1.PRINTFUL_MODULE);
|
|
10
11
|
const orderModule = container.resolve(utils_1.Modules.ORDER);
|
|
12
|
+
const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
|
|
11
13
|
const options = await printful.getOptions();
|
|
12
14
|
const client = await printful.getClient();
|
|
13
15
|
const existing = await printful.findOrderLink(input.order_id);
|
|
@@ -110,6 +112,26 @@ const createPrintfulOrderStep = (0, workflows_sdk_1.createStep)("printful-create
|
|
|
110
112
|
printful_order_id: String(pfOrder.id),
|
|
111
113
|
status: pfOrder.status || "created",
|
|
112
114
|
});
|
|
115
|
+
// Printful returns the real costs with the created order, so no separate
|
|
116
|
+
// estimate call is needed. Written best-effort: the order exists in
|
|
117
|
+
// Printful either way, and losing the margin figure must never fail the
|
|
118
|
+
// workflow and roll back a real order.
|
|
119
|
+
const costMetadata = (0, costs_1.planCostMetadata)(pfOrder);
|
|
120
|
+
if (Object.keys(costMetadata).length > 0) {
|
|
121
|
+
try {
|
|
122
|
+
// `orderRow` rather than `existing` — that name is taken by the
|
|
123
|
+
// order-link lookup earlier in this step.
|
|
124
|
+
const orderRow = await orderModule.retrieveOrder(input.order_id, {
|
|
125
|
+
select: ["id", "metadata"],
|
|
126
|
+
});
|
|
127
|
+
await orderModule.updateOrders(input.order_id, {
|
|
128
|
+
metadata: { ...(orderRow.metadata ?? {}), ...costMetadata },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
logger.error(`Printful order ${pfOrder.id}: could not store costs: ${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
113
135
|
return new workflows_sdk_1.StepResponse({
|
|
114
136
|
skipped: false,
|
|
115
137
|
printful_order_id: String(pfOrder.id),
|
|
@@ -121,4 +143,4 @@ exports.createPrintfulOrderWorkflow = (0, workflows_sdk_1.createWorkflow)("print
|
|
|
121
143
|
return new workflows_sdk_1.WorkflowResponse(result);
|
|
122
144
|
});
|
|
123
145
|
exports.default = exports.createPrintfulOrderWorkflow;
|
|
124
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
146
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLXByaW50ZnVsLW9yZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3dvcmtmbG93cy9jcmVhdGUtcHJpbnRmdWwtb3JkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEscUVBSzBDO0FBQzFDLHFEQUE4RTtBQUM5RSxrREFBcUQ7QUFPckQsOENBQW1EO0FBQ25ELDBDQUFpRDtBQWtCakQsTUFBTSx1QkFBdUIsR0FBRyxJQUFBLDBCQUFVLEVBQ3hDLHVCQUF1QixFQUN2QixLQUFLLEVBQUUsS0FBK0IsRUFBRSxFQUFFLFNBQVMsRUFBRSxFQUFFLEVBQUU7SUFDdkQsTUFBTSxRQUFRLEdBQTBCLFNBQVMsQ0FBQyxPQUFPLENBQUMsMEJBQWUsQ0FBQyxDQUFBO0lBQzFFLE1BQU0sV0FBVyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsZUFBTyxDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQ3BELE1BQU0sTUFBTSxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsTUFBTSxDQUFDLENBQUE7SUFDbEUsTUFBTSxPQUFPLEdBQUcsTUFBTSxRQUFRLENBQUMsVUFBVSxFQUFFLENBQUE7SUFDM0MsTUFBTSxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsU0FBUyxFQUFFLENBQUE7SUFFekMsTUFBTSxRQUFRLEdBQUcsTUFBTSxRQUFRLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUM3RCxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQ2IsT0FBTyxJQUFJLDRCQUFZLENBQWU7WUFDcEMsT0FBTyxFQUFFLElBQUk7WUFDYixNQUFNLEVBQUUsZ0JBQWdCO1lBQ3hCLGlCQUFpQixFQUFFLFFBQVEsQ0FBQyxpQkFBaUI7U0FDOUMsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUVELE1BQU0sS0FBSyxHQUFHLE1BQU0sV0FBVyxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsUUFBUSxF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
|
package/README.md
CHANGED
|
@@ -273,6 +273,28 @@ unpublished itself — a product you set to draft by hand stays draft. Variants
|
|
|
273
273
|
carry `printful_availability_status` in metadata, and discontinued products get
|
|
274
274
|
`printful_discontinued` unless `onDiscontinued: "ignore"`.
|
|
275
275
|
|
|
276
|
+
## Order economics
|
|
277
|
+
|
|
278
|
+
Printful returns what it charged along with the created order, so the plugin
|
|
279
|
+
stores it on the Medusa order rather than making a second API call. The order
|
|
280
|
+
page shows the Printful cost, the retail total, and the margin between them.
|
|
281
|
+
The figures are refreshed whenever a webhook re-reads the order, because
|
|
282
|
+
Printful finalizes shipping and fees at fulfillment.
|
|
283
|
+
|
|
284
|
+
Amounts are stored in minor units under `printful_cost_*`, `printful_retail_*`
|
|
285
|
+
and `printful_margin` in order metadata.
|
|
286
|
+
|
|
287
|
+
**Margin is only shown when both figures are in the same currency.** If
|
|
288
|
+
Printful bills in USD while the order is in EUR, both totals are stored and the
|
|
289
|
+
margin is withheld — converting would need an exchange rate this plugin does
|
|
290
|
+
not have, and a margin built on a guessed rate is worse than none.
|
|
291
|
+
|
|
292
|
+
The order page deliberately shows only the two totals and the margin, not the
|
|
293
|
+
per-fee breakdown. Those three are always written together from one response,
|
|
294
|
+
so they cannot disagree; the individual `printful_cost_*` fee keys are refreshed
|
|
295
|
+
per-key and a fee absent from a later response keeps its previous value, so a
|
|
296
|
+
breakdown need not sum to the total.
|
|
297
|
+
|
|
276
298
|
## Admin usage
|
|
277
299
|
|
|
278
300
|
1. Create products in Printful (Store Products).
|