@legenki/print2medusa 0.2.0 → 0.4.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.
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LEGACY_OPTION_IDS = exports.PRINTFUL_RETURN_OPTION_ID = exports.PRINTFUL_SHIPPING_METHODS = void 0;
4
+ exports.buildRateCacheKey = buildRateCacheKey;
5
+ exports.selectRate = selectRate;
6
+ exports.isAddressQuotable = isAddressQuotable;
7
+ exports.buildRateItems = buildRateItems;
8
+ const crypto_1 = require("crypto");
9
+ const mappers_1 = require("./mappers");
10
+ /**
11
+ * Shipping methods we expose as fulfillment options.
12
+ *
13
+ * These are Printful's own `ShippingInfo.id` values. The OpenAPI spec publishes
14
+ * no enum — only the example `STANDARD` — so this list comes from the recorded
15
+ * fixture and grows when a live response reveals more.
16
+ *
17
+ * The same string serves as the Medusa option id and the fallbackShippingRates
18
+ * key, so the three cannot drift apart.
19
+ */
20
+ exports.PRINTFUL_SHIPPING_METHODS = ["STANDARD"];
21
+ /**
22
+ * The return option id.
23
+ *
24
+ * Not a Printful method id: Printful's rate API quotes outbound shipping only,
25
+ * so a return has no live rate and must be priced flat by the admin.
26
+ */
27
+ exports.PRINTFUL_RETURN_OPTION_ID = "PRINTFUL_RETURN";
28
+ /**
29
+ * Option ids used before 0.3.0. Printful never returns these, so an option row
30
+ * surviving the upgrade prices at zero rather than from a live quote.
31
+ */
32
+ exports.LEGACY_OPTION_IDS = new Set([
33
+ "printful-standard",
34
+ "printful-return",
35
+ ]);
36
+ function normalize(value) {
37
+ return (value ?? "").trim().toLowerCase();
38
+ }
39
+ /**
40
+ * Cache key for a rate quote.
41
+ *
42
+ * Deliberately excludes the shipping method: Printful returns every method in
43
+ * one response, so a store offering two options costs one API call rather than
44
+ * two. `selectRate` picks the method from the cached list locally.
45
+ *
46
+ * Carts that differ only incidentally — item order, address casing, stray
47
+ * whitespace — must produce the same key, or the cache never hits.
48
+ */
49
+ function buildRateCacheKey(input) {
50
+ // JSON-encode rather than joining with delimiters: a "|" typed into an
51
+ // address field would otherwise merge two fields and collide with a
52
+ // genuinely different address.
53
+ const canonical = JSON.stringify([
54
+ normalize(input.address.country_code),
55
+ normalize(input.address.state_code),
56
+ normalize(input.address.city),
57
+ normalize(input.address.zip),
58
+ normalize(input.address.address1),
59
+ normalize(input.address.address2),
60
+ // `value` is part of the request Printful prices against — it drives
61
+ // duties on international shipments — so it must be part of the identity
62
+ // of the quote, or two carts differing only in price share one answer.
63
+ [...input.items]
64
+ .map((i) => `${i.variant_id}x${i.quantity}x${i.value ?? ""}`)
65
+ .sort(),
66
+ normalize(input.currency),
67
+ ]);
68
+ return (0, crypto_1.createHash)("sha256").update(canonical).digest("hex");
69
+ }
70
+ /**
71
+ * Pick one method out of a quote list.
72
+ *
73
+ * Printful sends `rate` as a decimal string; `parsePriceToMinorUnits` rounds it
74
+ * correctly, where `parseFloat("4.99") * 100` would yield 498.99999999999994.
75
+ *
76
+ * A quote in a currency the cart cannot use is rejected rather than converted —
77
+ * we never source an exchange rate ourselves.
78
+ */
79
+ function selectRate(rates, methodId, cartCurrency) {
80
+ const match = rates.find((r) => r.id === methodId);
81
+ if (!match) {
82
+ return { ok: false, reason: "method_unavailable" };
83
+ }
84
+ const quoteCurrency = match.currency.trim().toUpperCase();
85
+ const wantCurrency = cartCurrency.trim().toUpperCase();
86
+ if (!quoteCurrency || !wantCurrency || quoteCurrency !== wantCurrency) {
87
+ return { ok: false, reason: "currency_mismatch" };
88
+ }
89
+ // parseFloat stops at the first invalid character, so "4.99abc" would parse
90
+ // as 4.99 and "-4.99" as a negative price. Require the whole string to be a
91
+ // non-negative decimal; "0.00" is a legitimate free method and must pass.
92
+ if (!/^\d+(\.\d+)?$/.test(match.rate.trim())) {
93
+ return { ok: false, reason: "method_unavailable" };
94
+ }
95
+ return { ok: true, amount: (0, mappers_1.parsePriceToMinorUnits)(match.rate) };
96
+ }
97
+ /** Countries where Printful requires a state code alongside the country. */
98
+ const STATE_REQUIRED_COUNTRIES = new Set(["US", "AU", "CA"]);
99
+ /**
100
+ * Whether an address carries enough for Printful to quote.
101
+ *
102
+ * `calculatePrice` runs on every cart refresh, long before a shipping address
103
+ * exists. Calling the API anyway means a 400 on every storefront visit, so an
104
+ * incomplete address short-circuits to the fallback instead.
105
+ */
106
+ function isAddressQuotable(address) {
107
+ const country = (address.country_code ?? "").trim().toUpperCase();
108
+ if (!country) {
109
+ return false;
110
+ }
111
+ if (STATE_REQUIRED_COUNTRIES.has(country)) {
112
+ return Boolean((address.state_code ?? "").trim());
113
+ }
114
+ return true;
115
+ }
116
+ /**
117
+ * Turn cart lines into Printful rate items.
118
+ *
119
+ * `catalogIdByVariantId` maps a Medusa variant id to the Printful *catalog*
120
+ * variant id stored in variant metadata during sync. Lines whose variant has no
121
+ * catalog id are skipped: that covers non-Printful products in a mixed store,
122
+ * and Printful variants synced before the mapper recorded that id.
123
+ */
124
+ function buildRateItems(lines, catalogIdByVariantId) {
125
+ const items = [];
126
+ for (const line of lines) {
127
+ const catalogId = catalogIdByVariantId.get(line.variant_id);
128
+ if (!catalogId) {
129
+ continue;
130
+ }
131
+ // parseInt stops at the first invalid character, so "40.12" would parse as
132
+ // 40 — a quote for a different product. Require the whole string to be a
133
+ // positive integer.
134
+ const trimmed = catalogId.trim();
135
+ if (!/^\d+$/.test(trimmed)) {
136
+ continue;
137
+ }
138
+ const parsed = Number.parseInt(trimmed, 10);
139
+ if (parsed <= 0) {
140
+ continue;
141
+ }
142
+ const quantity = Number(line.quantity);
143
+ if (!Number.isInteger(quantity) || quantity <= 0) {
144
+ continue;
145
+ }
146
+ items.push({
147
+ variant_id: parsed,
148
+ quantity,
149
+ ...(line.unit_price != null
150
+ ? { value: (line.unit_price / 100).toFixed(2) }
151
+ : {}),
152
+ });
153
+ }
154
+ return items;
155
+ }
156
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2hpcHBpbmctcmF0ZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvc2hpcHBpbmctcmF0ZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBMERBLDhDQXFCQztBQWNELGdDQXdCQztBQVlELDhDQVlDO0FBaUJELHdDQXNDQztBQXBNRCxtQ0FBbUM7QUFDbkMsdUNBQWtEO0FBUWxEOzs7Ozs7Ozs7R0FTRztBQUNVLFFBQUEseUJBQXlCLEdBQUcsQ0FBQyxVQUFVLENBQVUsQ0FBQTtBQUU5RDs7Ozs7R0FLRztBQUNVLFFBQUEseUJBQXlCLEdBQUcsaUJBQWlCLENBQUE7QUFFMUQ7OztHQUdHO0FBQ1UsUUFBQSxpQkFBaUIsR0FBRyxJQUFJLEdBQUcsQ0FBQztJQUN2QyxtQkFBbUI7SUFDbkIsaUJBQWlCO0NBQ2xCLENBQUMsQ0FBQTtBQVFGLFNBQVMsU0FBUyxDQUFDLEtBQXlCO0lBQzFDLE9BQU8sQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUE7QUFDM0MsQ0FBQztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILFNBQWdCLGlCQUFpQixDQUFDLEtBQXdCO0lBQ3hELHVFQUF1RTtJQUN2RSxvRUFBb0U7SUFDcEUsK0JBQStCO0lBQy9CLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDL0IsU0FBUyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDO1FBQ3JDLFNBQVMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQztRQUNuQyxTQUFTLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDN0IsU0FBUyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDO1FBQzVCLFNBQVMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUNqQyxTQUFTLENBQUMsS0FBSyxD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
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DISCONTINUED_MARKER_KEY = exports.STOCK_MARKER_KEY = void 0;
4
+ exports.planStockActions = planStockActions;
5
+ exports.resolvePublication = resolvePublication;
6
+ /** Statuses that mean a variant cannot currently be ordered. */
7
+ const UNAVAILABLE = new Set([
8
+ "out_of_stock",
9
+ "temporary_out_of_stock",
10
+ "discontinued",
11
+ ]);
12
+ /**
13
+ * Decide what a product's stock state means for its publication.
14
+ *
15
+ * Pure: returns intent, performs nothing. The caller applies the marker rules
16
+ * that keep this from overriding a merchant's own draft.
17
+ *
18
+ * An unrecognized or missing status counts as available. Printful can add a
19
+ * status at any time, and treating the unknown as sold out would hide a
20
+ * catalog on a value we simply have not seen before.
21
+ */
22
+ function planStockActions(variants) {
23
+ const variantAvailability = {};
24
+ let anyAvailable = false;
25
+ let hasDiscontinued = false;
26
+ for (const v of variants) {
27
+ const status = v.availability_status ?? "active";
28
+ variantAvailability[String(v.id)] = status;
29
+ if (status === "discontinued") {
30
+ hasDiscontinued = true;
31
+ }
32
+ if (!UNAVAILABLE.has(status)) {
33
+ anyAvailable = true;
34
+ }
35
+ }
36
+ // No variants at all is a data oddity, not evidence of a sellout.
37
+ const allUnavailable = variants.length > 0 && !anyAvailable;
38
+ return {
39
+ status: allUnavailable ? "draft" : "published",
40
+ allUnavailable,
41
+ hasDiscontinued,
42
+ variantAvailability,
43
+ };
44
+ }
45
+ /** Marks a product the plugin unpublished because Printful ran out of it. */
46
+ exports.STOCK_MARKER_KEY = "printful_stock_status";
47
+ /** Set when Printful reports a variant as discontinued. Informational only. */
48
+ exports.DISCONTINUED_MARKER_KEY = "printful_discontinued";
49
+ /**
50
+ * Apply the stock plan without overriding the merchant.
51
+ *
52
+ * The plugin owns only the transitions it caused: it marks a product when it
53
+ * unpublishes one for being sold out, and re-publishes only a product carrying
54
+ * that marker. A product the merchant drafted themselves — no marker — is left
55
+ * exactly as they left it, however available Printful says it is.
56
+ */
57
+ function resolvePublication(input) {
58
+ const { plan, currentStatus, currentMetadata } = input;
59
+ const metadata = { ...currentMetadata };
60
+ const marked = currentMetadata[exports.STOCK_MARKER_KEY] === "unavailable";
61
+ if (plan.allUnavailable) {
62
+ // Only claim ownership of an unpublish we are actually performing. A
63
+ // product already drafted without our marker was drafted by the merchant,
64
+ // and marking it would make us republish their draft on restock.
65
+ if (currentStatus === "draft") {
66
+ return { status: "draft", metadata, changed: false };
67
+ }
68
+ metadata[exports.STOCK_MARKER_KEY] = "unavailable";
69
+ return { status: "draft", metadata, changed: true };
70
+ }
71
+ // Available again. Only undo an unpublish we performed.
72
+ if (currentStatus === "draft") {
73
+ if (!marked) {
74
+ return { status: "draft", metadata, changed: false };
75
+ }
76
+ delete metadata[exports.STOCK_MARKER_KEY];
77
+ return { status: "published", metadata, changed: true };
78
+ }
79
+ if (marked) {
80
+ delete metadata[exports.STOCK_MARKER_KEY];
81
+ return { status: "published", metadata, changed: true };
82
+ }
83
+ return { status: "published", metadata, changed: false };
84
+ }
85
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvc3RvY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBbUJBLDRDQTRCQztBQTZCRCxnREErQkM7QUF6R0QsZ0VBQWdFO0FBQ2hFLE1BQU0sV0FBVyxHQUFHLElBQUksR0FBRyxDQUFDO0lBQzFCLGNBQWM7SUFDZCx3QkFBd0I7SUFDeEIsY0FBYztDQUNmLENBQUMsQ0FBQTtBQUVGOzs7Ozs7Ozs7R0FTRztBQUNILFNBQWdCLGdCQUFnQixDQUM5QixRQUF3RTtJQUV4RSxNQUFNLG1CQUFtQixHQUEyQixFQUFFLENBQUE7SUFDdEQsSUFBSSxZQUFZLEdBQUcsS0FBSyxDQUFBO0lBQ3hCLElBQUksZUFBZSxHQUFHLEtBQUssQ0FBQTtJQUUzQixLQUFLLE1BQU0sQ0FBQyxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQ3pCLE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQyxtQkFBbUIsSUFBSSxRQUFRLENBQUE7UUFDaEQsbUJBQW1CLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxHQUFHLE1BQU0sQ0FBQTtRQUUxQyxJQUFJLE1BQU0sS0FBSyxjQUFjLEVBQUUsQ0FBQztZQUM5QixlQUFlLEdBQUcsSUFBSSxDQUFBO1FBQ3hCLENBQUM7UUFDRCxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1lBQzdCLFlBQVksR0FBRyxJQUFJLENBQUE7UUFDckIsQ0FBQztJQUNILENBQUM7SUFFRCxrRUFBa0U7SUFDbEUsTUFBTSxjQUFjLEdBQUcsUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUE7SUFFM0QsT0FBTztRQUNMLE1BQU0sRUFBRSxjQUFjLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsV0FBVztRQUM5QyxjQUFjO1FBQ2QsZUFBZTtRQUNmLG1CQUFtQjtLQUNwQixDQUFBO0FBQ0gsQ0FBQztBQUVELDZFQUE2RTtBQUNoRSxRQUFBLGdCQUFnQixHQUFHLHVCQUF1QixDQUFBO0FBRXZELCtFQUErRTtBQUNsRSxRQUFBLHVCQUF1QixHQUFHLHVCQUF1QixDQUFBO0FBZTlEOzs7Ozs7O0dBT0c7QUFDSCxTQUFnQixrQkFBa0IsQ0FBQyxLQUF1QjtJQUN4RCxNQUFNLEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxlQUFlLEVBQUUsR0FBRyxLQUFLLENBQUE7SUFDdEQsTUFBTSxRQUFRLEdBQUcsRUFBRSxHQUFHLGVBQWUsRUFBRSxDQUFBO0lBQ3ZDLE1BQU0sTUFBTSxHQUFHLGVBQWUsQ0FBQyx3QkFBZ0IsQ0FBQyxLQUFLLGFBQWEsQ0FBQTtJQUVsRSxJQUFJLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUN4QixxRUFBcUU7UUFDckUsMEVBQTBFO1FBQzFFLGlFQUFpRTtRQUNqRSxJQUFJLGFBQWEsS0FBSyxPQUFPLEVBQUUsQ0FBQztZQUM5QixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxDQUFBO1FBQ3RELENBQUM7UUFDRCxRQUFRLENBQUMsd0JBQWdCLENBQUMsR0FBRyxhQUFhLENBQUE7UUFDMUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQTtJQUNyRCxDQUFDO0lBRUQsd0RBQXdEO0lBQ3hELElBQUksYUFBYSxLQUFLLE9BQU8sRUFBRSxDQUFDO1FBQzlCLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLENBQUE7UUFDdEQsQ0FBQztRQUNELE9BQU8sUUFBUSxDQUFDLHdCQUFnQixDQUFDLENBQUE7UUFDakMsT0FBTyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQTtJQUN6RCxDQUFDO0lBRUQsSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUNYLE9BQU8sUUFBUSxDQUFDLHdCQUFnQixDQUFDLENBQUE7UUFDakMsT0FBTyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQTtJQUN6RCxDQUFDO0lBRUQsT0FBTyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQTtBQUMxRCxDQUFDIn0=
@@ -6,18 +6,20 @@ const core_flows_1 = require("@medusajs/medusa/core-flows");
6
6
  const utils_1 = require("@medusajs/framework/utils");
7
7
  const printful_1 = require("../modules/printful");
8
8
  const mappers_1 = require("../utils/mappers");
9
- const createSyncLogStep = (0, workflows_sdk_1.createStep)("printful-create-sync-log", async (_input, { container }) => {
10
- const printful = container.resolve(printful_1.PRINTFUL_MODULE);
11
- const log = await printful.createPrintfulSyncLogs({
12
- status: "running",
13
- started_at: new Date(),
14
- products_created: 0,
15
- products_updated: 0,
16
- products_failed: 0,
17
- });
18
- return new workflows_sdk_1.StepResponse(log);
19
- });
20
- const syncProductsStep = (0, workflows_sdk_1.createStep)("printful-sync-products", async (input, { container }) => {
9
+ const orphans_1 = require("../utils/orphans");
10
+ const syncProductsStep = (0, workflows_sdk_1.createStep)({
11
+ name: "printful-sync-products",
12
+ // Runs past the HTTP response and completes on its own — no
13
+ // setStepSuccess needed, which is what backgroundExecution adds to async.
14
+ async: true,
15
+ backgroundExecution: true,
16
+ // No timeout on purpose. Medusa schedules one only when the step sets it
17
+ // (TransactionStep.hasTimeout() returns !!definition.timeout, and
18
+ // transaction-orchestrator.js:637 guards on it), so omitting it is the
19
+ // safe state. Any value we picked could be exceeded by a large enough
20
+ // catalog, and the penalty is a SUCCESSFUL sync being reverted — deleting
21
+ // the orphans it was about to link.
22
+ }, async (input, { container }) => {
21
23
  const printful = container.resolve(printful_1.PRINTFUL_MODULE);
22
24
  const productModule = container.resolve(utils_1.Modules.PRODUCT);
23
25
  const client = await printful.getClient();
@@ -33,6 +35,14 @@ const syncProductsStep = (0, workflows_sdk_1.createStep)("printful-sync-products
33
35
  failed: 0,
34
36
  errors: [],
35
37
  };
38
+ // Products created but not yet linked. A crash leaves exactly these
39
+ // invisible to the next sync's findProductLink, so they are the only
40
+ // thing the compensation may delete.
41
+ const orphans = new orphans_1.OrphanTracker();
42
+ let processed = 0;
43
+ await printful.heartbeatSyncLog(input.sync_log_id, {
44
+ products_total: toProcess.length,
45
+ });
36
46
  for (const summary of toProcess) {
37
47
  if (summary.is_ignored) {
38
48
  continue;
@@ -44,11 +54,11 @@ const syncProductsStep = (0, workflows_sdk_1.createStep)("printful-sync-products
44
54
  counters.errors.push(`Product ${summary.id} has no variants`);
45
55
  continue;
46
56
  }
47
- const mapped = (0, mappers_1.mapSyncProductToMedusa)(detail, {
48
- storeId: options.storeId,
49
- defaultCurrency: options.defaultCurrency,
50
- markupPercent: options.markupPercent,
51
- });
57
+ // Pass the options wholesale rather than picking fields. Hand-building
58
+ // this object is how `onDiscontinued` was silently dropped: every field
59
+ // is optional, so omitting one is legal TypeScript and typecheck stayed
60
+ // green while the option did nothing. The mapper narrows internally.
61
+ const mapped = (0, mappers_1.mapSyncProductToMedusa)(detail, options);
52
62
  const existingLink = await printful.findProductLink(String(summary.id));
53
63
  if (existingLink?.medusa_product_id) {
54
64
  const productId = existingLink.medusa_product_id;
@@ -162,12 +172,15 @@ const syncProductsStep = (0, workflows_sdk_1.createStep)("printful-sync-products
162
172
  },
163
173
  });
164
174
  const created = result[0];
175
+ orphans.track(created.id);
165
176
  await printful.createPrintfulProductLinks({
166
177
  printful_store_id: storeId,
167
178
  printful_sync_product_id: String(summary.id),
168
179
  medusa_product_id: created.id,
169
180
  last_synced_at: new Date(),
170
181
  });
182
+ // Linked — no longer an orphan, and never to be deleted.
183
+ orphans.release(created.id);
171
184
  for (const variant of created.variants ?? []) {
172
185
  const syncVariantId = variant.metadata?.printful_sync_variant_id;
173
186
  if (!syncVariantId) {
@@ -189,8 +202,35 @@ const syncProductsStep = (0, workflows_sdk_1.createStep)("printful-sync-products
189
202
  const message = err instanceof Error ? err.message : String(err);
190
203
  counters.errors.push(`Product ${summary.id}: ${message}`);
191
204
  }
205
+ finally {
206
+ // In `finally` so it runs however the product exited — success, an
207
+ // early `continue`, or a throw. The heartbeat is what keeps the claim
208
+ // from being reaped, so a catalog of failures must not starve it.
209
+ processed += 1;
210
+ await printful.heartbeatSyncLog(input.sync_log_id, {
211
+ products_processed: processed,
212
+ products_total: toProcess.length,
213
+ });
214
+ }
215
+ }
216
+ return new workflows_sdk_1.StepResponse(counters, { orphanProductIds: orphans.toDelete() });
217
+ }, async (compensateInput, { container }) => {
218
+ if (!compensateInput?.orphanProductIds?.length) {
219
+ return;
220
+ }
221
+ const productModule = container.resolve(utils_1.Modules.PRODUCT);
222
+ const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
223
+ // Delete through the product module so variants, prices, and images go
224
+ // with the product rather than being orphaned a second time.
225
+ for (const id of compensateInput.orphanProductIds) {
226
+ try {
227
+ await productModule.deleteProducts([id]);
228
+ }
229
+ catch (err) {
230
+ logger.error(`Printful sync rollback: could not delete orphaned product ${id}: ${err instanceof Error ? err.message : String(err)}`);
231
+ }
192
232
  }
193
- return new workflows_sdk_1.StepResponse(counters);
233
+ logger.info(`Printful sync rolled back ${compensateInput.orphanProductIds.length} orphaned product(s)`);
194
234
  });
195
235
  const finalizeSyncLogStep = (0, workflows_sdk_1.createStep)("printful-finalize-sync-log", async (input, { container }) => {
196
236
  const printful = container.resolve(printful_1.PRINTFUL_MODULE);
@@ -211,10 +251,9 @@ const finalizeSyncLogStep = (0, workflows_sdk_1.createStep)("printful-finalize-s
211
251
  return new workflows_sdk_1.StepResponse(log);
212
252
  });
213
253
  exports.syncProductsWorkflow = (0, workflows_sdk_1.createWorkflow)("printful-sync-products", (input) => {
214
- const log = createSyncLogStep();
215
254
  const counters = syncProductsStep(input);
216
- const finalizeInput = (0, workflows_sdk_1.transform)({ log, counters }, (data) => ({
217
- logId: data.log.id,
255
+ const finalizeInput = (0, workflows_sdk_1.transform)({ input, counters }, (data) => ({
256
+ logId: data.input.sync_log_id,
218
257
  counters: data.counters,
219
258
  }));
220
259
  const finalLog = finalizeSyncLogStep(finalizeInput);
@@ -224,4 +263,4 @@ exports.syncProductsWorkflow = (0, workflows_sdk_1.createWorkflow)("printful-syn
224
263
  });
225
264
  });
226
265
  exports.default = exports.syncProductsWorkflow;
227
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1wcm9kdWN0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3Mvc3luYy1wcm9kdWN0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxRUFNMEM7QUFDMUMsNERBR29DO0FBQ3BDLHFEQUFtRDtBQUNuRCxrREFBcUQ7QUFFckQsOENBQWdGO0FBY2hGLE1BQU0saUJBQWlCLEdBQUcsSUFBQSwwQkFBVSxFQUNsQywwQkFBMEIsRUFDMUIsS0FBSyxFQUFFLE1BQVksRUFBRSxFQUFFLFNBQVMsRUFBRSxFQUFFLEVBQUU7SUFDcEMsTUFBTSxRQUFRLEdBQTBCLFNBQVMsQ0FBQyxPQUFPLENBQUMsMEJBQWUsQ0FBQyxDQUFBO0lBQzFFLE1BQU0sR0FBRyxHQUFHLE1BQU0sUUFBUSxDQUFDLHNCQUFzQixDQUFDO1FBQ2hELE1BQU0sRUFBRSxTQUFTO1FBQ2pCLFVBQVUsRUFBRSxJQUFJLElBQUksRUFBRTtRQUN0QixnQkFBZ0IsRUFBRSxDQUFDO1FBQ25CLGdCQUFnQixFQUFFLENBQUM7UUFDbkIsZUFBZSxFQUFFLENBQUM7S0FDbkIsQ0FBQyxDQUFBO0lBQ0YsT0FBTyxJQUFJLDRCQUFZLENBQUMsR0FBRyxDQUFDLENBQUE7QUFDOUIsQ0FBQyxDQUNGLENBQUE7QUFFRCxNQUFNLGdCQUFnQixHQUFHLElBQUEsMEJBQVUsRUFDakMsd0JBQXdCLEVBQ3hCLEtBQUssRUFBRSxLQUF3QixFQUFFLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUNoRCxNQUFNLFFBQVEsR0FBMEIsU0FBUyxDQUFDLE9BQU8sQ0FBQywwQkFBZSxDQUFDLENBQUE7SUFDMUUsTUFBTSxhQUFhLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxlQUFPLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDeEQsTUFBTSxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsU0FBUyxFQUFFLENBQUE7SUFDekMsTUFBTSxPQUFPLEdBQUcsTUFB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
266
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1wcm9kdWN0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3Mvc3luYy1wcm9kdWN0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxRUFNMEM7QUFDMUMsNERBR29DO0FBQ3BDLHFEQUE4RTtBQUM5RSxrREFBcUQ7QUFFckQsOENBQWdGO0FBQ2hGLDhDQUFnRDtBQWdCaEQsTUFBTSxnQkFBZ0IsR0FBRyxJQUFBLDBCQUFVLEVBQ2pDO0lBQ0UsSUFBSSxFQUFFLHdCQUF3QjtJQUM5Qiw0REFBNEQ7SUFDNUQsMEVBQTBFO0lBQzFFLEtBQUssRUFBRSxJQUFJO0lBQ1gsbUJBQW1CLEVBQUUsSUFBSTtJQUN6Qix5RUFBeUU7SUFDekUsa0VBQWtFO0lBQ2xFLHVFQUF1RTtJQUN2RSxzRUFBc0U7SUFDdEUsMEVBQTBFO0lBQzFFLG9DQUFvQztDQUNyQyxFQUNELEtBQUssRUFBRSxLQUF3QixFQUFFLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUNoRCxNQUFNLFFBQVEsR0FBMEIsU0FBUyxDQUFDLE9BQU8sQ0FBQywwQkFBZSxDQUFDLENBQUE7SUFDMUUsTUFBTSxhQUFhLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxlQUFPLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDeEQsTUFBTSxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsU0FBUyxFQUFFLENBQUE7SUFDekMsTUFBTSxPQUFPLEdBQUcsTUFBTSxRQUFRLENBQUMsVUFBVSxFQUFFLENBQUE7SUFDM0MsTUFBTSxPQUFPLEdBQUcsTUFBTSxRQUFRLENBQUMsVUFBVSxFQUFFLENBQUE7SUFFM0MsTUFBTSxTQUFTLEdBQUcsTUFBTSxNQUFNLENBQUMsbUJBQW1CLENBQUM7UUFDakQsS0FBSyxFQUFFLEdBQUc7S0FDWCxDQUFDLENBQUE7SUFFRixNQUFNLFNBQVMsR0FDYixLQUFLLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxDQUFDLENB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
package/README.md CHANGED
@@ -76,15 +76,16 @@ See `examples/basic-store/` for a fuller snippet.
76
76
 
77
77
  ## What it does (MVP)
78
78
 
79
- | Feature | How |
80
- | -------------------- | ----------------------------------------------------------------------------------------------------- |
81
- | Product sync | Admin **Sync Now** or `POST /admin/printful/sync` → workflow pulls Printful Sync Products into Medusa |
82
- | Links | `printful_product_link` / `printful_variant_link` (+ metadata IDs) |
83
- | Orders | On `payment.captured` → creates Printful order with **`sync_variant_id`** |
84
- | Fulfillment provider | Select Printful shipping option in Admin locations |
85
- | Status | `GET /admin/printful/status` + product list widget |
86
- | Shipment tracking | Printful webhooks → Medusa fulfillment + shipment per parcel, with tracking |
87
- | Order visibility | Printful status and per-parcel tracking on the Admin order page |
79
+ | Feature | How |
80
+ | -------------------- | ----------------------------------------------------------------------------------------- |
81
+ | Product sync | Admin **Sync Now** or `POST /admin/printful/sync` → runs in the background, one at a time |
82
+ | Stock awareness | Variants Printful reports as unavailable unpublish the product; restock republishes it |
83
+ | Links | `printful_product_link` / `printful_variant_link` (+ metadata IDs) |
84
+ | Orders | On `payment.captured` → creates Printful order with **`sync_variant_id`** |
85
+ | Fulfillment provider | Select Printful shipping option in Admin locations |
86
+ | Status | `GET /admin/printful/status` + product list widget |
87
+ | Shipment tracking | Printful webhooks → Medusa fulfillment + shipment per parcel, with tracking |
88
+ | Order visibility | Printful status and per-parcel tracking on the Admin order page |
88
89
 
89
90
  ### Idempotency
90
91
 
@@ -177,6 +178,101 @@ lines contain the real path. The endpoint's body limit is therefore raised to
177
178
  exceeded by roughly a 25-item order), so genuine Printful traffic does not
178
179
  reach that path. This is another reason to treat the secret as rotatable.
179
180
 
181
+ ## Live shipping rates
182
+
183
+ Printful quotes shipping for the destination and cart contents instead of you
184
+ setting a flat price by hand.
185
+
186
+ ```ts
187
+ plugins: [
188
+ {
189
+ resolve: "@legenki/print2medusa",
190
+ options: {
191
+ apiToken: process.env.PRINTFUL_API_TOKEN,
192
+ liveShippingRates: true,
193
+ fallbackShippingRates: { STANDARD: 500 }, // minor units
194
+ },
195
+ },
196
+ ],
197
+ modules: [
198
+ {
199
+ resolve: "@medusajs/medusa/fulfillment",
200
+ // Required. The provider reads Printful variant ids from variant metadata
201
+ // through Query, and Medusa only bridges modules a provider declares.
202
+ dependencies: ["query"],
203
+ options: {
204
+ providers: [
205
+ {
206
+ resolve: "@legenki/print2medusa/providers/printful-fulfillment",
207
+ id: "printful",
208
+ options: { apiToken: process.env.PRINTFUL_API_TOKEN },
209
+ },
210
+ ],
211
+ },
212
+ },
213
+ ],
214
+ ```
215
+
216
+ **`dependencies: ["query"]` is not optional.** Without it the provider cannot
217
+ resolve Printful variant ids, and every quote quietly falls back to the flat
218
+ rate. Medusa resolves an undeclared dependency to `undefined` rather than
219
+ failing, so the plugin logs an error at startup instead.
220
+
221
+ **Give `fallbackShippingRates` an entry for every method you offer.** A method
222
+ with no entry prices at **zero** rather than blocking checkout: Medusa cannot
223
+ complete a cart whose shipping price fails to resolve, so an underpriced
224
+ delivery is the lesser harm. The plugin logs an error each time it happens.
225
+
226
+ ### What happens when Printful is unreachable
227
+
228
+ Checkout still completes. Prices fall back in this order:
229
+
230
+ 1. A cached quote inside `shippingRateCacheTtlSeconds` (default 600)
231
+ 2. A cached quote past that but within `shippingRateStaleSeconds` (default 86400)
232
+ 3. The flat rate from `fallbackShippingRates`
233
+
234
+ A day-old real quote beats a constant someone typed once, which is why the stale
235
+ tier outranks the flat rate. One Printful call serves every shipping option on a
236
+ cart — the whole response is cached, and each option is picked from it locally.
237
+
238
+ ### Limits worth knowing
239
+
240
+ - **Printful chooses the shipping method on the order.** The method the customer
241
+ selected is priced correctly but is not passed through to Printful, so it can
242
+ ship by a different service. Medusa does not carry provider data from price
243
+ calculation onto the shipping method, and the mechanism that would fix this
244
+ needs its own release.
245
+ - **Return options are never priced live.** Printful quotes outbound shipping
246
+ only, so a return shipping option must be given a flat admin price.
247
+ - **Rates are quoted in the cart's currency** by asking Printful to convert. If
248
+ a quote comes back in another currency it is discarded rather than converted.
249
+
250
+ ## Catalog sync
251
+
252
+ `POST /admin/printful/sync` returns `202 {sync_id}` right away and the sync runs
253
+ in the background, so a large catalog no longer holds the request open. The
254
+ widget polls progress while it runs.
255
+
256
+ **One sync at a time.** A second request gets `409` with the running sync's
257
+ `started_at`, and the nightly job skips quietly rather than piling on. This is
258
+ enforced by a partial unique index in Postgres, not by a check-then-insert, so
259
+ double-clicking **Sync Now** cannot start two.
260
+
261
+ **A killed process is recovered lazily.** If Medusa dies mid-sync, the log row
262
+ stays `running` and the widget keeps showing a sync that is no longer alive.
263
+ Nothing sweeps on a timer: the next sync attempt — manual, or the nightly job —
264
+ reclaims any claim whose heartbeat is older than `syncStaleMinutes` (default 60)
265
+ and proceeds. Products created but not yet linked are deleted on rollback, so a
266
+ crash leaves no half-imported products behind.
267
+
268
+ ### Stock
269
+
270
+ A product whose variants Printful all reports as unavailable is set to `draft`,
271
+ and republished when it comes back. The plugin only republishes what it
272
+ unpublished itself — a product you set to draft by hand stays draft. Variants
273
+ carry `printful_availability_status` in metadata, and discontinued products get
274
+ `printful_discontinued` unless `onDiscontinued: "ignore"`.
275
+
180
276
  ## Admin usage
181
277
 
182
278
  1. Create products in Printful (Store Products).
@@ -217,16 +313,18 @@ the testing strategy for each release.
217
313
 
218
314
  ## Options
219
315
 
220
- | Option | Description |
221
- | --------------------- | ----------------------------------------------------------------------- |
222
- | `apiToken` | Printful private token (required) |
223
- | `storeId` | `X-PF-Store-Id` for account-level tokens |
224
- | `autoSubmitOrders` | Confirm orders for fulfillment (default true) |
225
- | `createOnOrderPlaced` | Also create Printful order on `order.placed` |
226
- | `allowPartialOrders` | Allow orders that mix Printful + non-Printful items |
227
- | `markupPercent` | Markup on retail prices during sync |
228
- | `defaultCurrency` | Fallback currency code |
229
- | `webhookSecret` | Shared secret for the Printful webhook path (see [Webhooks](#webhooks)) |
316
+ | Option | Description |
317
+ | --------------------- | --------------------------------------------------------------------------- |
318
+ | `apiToken` | Printful private token (required) |
319
+ | `storeId` | `X-PF-Store-Id` for account-level tokens |
320
+ | `autoSubmitOrders` | Confirm orders for fulfillment (default true) |
321
+ | `createOnOrderPlaced` | Also create Printful order on `order.placed` |
322
+ | `allowPartialOrders` | Allow orders that mix Printful + non-Printful items |
323
+ | `markupPercent` | Markup on retail prices during sync |
324
+ | `defaultCurrency` | Fallback currency code |
325
+ | `webhookSecret` | Shared secret for the Printful webhook path (see [Webhooks](#webhooks)) |
326
+ | `syncStaleMinutes` | Minutes before a running sync is presumed dead and reclaimed (default 60) |
327
+ | `onDiscontinued` | `"flag"` (default) marks discontinued products, `"ignore"` omits the marker |
230
328
 
231
329
  ## License
232
330
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legenki/print2medusa",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Printful → Medusa v2 plugin: product sync, auto fulfillment, and admin tools",
5
5
  "author": "Andy Legenki",
6
6
  "license": "MIT",
@@ -66,7 +66,7 @@
66
66
  "@types/react-dom": "^18.2.25",
67
67
  "eslint": "^9.0.0",
68
68
  "jiti": "^2.0.0",
69
- "prettier": "^3.3.0",
69
+ "prettier": "^3.9.6",
70
70
  "prop-types": "^15.8.1",
71
71
  "react": "^18.2.0",
72
72
  "react-dom": "^18.2.0",