medusa-plugin-ordinant 0.1.0 → 0.1.2

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.
@@ -34,6 +34,7 @@ const CATEGORY_FIELDS = {
34
34
  "aow",
35
35
  "destructive_device",
36
36
  "suppressor",
37
+ "suppressor_part",
37
38
  "firearm_part"
38
39
  ],
39
40
  mag_capacity: [
@@ -53,7 +54,8 @@ const CATEGORY_FIELDS = {
53
54
  "short_barreled_shotgun",
54
55
  "machine_gun",
55
56
  "aow",
56
- "suppressor"
57
+ "suppressor",
58
+ "suppressor_part"
57
59
  ],
58
60
  action_type: [
59
61
  "handgun",
@@ -139,6 +141,57 @@ function formatValue(value) {
139
141
  }
140
142
  return labelValue(String(value));
141
143
  }
144
+ function CoveragePreview({ attributes }) {
145
+ const category = typeof attributes["category"] === "string" ? attributes["category"] : void 0;
146
+ const { data, isFetching, error } = reactQuery.useQuery({
147
+ queryKey: ["ordinant-coverage", JSON.stringify(attributes)],
148
+ queryFn: () => sdk.client.fetch("/admin/ordinant/coverage", {
149
+ method: "POST",
150
+ body: { attributes }
151
+ }),
152
+ enabled: category !== void 0,
153
+ staleTime: 5 * 60 * 1e3
154
+ });
155
+ if (category === void 0) {
156
+ return null;
157
+ }
158
+ if (isFetching && !data) {
159
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: "Checking where this configuration can ship…" });
160
+ }
161
+ if (error || !data) {
162
+ return null;
163
+ }
164
+ const blocked = data.blocked ?? [];
165
+ const conditional = data.conditional ?? [];
166
+ if (blocked.length === 0 && conditional.length === 0) {
167
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
168
+ "Checkout is allowed in all ",
169
+ data.total_states,
170
+ " modeled states with this configuration."
171
+ ] });
172
+ }
173
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 rounded-lg border p-3 bg-ui-bg-subtle", children: [
174
+ blocked.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
175
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-error", children: [
176
+ "With this configuration, shipping/checkout will be denied to",
177
+ " ",
178
+ blocked.length,
179
+ " ",
180
+ blocked.length === 1 ? "state" : "states",
181
+ ":"
182
+ ] }),
183
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: blocked.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "red", title: s.name, children: s.state }, s.state)) })
184
+ ] }),
185
+ conditional.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
186
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-subtle", children: [
187
+ "Requires buyer verification (FFL / permit / age) in ",
188
+ conditional.length,
189
+ ":"
190
+ ] }),
191
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: conditional.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "orange", title: s.name, children: s.state }, s.state)) })
192
+ ] })
193
+ ] });
194
+ }
142
195
  function AttributeEditor({
143
196
  value,
144
197
  onChange
@@ -182,17 +235,20 @@ function AttributeEditor({
182
235
  {
183
236
  def,
184
237
  value: value[def.key],
185
- onChange: (v) => set(def.key, v)
238
+ onChange: (v) => set(def.key, v),
239
+ extraHint: def.key === "nfa_class" && category === "suppressor_part" ? "For a suppressor component, this is what decides whether it can ship: choose “Suppressor” for the sound-reducing core (baffle, monocore, expansion chamber, outer tube). Choose “None” for external accessories that don't reduce sound (end cap, cover, mount, thread adapter)." : void 0
186
240
  },
187
241
  def.key
188
- ))
242
+ )),
243
+ category !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(CoveragePreview, { attributes: value })
189
244
  ] });
190
245
  }
191
246
  const NONE = "__none__";
192
247
  function AttributeField({
193
248
  def,
194
249
  value,
195
- onChange
250
+ onChange,
251
+ extraHint
196
252
  }) {
197
253
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
198
254
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -200,6 +256,7 @@ function AttributeField({
200
256
  def.hard_gate && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "orange", children: "required" })
201
257
  ] }),
202
258
  def.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: def.description }),
259
+ extraHint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: extraHint }),
203
260
  /* @__PURE__ */ jsxRuntime.jsx(Control, { def, value, onChange })
204
261
  ] });
205
262
  }
@@ -31,6 +31,7 @@ const CATEGORY_FIELDS = {
31
31
  "aow",
32
32
  "destructive_device",
33
33
  "suppressor",
34
+ "suppressor_part",
34
35
  "firearm_part"
35
36
  ],
36
37
  mag_capacity: [
@@ -50,7 +51,8 @@ const CATEGORY_FIELDS = {
50
51
  "short_barreled_shotgun",
51
52
  "machine_gun",
52
53
  "aow",
53
- "suppressor"
54
+ "suppressor",
55
+ "suppressor_part"
54
56
  ],
55
57
  action_type: [
56
58
  "handgun",
@@ -136,6 +138,57 @@ function formatValue(value) {
136
138
  }
137
139
  return labelValue(String(value));
138
140
  }
141
+ function CoveragePreview({ attributes }) {
142
+ const category = typeof attributes["category"] === "string" ? attributes["category"] : void 0;
143
+ const { data, isFetching, error } = useQuery({
144
+ queryKey: ["ordinant-coverage", JSON.stringify(attributes)],
145
+ queryFn: () => sdk.client.fetch("/admin/ordinant/coverage", {
146
+ method: "POST",
147
+ body: { attributes }
148
+ }),
149
+ enabled: category !== void 0,
150
+ staleTime: 5 * 60 * 1e3
151
+ });
152
+ if (category === void 0) {
153
+ return null;
154
+ }
155
+ if (isFetching && !data) {
156
+ return /* @__PURE__ */ jsx(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: "Checking where this configuration can ship…" });
157
+ }
158
+ if (error || !data) {
159
+ return null;
160
+ }
161
+ const blocked = data.blocked ?? [];
162
+ const conditional = data.conditional ?? [];
163
+ if (blocked.length === 0 && conditional.length === 0) {
164
+ return /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
165
+ "Checkout is allowed in all ",
166
+ data.total_states,
167
+ " modeled states with this configuration."
168
+ ] });
169
+ }
170
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-lg border p-3 bg-ui-bg-subtle", children: [
171
+ blocked.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
172
+ /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-error", children: [
173
+ "With this configuration, shipping/checkout will be denied to",
174
+ " ",
175
+ blocked.length,
176
+ " ",
177
+ blocked.length === 1 ? "state" : "states",
178
+ ":"
179
+ ] }),
180
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: blocked.map((s) => /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "red", title: s.name, children: s.state }, s.state)) })
181
+ ] }),
182
+ conditional.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
183
+ /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-subtle", children: [
184
+ "Requires buyer verification (FFL / permit / age) in ",
185
+ conditional.length,
186
+ ":"
187
+ ] }),
188
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: conditional.map((s) => /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "orange", title: s.name, children: s.state }, s.state)) })
189
+ ] })
190
+ ] });
191
+ }
139
192
  function AttributeEditor({
140
193
  value,
141
194
  onChange
@@ -179,17 +232,20 @@ function AttributeEditor({
179
232
  {
180
233
  def,
181
234
  value: value[def.key],
182
- onChange: (v) => set(def.key, v)
235
+ onChange: (v) => set(def.key, v),
236
+ extraHint: def.key === "nfa_class" && category === "suppressor_part" ? "For a suppressor component, this is what decides whether it can ship: choose “Suppressor” for the sound-reducing core (baffle, monocore, expansion chamber, outer tube). Choose “None” for external accessories that don't reduce sound (end cap, cover, mount, thread adapter)." : void 0
183
237
  },
184
238
  def.key
185
- ))
239
+ )),
240
+ category !== void 0 && /* @__PURE__ */ jsx(CoveragePreview, { attributes: value })
186
241
  ] });
187
242
  }
188
243
  const NONE = "__none__";
189
244
  function AttributeField({
190
245
  def,
191
246
  value,
192
- onChange
247
+ onChange,
248
+ extraHint
193
249
  }) {
194
250
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
195
251
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -197,6 +253,7 @@ function AttributeField({
197
253
  def.hard_gate && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "orange", children: "required" })
198
254
  ] }),
199
255
  def.description && /* @__PURE__ */ jsx(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: def.description }),
256
+ extraHint && /* @__PURE__ */ jsx(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: extraHint }),
200
257
  /* @__PURE__ */ jsx(Control, { def, value, onChange })
201
258
  ] });
202
259
  }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const ordinant_1 = require("../../../../modules/ordinant");
5
+ /**
6
+ * POST /admin/ordinant/coverage
7
+ *
8
+ * Preview where a proposed classification would be denied. Given an attribute
9
+ * set, the engine sweeps every modeled jurisdiction and returns the states
10
+ * where checkout is BLOCKED vs. CONDITIONAL. Powers the editor's "with this
11
+ * configuration, shipping is denied to…" panel. Non-logging, advisory.
12
+ */
13
+ async function POST(req, res) {
14
+ const attributes = req.body?.attributes;
15
+ if (!attributes || Object.keys(attributes).length === 0) {
16
+ res.status(400).json({ message: "attributes are required to preview coverage" });
17
+ return;
18
+ }
19
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
20
+ const coverage = await ordinant.client.coverage(attributes);
21
+ res.json(coverage);
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2NvdmVyYWdlL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBY0Esb0JBWUM7QUF6QkQsMkRBQThEO0FBSzlEOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsSUFBSSxDQUN4QixHQUE2QyxFQUM3QyxHQUFtQjtJQUVuQixNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQTtJQUN2QyxJQUFJLENBQUMsVUFBVSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hELEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLDZDQUE2QyxFQUFFLENBQUMsQ0FBQTtRQUNoRixPQUFNO0lBQ1IsQ0FBQztJQUNELE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUF3QiwwQkFBZSxDQUFDLENBQUE7SUFDMUUsTUFBTSxRQUFRLEdBQUcsTUFBTSxRQUFRLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQTtJQUMzRCxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFBO0FBQ3BCLENBQUMifQ==
@@ -51,6 +51,16 @@ class OrdinantClient {
51
51
  }
52
52
  return this.call("POST", "/v1/decisions/preview", request, headers);
53
53
  }
54
+ /** Sweep a proposed attribute set across every modeled jurisdiction and
55
+ * report where checkout would be denied vs. conditionally allowed. Non-
56
+ * logging — powers the classification editor's "denied to" preview so a
57
+ * merchant sees the shipping impact of a classification before confirming. */
58
+ async coverage(attributes, buyer) {
59
+ return this.call("POST", "/v1/decisions/coverage", {
60
+ attributes,
61
+ buyer,
62
+ });
63
+ }
54
64
  async classifyCatalog(items) {
55
65
  // Classification makes one LLM call per item (6-way concurrent
56
66
  // server-side); a batch legitimately outlasts the checkout timeout.
@@ -133,4 +143,4 @@ class OrdinantClient {
133
143
  }
134
144
  }
135
145
  exports.OrdinantClient = OrdinantClient;
136
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7O0dBSUc7OztBQWtLSCxNQUFhLGdCQUFpQixTQUFRLEtBQUs7SUFJekMsWUFBWSxPQUFlLEVBQUUsSUFBWSxFQUFFLE1BQWU7UUFDeEQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2QsSUFBSSxDQUFDLElBQUksR0FBRyxrQkFBa0IsQ0FBQTtRQUM5QixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtRQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtJQUN0QixDQUFDO0NBQ0Y7QUFWRCw0Q0FVQztBQWlCRCxNQUFhLGNBQWM7SUFVekIsWUFBWSxPQUE4QjtRQUN4QyxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUM1RCxJQUFJLENBQUMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQzFELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUE7UUFDbEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQTtRQUMxQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixJQUFJLE9BQU8sQ0FBQTtJQUM3RCxDQUFDO0lBRUQsbUVBQW1FO0lBQ25FLEtBQUssQ0FBQyxVQUFVO1FBQ2QsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFhLEtBQUssRUFBRSxrQkFBa0IsQ0FBQyxDQUFBO0lBQ3pELENBQUM7SUFFRDtzQ0FDa0M7SUFDbEMsS0FBSyxDQUFDLGNBQWM7UUFDbEIs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
146
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7O0dBSUc7OztBQWtLSCxNQUFhLGdCQUFpQixTQUFRLEtBQUs7SUFJekMsWUFBWSxPQUFlLEVBQUUsSUFBWSxFQUFFLE1BQWU7UUFDeEQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2QsSUFBSSxDQUFDLElBQUksR0FBRyxrQkFBa0IsQ0FBQTtRQUM5QixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtRQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtJQUN0QixDQUFDO0NBQ0Y7QUFWRCw0Q0FVQztBQW9DRCxNQUFhLGNBQWM7SUFVekIsWUFBWSxPQUE4QjtRQUN4QyxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUM1RCxJQUFJLENBQUMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQzFELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUE7UUFDbEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQTtRQUMxQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixJQUFJLE9BQU8sQ0FBQTtJQUM3RCxDQUFDO0lBRUQsbUVBQW1FO0lBQ25FLEtBQUssQ0FBQyxVQUFVO1FBQ2QsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFhLEtBQUssRUFBRSxrQkFBa0IsQ0FBQyxDQUFBO0lBQ3pELENBQUM7SUFFRDtzQ0FDa0M7SUFDbEMsS0FBSyxDQUFDLGNBQWM7UUFDbEIs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
package/README.md CHANGED
@@ -3,6 +3,9 @@
3
3
  Ordinant compliance for Medusa v2: AI-proposed product classification with
4
4
  merchant attestation, and a deterministic, citation-bearing checkout gate.
5
5
 
6
+ Published on npm — install with `npm install medusa-plugin-ordinant`
7
+ ([npmjs.com/package/medusa-plugin-ordinant](https://www.npmjs.com/package/medusa-plugin-ordinant)).
8
+
6
9
  > **Ordinant is a compliance information and automation service — not legal
7
10
  > advice.** No attorney–client relationship is created by its use. Merchants
8
11
  > attest to the final classification of their products.
@@ -245,6 +248,14 @@ dictionary change.
245
248
  Re-classifying a SKU (e.g. after a product edit) resets it to `proposed`: a
246
249
  stale attestation never survives changed product data.
247
250
 
251
+ **Live shipping-impact preview.** While editing a classification, the widget
252
+ shows *"With this configuration, shipping/checkout will be denied to: CA, NY, …"*
253
+ (and a separate "requires FFL/permit/age in …" list), swept across every modeled
254
+ jurisdiction — so you see exactly where a classification will and won't sell
255
+ **before** you attest to it. Fields with subtle legal meaning carry inline
256
+ guidance (e.g. for a suppressor part, `nfa_class` = *Suppressor* for the sound-
257
+ reducing core, *None* for an external accessory like an end cap).
258
+
248
259
  ## The checkout gate
249
260
 
250
261
  Consumes `completeCartWorkflow.hooks.validate`, which runs before any order
@@ -415,6 +426,52 @@ self-contained function — copy it from `src/storefront.ts` into your codebase
415
426
  instead. Either way, anything you don't wire is still caught by the completeCart
416
427
  gate; the preview is pure upside.
417
428
 
429
+ ### Also re-check when the shopper *enters* checkout (defense in depth)
430
+
431
+ The address-step check catches a bad address as it's entered. But a shopper can
432
+ set a *compliant* address, then add an item that's illegal to their state, and
433
+ walk up to the review step — where the block would otherwise only appear at
434
+ "Place Order". Close that gap by re-checking the **whole cart** when they land on
435
+ (or return to) the checkout page, and bouncing them back to the shipping step if
436
+ it's blocked.
437
+
438
+ The same preview does it — pass the `cartId` with **no `destination`** and it
439
+ evaluates the cart's saved address against its *current* items server-side:
440
+
441
+ ```ts
442
+ import { checkAddressCompliance } from "medusa-plugin-ordinant/storefront"
443
+
444
+ // on entering checkout (any storefront):
445
+ const block = await checkAddressCompliance(sdk, { cartId }) // no destination = full current cart
446
+ if (block) {
447
+ // send them back to the shipping step and show `block`
448
+ }
449
+ ```
450
+
451
+ In the Medusa **Next.js starter**, do it in the checkout page (a server
452
+ component) so the redirect happens before payment/review render:
453
+
454
+ ```tsx
455
+ // src/app/[countryCode]/(checkout)/checkout/page.tsx
456
+ import { getCartComplianceBlock } from "@lib/data/cart" // thin wrapper over the preview
457
+ import { redirect } from "next/navigation"
458
+
459
+ const complianceBlock = await getCartComplianceBlock(cart.id)
460
+ if (complianceBlock && step !== "address" && step !== "delivery") {
461
+ redirect(`/${countryCode}/checkout?step=delivery`) // bounce back to shipping
462
+ }
463
+ // ...and render `complianceBlock` as a banner above <CheckoutForm />.
464
+ ```
465
+
466
+ It only redirects when the shopper is *past* the shipping step, and it targets
467
+ the shipping step — so there's no redirect loop, and they can fix the address or
468
+ remove the item. Fail-open, same as the address check.
469
+
470
+ **The three layers together:** (1) the address step vets the address as it's
471
+ entered, (2) checkout entry re-checks the whole cart, (3) the completeCart gate
472
+ is the non-bypassable backstop at "Place Order". Wire 1 and 2, or neither — 3
473
+ always holds.
474
+
418
475
  ## Onboarding an existing catalog
419
476
 
420
477
  Products created **after** installation classify automatically. For the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medusa-plugin-ordinant",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Ordinant compliance for Medusa v2: catalog classification with merchant attestation and a deterministic, citation-bearing checkout gate. Compliance information and automation - not legal advice.",
5
5
  "author": "Ordinant (https://ordinant.com)",
6
6
  "license": "MIT",