@tokenoftrust/storefront-runner 6.3.5 → 6.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.
@@ -58,7 +58,7 @@ function vapeRuleset(tenantId: string): ComplianceRuleset {
58
58
  { region: "UT", banAll: true, note: "UT restricts DTC vapor shipment" },
59
59
  { region: "VT", banAll: true, note: "VT prohibits DTC e-cigarette shipment to consumers" },
60
60
  { region: "AR", banAll: true, note: "AR restricts DTC vapor shipment" },
61
- // --- Flavored-ENDS bans (tobacco/menthol still permitted) ---
61
+ // --- Flavored-ENDS bans (plain tobacco/unflavoured only; menthol is a characterizing flavour) ---
62
62
  { region: "MA", banFlavored: true, note: "MA flavored tobacco/ENDS ban" },
63
63
  { region: "NY", banFlavored: true, note: "NY flavored e-cigarette ban" },
64
64
  { region: "NJ", banFlavored: true, note: "NJ flavored vape ban" },
@@ -9,11 +9,37 @@ export async function scopePreviewPinnedImageUrls(response: Response, basePath:
9
9
  return new Response(scoped, { status: response.status, statusText: response.statusText, headers });
10
10
  }
11
11
 
12
- /** Scope only exact captured-image addresses; platform assets and external URLs retain their host. */
12
+ const CAPTURED_ENTRY = String.raw`\/_a\/([0-9a-f]{64}(?:\.[A-Za-z0-9]+)?)`;
13
+ /** A URL-bearing attribute and its quoted or bare value. Form data (`value=`, `data-*`) never matches. */
14
+ const URL_ATTRIBUTE = /(\s(?:src|srcset|imagesrcset|xlink:href|href|poster)\s*=\s*)("[^"]*"|'[^']*'|[^\s"'=<>`]+)/gi;
15
+ /** A captured address that opens a URL or a srcset candidate inside an attribute value. */
16
+ const ATTRIBUTE_ENTRY = new RegExp(String.raw`(^["']?|[\s,])${CAPTURED_ENTRY}(?=[\s,"'?#]|$)`, "g");
17
+ /** A CSS `url(...)` reference, in a style element or a style attribute. */
18
+ const CSS_URL_ENTRY = new RegExp(String.raw`(url\(\s*["']?)${CAPTURED_ENTRY}(?=["'?#)\s])`, "gi");
19
+ /** A CSS `image-set(...)` / `image(...)` body, whose candidates may be bare quoted strings. */
20
+ const CSS_IMAGE_FUNCTION = /((?<![\w-])(?:-webkit-)?image(?:-set)?\()([^()]*(?:\([^()]*\)[^()]*)*)\)/g;
21
+ /** A quoted captured address inside an image function body. */
22
+ const QUOTED_ENTRY = new RegExp(String.raw`(["'])${CAPTURED_ENTRY}(?=["'?#])`, "g");
23
+ /** A social-image `<meta>` tag (og:image, twitter:image and their URL variants). */
24
+ const IMAGE_META = /<meta\b(?=[^>]*\b(?:property|name)\s*=\s*["']?(?:og|twitter):image(?::url|:secure_url|:src)?["'\s>/])[^>]*>/gi;
25
+ /** The `content` attribute of a tag and its quoted or bare value. */
26
+ const CONTENT_ATTRIBUTE = /(\scontent\s*=\s*)("[^"]*"|'[^']*'|[^\s"'=<>`]+)/i;
27
+
28
+ /**
29
+ * Scope only exact captured-image addresses in URL-bearing attributes (src, srcset, href,
30
+ * xlink:href, poster, a social-image meta `content`) and CSS url() / image-set() / image().
31
+ * Platform assets, external URLs and every other attribute — signed form values included — are
32
+ * left byte-for-byte.
33
+ */
13
34
  export function prefixPreviewPinnedImageUrls(html: string, basePath: string): string {
14
35
  if (!basePath) return html;
15
- return html.replace(/([\s"'(=,])\/_a\/([0-9a-f]{64}(?:\.[A-Za-z0-9]+)?)(?=[\s"'?#),<>]|$)/g,
16
- (_match, before: string, entry: string) => `${before}${basePath}/_a/${entry}`);
36
+ const scope = (_match: string, before: string, entry: string) => `${before}${basePath}/_a/${entry}`;
37
+ const scopeValue = (_match: string, name: string, value: string) => `${name}${value.replace(ATTRIBUTE_ENTRY, scope)}`;
38
+ return html
39
+ .replace(URL_ATTRIBUTE, scopeValue)
40
+ .replace(IMAGE_META, (tag) => tag.replace(CONTENT_ATTRIBUTE, scopeValue))
41
+ .replace(CSS_URL_ENTRY, scope)
42
+ .replace(CSS_IMAGE_FUNCTION, (_match, open: string, body: string) => `${open}${body.replace(QUOTED_ENTRY, scope)})`);
17
43
  }
18
44
 
19
45
  /** Choose asset routing from the resolved request tenant, never a second host-membership check. */
@@ -2,9 +2,13 @@
2
2
  * The admin Products tab's view of the store's real catalog. Two readiness measures stay separate:
3
3
  * listing readiness (can this product be listed: active, photographed, every variant priced and
4
4
  * SKU'd) and catalog completeness (how many accepted catalog facts are known). Tax readiness is
5
- * neither, and is not judged here.
5
+ * neither, and is not judged here. On a regulated store, listing readiness also requires the
6
+ * compliance classification checkout enforces: a product missing it is refused in flavour-ban
7
+ * states until the merchant classifies it
8
+ * (docs/devkit-staging/books/storefront-devbook-compliance-classification.md).
6
9
  */
7
- import type { CatalogFact, CatalogProduct } from "@tot/public-runtime";
10
+ import { complianceClassificationGaps, deriveProductProfile } from "@tot/public-runtime";
11
+ import type { CatalogFact, CatalogProduct, ComplianceClassificationGap } from "@tot/public-runtime";
8
12
  import type { AdminCatalog, CatalogOrigin } from "../services/catalogProducts";
9
13
 
10
14
  export interface ListingReadiness {
@@ -71,8 +75,28 @@ function money(amount: number, currency: string, locale: string): string {
71
75
 
72
76
  const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
73
77
 
74
- export function listingReadiness(product: CatalogProduct): ListingReadiness {
78
+ const CLASSIFICATION_MISSING: Record<ComplianceClassificationGap, string> = {
79
+ category: "Compliance category not classified",
80
+ flavor: "Flavour not classified",
81
+ };
82
+
83
+ /**
84
+ * The compliance classifications any of the product's variants is missing, in a stable order. A
85
+ * listing-projection record carries no metafields, so it cannot show a classification: it reports
86
+ * no gap rather than a false one.
87
+ */
88
+ export function classificationGaps(product: CatalogProduct): ComplianceClassificationGap[] {
89
+ if (!Array.isArray(product.metafields)) return [];
90
+ const variants = product.variants.length ? product.variants : [null];
91
+ const gaps = new Set(variants.flatMap((v) => complianceClassificationGaps(deriveProductProfile(product, v))));
92
+ return (["category", "flavor"] as const).filter((g) => gaps.has(g));
93
+ }
94
+
95
+ export function listingReadiness(product: CatalogProduct, regulated = false): ListingReadiness {
75
96
  const missing: string[] = [];
97
+ if (regulated) {
98
+ for (const gap of classificationGaps(product)) missing.push(`${CLASSIFICATION_MISSING[gap]} (refused in flavour-ban states)`);
99
+ }
76
100
  if (product.status !== "active") missing.push(`Status is ${product.status}`);
77
101
  if (!product.title.trim()) missing.push("No title");
78
102
  if (product.images.length === 0) missing.push("No photo");
@@ -95,7 +119,7 @@ export function catalogCompleteness(product: CatalogProduct): CatalogCompletenes
95
119
  };
96
120
  }
97
121
 
98
- export function toCatalogProductsViewModel(catalog: AdminCatalog, locale: string): CatalogProductsViewModel {
122
+ export function toCatalogProductsViewModel(catalog: AdminCatalog, locale: string, regulated = false): CatalogProductsViewModel {
99
123
  if (!catalog.ok) return { state: "error", message: catalog.message, failures: catalog.failures ?? [] };
100
124
  const rows: CatalogProductRow[] = [];
101
125
  const detailsByHandle: Record<string, CatalogProductDetail> = {};
@@ -111,7 +135,7 @@ export function toCatalogProductsViewModel(catalog: AdminCatalog, locale: string
111
135
  variantCount: p.variants.length,
112
136
  priceLabel: min === max ? money(min, currency, locale) : `${money(min, currency, locale)} – ${money(max, currency, locale)}`,
113
137
  imageUrl: [...p.images].sort((a, b) => a.position - b.position)[0]?.url ?? null,
114
- listing: listingReadiness(p),
138
+ listing: listingReadiness(p, regulated),
115
139
  catalog: catalogCompleteness(p),
116
140
  };
117
141
  rows.push(row);
@@ -15,6 +15,7 @@ import {
15
15
  sampleVariantCartPayload,
16
16
  visiblePurchaseOptions,
17
17
  isAutopilotEnabled,
18
+ tenantPublicOrigin,
18
19
  workerCartApiPath,
19
20
  } from "@tot/public-runtime";
20
21
  import { isLocalSampleCheckout } from "@/config/declaredTenantConfig";
@@ -69,6 +70,7 @@ export async function buildCardCartForm(input: {
69
70
  tenantId: commerceTenantId(input.tenant),
70
71
  currency: input.tenant.currency,
71
72
  contentVersionId: input.environment === "live" ? undefined : input.contentVersionId,
73
+ publicOrigin: tenantPublicOrigin(input.tenant),
72
74
  });
73
75
  const provider = input.tenant.serving?.provider === "s3-overwrite" ? "s3-overwrite" : "worker-r2-pointer";
74
76
  return {
@@ -32,6 +32,7 @@ import {
32
32
  withShippingPolicy,
33
33
  withReturnUrl,
34
34
  trustedCartReturnUrl,
35
+ tenantPublicOrigin,
35
36
  CHECKOUT_NAVIGATION_REFERRER_POLICY,
36
37
  SHIPPING_UNAVAILABLE_CART_MESSAGE,
37
38
  ShippingPolicyUnavailableError,
@@ -174,6 +175,19 @@ function submittedFromUrl(request: Request, tenant: TenantConfig): string | null
174
175
  return trustedCartReturnUrl(request.headers.get("referer"), hosts);
175
176
  }
176
177
 
178
+ /**
179
+ * The operator record of a refused cart: tenant, stage, denial code and the base names of the
180
+ * signed fields that failed. Never a field value, hash, the secret or any shopper data.
181
+ */
182
+ function logCartRefusal(context: APIContext, code: CartDenialCode, failedFields: readonly string[]): void {
183
+ console.warn(`[storefront] checkout-handoff refused ${JSON.stringify({
184
+ tenant: context.locals.tenant.tenant_id,
185
+ stage: context.locals.browserEnvironment?.environment ?? "unknown",
186
+ code,
187
+ failedFields,
188
+ })}`);
189
+ }
190
+
177
191
  /** Native cart POST. Browser preflight is not consulted. Payment is never authorized. */
178
192
  async function cartFormPost(context: APIContext): Promise<Response> {
179
193
  const { tenant, commerce } = context.locals;
@@ -207,6 +221,7 @@ async function cartFormPost(context: APIContext): Promise<Response> {
207
221
  trustedTenantId: commerceTenantId(tenant),
208
222
  });
209
223
  if (!result.ok) {
224
+ logCartRefusal(context, result.code, result.failedFields);
210
225
  const status = result.code === "signing-unavailable" ? 503 : 422;
211
226
  return cartDenial(result.code, shopperCartDenialMessage(result.code, result.message), status);
212
227
  }
@@ -333,6 +348,7 @@ export async function sampleCartGet(
333
348
  if (!signing.enabled) return sampleResponse('{"error":"sample_unavailable"}', 503);
334
349
  const payload = await buildVariantCartPayload(product, variant, {
335
350
  mode: "signed", secret: signing.secret, tenantId: commerceTenantId(tenant), currency: tenant.currency,
351
+ publicOrigin: tenantPublicOrigin(tenant),
336
352
  });
337
353
  const result = await enforceCartSubmission({
338
354
  entries: [...payload.hidden.map((f): [string, string] => [f.name, f.value]), [payload.quantityName, quantity]],
@@ -33,6 +33,7 @@ import {
33
33
  DEFAULT_STACKING_POLICY,
34
34
  readReviewAggregate,
35
35
  resolveProductDetails,
36
+ tenantPublicOrigin,
36
37
  } from "@tot/public-runtime";
37
38
  import type {
38
39
  ProductPurchaseOption,
@@ -268,6 +269,7 @@ if (commerce || sampleHandoff) {
268
269
  tenantId: commerceTenantId(tenant),
269
270
  currency: tenant.currency,
270
271
  contentVersionId: Astro.locals.browserEnvironment.environment === "live" ? undefined : Astro.locals.contentVersionId,
272
+ publicOrigin: tenantPublicOrigin(tenant),
271
273
  }),
272
274
  ] as const;
273
275
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "6.3.5",
3
+ "version": "6.5.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",
@@ -172,6 +172,32 @@ export interface BuildVariantCartPayloadOptions {
172
172
  * A publication page and a shared-channel preview omit it. Never taken from the form.
173
173
  */
174
174
  contentVersionId?: string;
175
+ /**
176
+ * The tenant's canonical public origin (`tenantPublicOrigin`), from server-side tenant config.
177
+ * A root-relative catalog image is signed as an absolute URL on it, so the checkout can load it
178
+ * and no page rewrite of the relative path can break the signature. Never a request value.
179
+ */
180
+ publicOrigin?: string;
181
+ }
182
+
183
+ /** `https://<canonicalDomain>` from server-side tenant config; undefined when it is not a bare host. */
184
+ export function tenantPublicOrigin(tenant: Pick<TenantConfig, "canonicalDomain">): string | undefined {
185
+ const host = tenant.canonicalDomain?.trim().toLowerCase();
186
+ return host && /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\d{1,5})?$/.test(host) ? `https://${host}` : undefined;
187
+ }
188
+
189
+ /**
190
+ * A root-relative image resolved against the public origin. Absolute and protocol-relative values,
191
+ * and any that fail to parse or would resolve onto another origin, are signed as they are.
192
+ */
193
+ function cartImageUrl(image: string, publicOrigin: string | undefined): string {
194
+ if (!publicOrigin || !image.startsWith("/") || image.startsWith("//")) return image;
195
+ try {
196
+ const resolved = new URL(image, publicOrigin);
197
+ return resolved.origin === new URL(publicOrigin).origin ? resolved.href : image;
198
+ } catch {
199
+ return image;
200
+ }
175
201
  }
176
202
 
177
203
  /** Declared public serving substrate (ADR 0013 `serving.provider`). */
@@ -527,11 +553,12 @@ export function cartFieldsForVariant(
527
553
  product: CatalogProduct,
528
554
  variant: CatalogVariant,
529
555
  purchaseOption?: ProductPurchaseOption,
530
- opts?: { signedPrice?: number; tenantId?: string; currency?: string; contentVersionId?: string },
556
+ opts?: { signedPrice?: number; tenantId?: string; currency?: string; contentVersionId?: string; publicOrigin?: string },
531
557
  ): { code: string; fields: CartField[] } {
532
558
  const code = cartLineCode(variant);
533
- const image = product.images.find((i) => i.id === variant.image_id)?.url ??
559
+ const catalogImage = product.images.find((i) => i.id === variant.image_id)?.url ??
534
560
  product.images[0]?.url;
561
+ const image = catalogImage ? cartImageUrl(catalogImage, opts?.publicOrigin) : undefined;
535
562
  // The signed price is either the caller's member-adjusted EFFECTIVE price
536
563
  // (F11b — the single signed source of truth, computed server-side from the
537
564
  // shopper's resolved eligibility) or, absent that, the list/subscription price.
@@ -762,7 +789,10 @@ export async function buildVariantCartPayload(
762
789
  product,
763
790
  variant,
764
791
  opts.purchaseOption,
765
- { signedPrice: opts.signedPrice, tenantId: opts.tenantId, currency: opts.currency, contentVersionId: opts.contentVersionId },
792
+ {
793
+ signedPrice: opts.signedPrice, tenantId: opts.tenantId, currency: opts.currency,
794
+ contentVersionId: opts.contentVersionId, publicOrigin: opts.publicOrigin,
795
+ },
766
796
  );
767
797
  const signed = opts.mode === "signed";
768
798
  if (signed && !opts.secret) {
@@ -796,7 +826,15 @@ export interface CartSubmissionField {
796
826
 
797
827
  export type CartEnforcement =
798
828
  | { ok: true; fields: CartSubmissionField[]; paymentAuthorized: false }
799
- | { ok: false; code: CartDenialCode; message: string; paymentAuthorized: false };
829
+ | {
830
+ ok: false; code: CartDenialCode; message: string; paymentAuthorized: false;
831
+ /**
832
+ * Base names of the signed fields that failed verification or were missing, for the operator
833
+ * log. Names only, never a value, hash or the secret; a name that is not a plain field name is
834
+ * reported as `(unrecognized)`.
835
+ */
836
+ failedFields: string[];
837
+ };
800
838
 
801
839
  interface ParsedCartField {
802
840
  rawName: string;
@@ -839,12 +877,26 @@ function parseSubmittedField(rawName: string, value: string): ParsedCartField {
839
877
  return { rawName, baseName: rawName, value, open: false, passthrough: false };
840
878
  }
841
879
 
842
- function denial(code: CartDenialCode): CartEnforcement {
880
+ const MAX_REPORTED_CART_FIELDS = 20;
881
+
882
+ /**
883
+ * A submitted base name safe to log. Native and internal names are reported as-is; any other
884
+ * (a variant option) only when it is a short letters-only label, so a crafted name can never
885
+ * carry digits, an address or markup into the log.
886
+ */
887
+ function reportedFieldName(baseName: string): string {
888
+ if ((NATIVE_CART_FIELDS as readonly string[]).includes(baseName) ||
889
+ (INTERNAL_CART_FIELDS as readonly string[]).includes(baseName)) return baseName;
890
+ return /^[A-Za-z][A-Za-z _.-]{0,63}$/.test(baseName) ? baseName : "(unrecognized)";
891
+ }
892
+
893
+ function denial(code: CartDenialCode, failedFields: Iterable<string> = []): CartEnforcement {
843
894
  return {
844
895
  ok: false,
845
896
  code,
846
897
  message: CART_DENIAL_MESSAGE[code],
847
898
  paymentAuthorized: false,
899
+ failedFields: [...new Set([...failedFields].map(reportedFieldName))].slice(0, MAX_REPORTED_CART_FIELDS),
848
900
  };
849
901
  }
850
902
 
@@ -875,14 +927,15 @@ export async function enforceCartSubmission(input: {
875
927
  const byBase = new Map<string, ParsedCartField>();
876
928
  for (const field of parsed) {
877
929
  if (field.passthrough) continue;
878
- if (byBase.has(field.baseName)) return denial("signature");
930
+ if (byBase.has(field.baseName)) return denial("signature", [field.baseName]);
879
931
  byBase.set(field.baseName, field);
880
932
  }
881
933
 
882
934
  const codeField = byBase.get("code");
883
- if (!codeField || !codeField.value) return denial("signature");
935
+ if (!codeField || !codeField.value) return denial("signature", ["code"]);
884
936
 
885
937
  const failures = new Set<CartDenialCode>();
938
+ const failedFields = new Set<string>();
886
939
  const signed = input.mode === "signed";
887
940
  const secret = input.secret ?? "";
888
941
 
@@ -894,10 +947,16 @@ export async function enforceCartSubmission(input: {
894
947
  };
895
948
 
896
949
  if (signed) {
897
- if (!(await hashOk(codeField))) failures.add("signature");
950
+ if (!(await hashOk(codeField))) {
951
+ failures.add("signature");
952
+ failedFields.add("code");
953
+ }
898
954
  for (const field of byBase.values()) {
899
955
  if (field.baseName === "code") continue;
900
- if (!(await hashOk(field))) failures.add(specificCode(field.baseName));
956
+ if (!(await hashOk(field))) {
957
+ failures.add(specificCode(field.baseName));
958
+ failedFields.add(field.baseName);
959
+ }
901
960
  }
902
961
  }
903
962
 
@@ -911,13 +970,16 @@ export async function enforceCartSubmission(input: {
911
970
  if (!quantityField || !quantityAllowed(quantityField.value)) failures.add("quantity");
912
971
  if (signed) {
913
972
  for (const required of ["name", "currency"]) {
914
- if (!byBase.get(required)) failures.add("signature");
973
+ if (!byBase.get(required)) {
974
+ failures.add("signature");
975
+ failedFields.add(required);
976
+ }
915
977
  }
916
978
  }
917
979
 
918
980
  const order: CartDenialCode[] = ["tenant", "price", "quantity", "signature"];
919
981
  for (const code of order) {
920
- if (failures.has(code)) return denial(code);
982
+ if (failures.has(code)) return denial(code, failedFields);
921
983
  }
922
984
 
923
985
  return {
@@ -12,13 +12,15 @@
12
12
  * enforcement wiring resolves (see `evidence-signals.ts`).
13
13
  *
14
14
  * Fail-closed everywhere: a regulated cart with no resolvable ruleset, an
15
- * unconfigured verification provider, or an unsatisfied excise obligation BLOCKS.
15
+ * unconfigured verification provider, an unsatisfied excise obligation, or a
16
+ * product the merchant has not classified shipping into a flavour-ban
17
+ * jurisdiction BLOCKS.
16
18
  * Pure + edge-safe.
17
19
  */
18
20
  import type { ComplianceRuleset } from "./ruleset.js";
19
21
  import { stateRuleFor, effectivePerOrderLimit } from "./ruleset.js";
20
- import type { ProductComplianceProfile } from "./profile.js";
21
- import { isRegulatedProfile } from "./profile.js";
22
+ import type { ComplianceClassificationGap, ProductComplianceProfile } from "./profile.js";
23
+ import { complianceClassificationGaps, isRegulatedProfile } from "./profile.js";
22
24
  import type { VerificationState } from "./verification.js";
23
25
  import { resolveVerificationDecision } from "./verification.js";
24
26
 
@@ -72,6 +74,12 @@ export interface ComplianceBlock {
72
74
  sku?: string;
73
75
  /** `flavored_prohibited` only: the characterizing flavor labels that triggered it. */
74
76
  flavors?: string[];
77
+ /**
78
+ * `flavored_prohibited` only: the classification the product is missing. Set
79
+ * when the block is the fail-closed default for an unclassified product
80
+ * rather than a recorded characterizing flavour.
81
+ */
82
+ unclassified?: ComplianceClassificationGap[];
75
83
  /** True when this block is a fail-closed safety default, not a real violation. */
76
84
  failedClosed?: boolean;
77
85
  }
@@ -120,6 +128,11 @@ export function evaluateCartCompliance(
120
128
  const blocks: ComplianceBlock[] = [];
121
129
  const regulatedLines = input.lines.filter((l) => isRegulatedProfile(l.profile));
122
130
  const hasRegulatedGoods = regulatedLines.length > 0;
131
+ // On a regulated tenant a product with no recorded category may be a
132
+ // regulated one: it needs a destination and fails closed where flavours are banned.
133
+ const unclassifiedLines = input.regulated
134
+ ? input.lines.filter((l) => !isRegulatedProfile(l.profile) && !l.profile.categoryClassified)
135
+ : [];
123
136
 
124
137
  const requirements: CartComplianceRequirements = {
125
138
  ageVerification: "not_required",
@@ -187,7 +200,7 @@ export function evaluateCartCompliance(
187
200
 
188
201
  // 5. Destination-dependent rules. If regulated goods but no ship-to state, the
189
202
  // cart cannot clear — the state must be known to apply jurisdiction rules.
190
- if (hasRegulatedGoods) {
203
+ if (hasRegulatedGoods || unclassifiedLines.length > 0) {
191
204
  if (!input.destination) {
192
205
  requirements.destination = "required";
193
206
  blocks.push({
@@ -201,7 +214,7 @@ export function evaluateCartCompliance(
201
214
  const stateRule = stateRuleFor(ruleset, region);
202
215
  if (stateRule?.adultSignatureRequired) requirements.adultSignature = true;
203
216
 
204
- for (const line of regulatedLines) {
217
+ for (const line of [...regulatedLines, ...unclassifiedLines]) {
205
218
  // Per-product ToT-synced restriction (data-driven per SKU).
206
219
  const perProductBanned = line.profile.restrictedRegions.includes(region);
207
220
  if (stateRule?.banAll || perProductBanned) {
@@ -215,6 +228,23 @@ export function evaluateCartCompliance(
215
228
  });
216
229
  continue;
217
230
  }
231
+ const gaps = complianceClassificationGaps(line.profile);
232
+ if (stateRule?.banFlavored && gaps.length > 0) {
233
+ blocks.push({
234
+ code: "flavored_prohibited",
235
+ region,
236
+ sku: line.sku,
237
+ flavors: [],
238
+ unclassified: gaps,
239
+ failedClosed: true,
240
+ message: `${line.title ?? line.sku} cannot be shipped to ${region}: its ${gaps
241
+ .map((g) => (g === "flavor" ? "flavour" : g))
242
+ .join(" and ")} is not classified, so it is treated as flavored (safe default = BLOCK)${
243
+ stateRule.note ? ` (${stateRule.note})` : ""
244
+ }.`,
245
+ });
246
+ continue;
247
+ }
218
248
  if (stateRule?.banFlavored && line.profile.isFlavored) {
219
249
  const flavors = line.profile.characterizingFlavors;
220
250
  blocks.push({
@@ -4,12 +4,21 @@
4
4
  * NO native compliance attributes — see `product.ts`). The signal sources, in
5
5
  * precedence order, are the ones the ToT catalog sync already populates:
6
6
  *
7
- * 1. `metafields[compliance/*]` — ToT-synced per-SKU compliance data
8
- * (`restricted_states` JSON, `product_category`). This is the authoritative,
9
- * data-driven per-product restriction source; it is NOT a hardcoded list.
7
+ * 1. `metafields[compliance/*]` — per-SKU compliance classification
8
+ * (`restricted_states` JSON, `product_category`, `flavor`). This is the
9
+ * authoritative, data-driven per-product source; it is NOT a hardcoded list.
10
+ * `docs/devkit-staging/books/storefront-devbook-compliance-classification.md`
11
+ * is how a store records it.
10
12
  * 2. `product_type` / `tags` — merchant taxonomy (nicotine/vape/e-liquid, the
11
13
  * `F: <flavor>` tag convention the PDP already reads).
12
- * 3. the chosen variant's `option_values` (Flavor, Nicotine strength).
14
+ * 3. BigCommerce custom fields (`bigcommerce_custom/Compliance Category`,
15
+ * `bigcommerce_custom/Flavor`), which the BigCommerce catalog extract
16
+ * carries as metafields.
17
+ * 4. the chosen variant's `option_values` (Flavor, Nicotine strength).
18
+ *
19
+ * A product the merchant has not classified is reported as such
20
+ * (`categoryClassified` / `flavorClassified`) so enforcement can fail closed
21
+ * instead of reading "no flavour label" as "unflavoured".
13
22
  *
14
23
  * Pure + edge-safe (no node, no I/O). The storefront resolves a product's profile
15
24
  * once and feeds it to `evaluateCartCompliance`.
@@ -28,8 +37,8 @@ export interface ProductComplianceProfile {
28
37
  /** Carries nicotine / is an ENDS product (the age-gate + excise axis). */
29
38
  containsNicotine: boolean;
30
39
  /**
31
- * Has a characterizing flavor beyond tobacco/menthol — the axis several states
32
- * ban for ENDS. `false` for tobacco/menthol/unflavored.
40
+ * Has a characterizing flavor (anything but plain tobacco or unflavoured;
41
+ * menthol IS characterizing) — the axis several states ban for ENDS.
33
42
  */
34
43
  isFlavored: boolean;
35
44
  /**
@@ -51,15 +60,26 @@ export interface ProductComplianceProfile {
51
60
  * per-SKU no-ship list — complements the tenant ruleset's jurisdiction rules.
52
61
  */
53
62
  restrictedRegions: string[];
63
+ /**
64
+ * The product's regulated category is recorded: a `compliance/product_category`
65
+ * metafield, or a merchant `product_type` that names a kind of product. A
66
+ * platform-generic type (BigCommerce `physical` / `digital`) is not one.
67
+ */
68
+ categoryClassified: boolean;
69
+ /** At least one flavour label is recorded (`F:` tag, flavour metafield/custom field, or variant option). */
70
+ flavorClassified: boolean;
54
71
  }
55
72
 
73
+ /** A classification the merchant still has to record before the product can be sold everywhere. */
74
+ export type ComplianceClassificationGap = "category" | "flavor";
75
+
56
76
  /**
57
77
  * Exact labels that do NOT count as "characterizing" for a flavored-ENDS ban.
58
78
  * Tobacco labels are classified by `isTobaccoFlavorLabel`.
59
79
  */
60
80
  const UNFLAVORED = new Set([
61
- "menthol",
62
81
  "unflavored",
82
+ "unflavoured",
63
83
  "none",
64
84
  "original",
65
85
  "plain",
@@ -128,6 +148,15 @@ function isCharacterizingFlavor(label: string): boolean {
128
148
  return !UNFLAVORED.has(label) && !isTobaccoFlavorLabel(label);
129
149
  }
130
150
 
151
+ /** Platform-default product types that say nothing about what the product is. */
152
+ const GENERIC_PRODUCT_TYPES = new Set(["physical", "digital", "gift_certificate"]);
153
+
154
+ /** Flavour field names a source catalog uses (variant options, BigCommerce custom fields). */
155
+ const FLAVOR_FIELD_NAMES = new Set(["flavor", "flavour", "flavors", "flavours"]);
156
+
157
+ /** BigCommerce custom-field names that carry the compliance category. */
158
+ const CATEGORY_FIELD_NAMES = new Set(["compliance category", "product category", "product_category"]);
159
+
131
160
  const NICOTINE_HINTS = [
132
161
  "nicotine",
133
162
  "vape",
@@ -159,6 +188,17 @@ function readMetafield(
159
188
  return mf?.value?.trim() || undefined;
160
189
  }
161
190
 
191
+ /** A BigCommerce custom field (`bigcommerce_custom/<name>`), matched case-insensitively by name. */
192
+ function readCustomField(
193
+ product: Pick<CatalogProduct, "metafields">,
194
+ names: ReadonlySet<string>,
195
+ ): string | undefined {
196
+ const mf = product.metafields.find(
197
+ (m) => m.namespace === "bigcommerce_custom" && names.has(m.key.trim().toLowerCase()),
198
+ );
199
+ return mf?.value?.trim() || undefined;
200
+ }
201
+
162
202
  /** Parse `compliance.restricted_states` — a JSON array OR an object keyed by code. */
163
203
  function parseRestrictedRegions(raw: string | undefined): string[] {
164
204
  if (!raw) return [];
@@ -178,11 +218,13 @@ function parseRestrictedRegions(raw: string | undefined): string[] {
178
218
  }
179
219
 
180
220
  /**
181
- * Collect flavor labels from the `F: <flavor>` tag convention + a chosen
182
- * variant, dropping nicotine-format labels.
221
+ * Collect flavor labels from the `F: <flavor>` tag convention, the
222
+ * `compliance/flavor` metafield, a BigCommerce `Flavor` custom field and the
223
+ * chosen variant's flavour option, dropping nicotine-format labels. A
224
+ * metafield/custom-field value may list several labels separated by `,` `;` `|`.
183
225
  */
184
226
  function collectFlavors(
185
- product: Pick<CatalogProduct, "tags">,
227
+ product: Pick<CatalogProduct, "tags" | "metafields">,
186
228
  variant?: Pick<CatalogVariant, "option_values"> | null,
187
229
  ): string[] {
188
230
  const labels = new Set<string>();
@@ -190,11 +232,20 @@ function collectFlavors(
190
232
  const v = raw.trim().toLowerCase();
191
233
  if (v && !NICOTINE_FORMAT_LABELS.has(v)) labels.add(v);
192
234
  };
235
+ const addList = (raw: string | undefined) => {
236
+ for (const part of (raw ?? "").split(/[,;|]/)) add(part);
237
+ };
193
238
  for (const tag of product.tags ?? []) {
194
239
  if (tag.startsWith("F: ")) add(tag.slice(3));
195
240
  }
196
- const optFlavor = variant?.option_values?.["Flavor"];
197
- if (optFlavor) add(optFlavor);
241
+ for (const mf of product.metafields ?? []) {
242
+ const key = mf.key.trim().toLowerCase();
243
+ if (mf.namespace === "compliance" && key === "flavor") addList(mf.value);
244
+ if (mf.namespace === "bigcommerce_custom" && FLAVOR_FIELD_NAMES.has(key)) addList(mf.value);
245
+ }
246
+ for (const [name, value] of Object.entries(variant?.option_values ?? {})) {
247
+ if (FLAVOR_FIELD_NAMES.has(name.trim().toLowerCase()) && value) add(value);
248
+ }
198
249
  return [...labels];
199
250
  }
200
251
 
@@ -214,10 +265,15 @@ export function deriveProductProfile(
214
265
  >,
215
266
  variant?: Pick<CatalogVariant, "option_values"> | null,
216
267
  ): ProductComplianceProfile {
217
- const category = readMetafield(product, "compliance", "product_category");
218
- const type = (product.product_type ?? category ?? "").toLowerCase();
268
+ const category =
269
+ readMetafield(product, "compliance", "product_category") ??
270
+ readCustomField(product, CATEGORY_FIELD_NAMES);
271
+ const productType = product.product_type?.trim().toLowerCase() ?? "";
272
+ const merchantType = GENERIC_PRODUCT_TYPES.has(productType) ? "" : productType;
273
+ const categoryClassified = Boolean(category) || merchantType.length > 0;
219
274
  const haystack = [
220
- type,
275
+ merchantType,
276
+ (category ?? "").toLowerCase(),
221
277
  (product.title ?? "").toLowerCase(),
222
278
  ...(product.tags ?? []).map((t) => t.toLowerCase()),
223
279
  ].join(" ");
@@ -261,9 +317,26 @@ export function deriveProductProfile(
261
317
  characterizingFlavors,
262
318
  category: cat,
263
319
  restrictedRegions,
320
+ categoryClassified,
321
+ flavorClassified: flavorLabels.length > 0,
264
322
  };
265
323
  }
266
324
 
325
+ /**
326
+ * What the merchant still has to classify for this profile. A product whose
327
+ * category is unknown cannot be judged at all; a nicotine product with no
328
+ * flavour label cannot be judged in a flavour-ban jurisdiction. Enforcement
329
+ * blocks both there, and Admin readiness lists them.
330
+ */
331
+ export function complianceClassificationGaps(
332
+ p: ProductComplianceProfile,
333
+ ): ComplianceClassificationGap[] {
334
+ const gaps: ComplianceClassificationGap[] = [];
335
+ if (!p.categoryClassified) gaps.push("category");
336
+ if (p.containsNicotine && !p.flavorClassified) gaps.push("flavor");
337
+ return gaps;
338
+ }
339
+
267
340
  /** True when a profile carries ANY regulated characteristic that enforcement covers. */
268
341
  export function isRegulatedProfile(p: ProductComplianceProfile): boolean {
269
342
  return (
@@ -22,7 +22,11 @@ export interface StateComplianceRule {
22
22
  region: string;
23
23
  /** No regulated (ENDS/vapor) shipments to this region at all (state no-ship). */
24
24
  banAll?: boolean;
25
- /** Flavored-ENDS ban — tobacco/menthol still allowed, characterizing flavors not. */
25
+ /**
26
+ * Flavored-ENDS ban — plain tobacco and unflavoured still allowed; menthol and
27
+ * every other characterizing flavour are not. A product with no recorded
28
+ * classification is blocked here (fail closed).
29
+ */
26
30
  banFlavored?: boolean;
27
31
  /** Per-order regulated-unit cap for this region (overrides the ruleset default). */
28
32
  perOrderUnitLimit?: number;
@@ -282,6 +282,8 @@ export interface MemberSignedCartPayloadOptions {
282
282
  currency?: string;
283
283
  /** Candidate content pin the preview page rendered. Omitted for publication and the shared channel. */
284
284
  contentVersionId?: string;
285
+ /** Tenant public origin a relative image is signed on (see {@link BuildVariantCartPayloadOptions.publicOrigin}). */
286
+ publicOrigin?: string;
285
287
  }
286
288
 
287
289
  /**
@@ -318,6 +320,7 @@ export async function buildMemberSignedCartPayload(
318
320
  tenantId: opts.tenantId,
319
321
  currency: opts.currency,
320
322
  contentVersionId: opts.contentVersionId,
323
+ publicOrigin: opts.publicOrigin,
321
324
  });
322
325
  }
323
326
 
@@ -5,7 +5,7 @@
5
5
  "products": {
6
6
  "unflavored": {"containsNicotine":true,"isFlavored":false,"category":"ends","regulated":true,"restrictedRegions":[]},
7
7
  "tobacco-only": {"containsNicotine":true,"isFlavored":false,"category":"ends","regulated":true,"restrictedRegions":[]},
8
- "menthol": {"containsNicotine":true,"isFlavored":false,"category":"ends","regulated":true,"restrictedRegions":[]},
8
+ "menthol": {"containsNicotine":true,"isFlavored":true,"category":"ends","regulated":true,"restrictedRegions":[]},
9
9
  "tobacco-qualified-american": {"containsNicotine":true,"isFlavored":false,"category":"ends","regulated":true,"restrictedRegions":[]},
10
10
  "tobacco-qualified-caramel": {"containsNicotine":true,"isFlavored":true,"category":"ends","regulated":true,"restrictedRegions":[]},
11
11
  "flavored-mango": {"containsNicotine":true,"isFlavored":true,"category":"ends","regulated":true,"restrictedRegions":[]},
@@ -161,18 +161,18 @@
161
161
  {"id":"AR|non-regulated|q9","region":"AR","product":"non-regulated","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
162
162
  {"id":"AR|non-regulated|q10","region":"AR","product":"non-regulated","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
163
163
  {"id":"AR|non-regulated|q11","region":"AR","product":"non-regulated","quantity":11,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
164
- {"id":"MA|unflavored|q1","region":"MA","product":"unflavored","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
165
- {"id":"MA|unflavored|q9","region":"MA","product":"unflavored","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
166
- {"id":"MA|unflavored|q10","region":"MA","product":"unflavored","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
167
- {"id":"MA|unflavored|q11","region":"MA","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
164
+ {"id":"MA|unflavored|q1","region":"MA","product":"unflavored","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
165
+ {"id":"MA|unflavored|q9","region":"MA","product":"unflavored","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
166
+ {"id":"MA|unflavored|q10","region":"MA","product":"unflavored","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
167
+ {"id":"MA|unflavored|q11","region":"MA","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
168
168
  {"id":"MA|tobacco-only|q1","region":"MA","product":"tobacco-only","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
169
169
  {"id":"MA|tobacco-only|q9","region":"MA","product":"tobacco-only","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
170
170
  {"id":"MA|tobacco-only|q10","region":"MA","product":"tobacco-only","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
171
171
  {"id":"MA|tobacco-only|q11","region":"MA","product":"tobacco-only","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
172
- {"id":"MA|menthol|q1","region":"MA","product":"menthol","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
173
- {"id":"MA|menthol|q9","region":"MA","product":"menthol","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
174
- {"id":"MA|menthol|q10","region":"MA","product":"menthol","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
175
- {"id":"MA|menthol|q11","region":"MA","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
172
+ {"id":"MA|menthol|q1","region":"MA","product":"menthol","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
173
+ {"id":"MA|menthol|q9","region":"MA","product":"menthol","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
174
+ {"id":"MA|menthol|q10","region":"MA","product":"menthol","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
175
+ {"id":"MA|menthol|q11","region":"MA","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
176
176
  {"id":"MA|tobacco-qualified-american|q1","region":"MA","product":"tobacco-qualified-american","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
177
177
  {"id":"MA|tobacco-qualified-american|q9","region":"MA","product":"tobacco-qualified-american","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
178
178
  {"id":"MA|tobacco-qualified-american|q10","region":"MA","product":"tobacco-qualified-american","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -189,18 +189,18 @@
189
189
  {"id":"MA|flavored-via-variant-option|q9","region":"MA","product":"flavored-via-variant-option","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
190
190
  {"id":"MA|flavored-via-variant-option|q10","region":"MA","product":"flavored-via-variant-option","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
191
191
  {"id":"MA|flavored-via-variant-option|q11","region":"MA","product":"flavored-via-variant-option","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
192
- {"id":"MA|nicotine-format-label|q1","region":"MA","product":"nicotine-format-label","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
193
- {"id":"MA|nicotine-format-label|q9","region":"MA","product":"nicotine-format-label","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
194
- {"id":"MA|nicotine-format-label|q10","region":"MA","product":"nicotine-format-label","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
195
- {"id":"MA|nicotine-format-label|q11","region":"MA","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
192
+ {"id":"MA|nicotine-format-label|q1","region":"MA","product":"nicotine-format-label","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
193
+ {"id":"MA|nicotine-format-label|q9","region":"MA","product":"nicotine-format-label","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
194
+ {"id":"MA|nicotine-format-label|q10","region":"MA","product":"nicotine-format-label","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
195
+ {"id":"MA|nicotine-format-label|q11","region":"MA","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
196
196
  {"id":"MA|zero-mg-flavored-eliquid|q1","region":"MA","product":"zero-mg-flavored-eliquid","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
197
197
  {"id":"MA|zero-mg-flavored-eliquid|q9","region":"MA","product":"zero-mg-flavored-eliquid","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
198
198
  {"id":"MA|zero-mg-flavored-eliquid|q10","region":"MA","product":"zero-mg-flavored-eliquid","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
199
199
  {"id":"MA|zero-mg-flavored-eliquid|q11","region":"MA","product":"zero-mg-flavored-eliquid","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
200
- {"id":"MA|per-product-restricted-tx|q1","region":"MA","product":"per-product-restricted-tx","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
201
- {"id":"MA|per-product-restricted-tx|q9","region":"MA","product":"per-product-restricted-tx","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
202
- {"id":"MA|per-product-restricted-tx|q10","region":"MA","product":"per-product-restricted-tx","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
203
- {"id":"MA|per-product-restricted-tx|q11","region":"MA","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
200
+ {"id":"MA|per-product-restricted-tx|q1","region":"MA","product":"per-product-restricted-tx","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
201
+ {"id":"MA|per-product-restricted-tx|q9","region":"MA","product":"per-product-restricted-tx","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
202
+ {"id":"MA|per-product-restricted-tx|q10","region":"MA","product":"per-product-restricted-tx","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
203
+ {"id":"MA|per-product-restricted-tx|q11","region":"MA","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
204
204
  {"id":"MA|accessory-coil|q1","region":"MA","product":"accessory-coil","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
205
205
  {"id":"MA|accessory-coil|q9","region":"MA","product":"accessory-coil","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
206
206
  {"id":"MA|accessory-coil|q10","region":"MA","product":"accessory-coil","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -209,18 +209,18 @@
209
209
  {"id":"MA|non-regulated|q9","region":"MA","product":"non-regulated","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
210
210
  {"id":"MA|non-regulated|q10","region":"MA","product":"non-regulated","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
211
211
  {"id":"MA|non-regulated|q11","region":"MA","product":"non-regulated","quantity":11,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
212
- {"id":"NY|unflavored|q1","region":"NY","product":"unflavored","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
213
- {"id":"NY|unflavored|q9","region":"NY","product":"unflavored","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
214
- {"id":"NY|unflavored|q10","region":"NY","product":"unflavored","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
215
- {"id":"NY|unflavored|q11","region":"NY","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
212
+ {"id":"NY|unflavored|q1","region":"NY","product":"unflavored","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
213
+ {"id":"NY|unflavored|q9","region":"NY","product":"unflavored","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
214
+ {"id":"NY|unflavored|q10","region":"NY","product":"unflavored","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
215
+ {"id":"NY|unflavored|q11","region":"NY","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
216
216
  {"id":"NY|tobacco-only|q1","region":"NY","product":"tobacco-only","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
217
217
  {"id":"NY|tobacco-only|q9","region":"NY","product":"tobacco-only","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
218
218
  {"id":"NY|tobacco-only|q10","region":"NY","product":"tobacco-only","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
219
219
  {"id":"NY|tobacco-only|q11","region":"NY","product":"tobacco-only","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
220
- {"id":"NY|menthol|q1","region":"NY","product":"menthol","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
221
- {"id":"NY|menthol|q9","region":"NY","product":"menthol","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
222
- {"id":"NY|menthol|q10","region":"NY","product":"menthol","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
223
- {"id":"NY|menthol|q11","region":"NY","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
220
+ {"id":"NY|menthol|q1","region":"NY","product":"menthol","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
221
+ {"id":"NY|menthol|q9","region":"NY","product":"menthol","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
222
+ {"id":"NY|menthol|q10","region":"NY","product":"menthol","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
223
+ {"id":"NY|menthol|q11","region":"NY","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
224
224
  {"id":"NY|tobacco-qualified-american|q1","region":"NY","product":"tobacco-qualified-american","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
225
225
  {"id":"NY|tobacco-qualified-american|q9","region":"NY","product":"tobacco-qualified-american","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
226
226
  {"id":"NY|tobacco-qualified-american|q10","region":"NY","product":"tobacco-qualified-american","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -237,18 +237,18 @@
237
237
  {"id":"NY|flavored-via-variant-option|q9","region":"NY","product":"flavored-via-variant-option","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
238
238
  {"id":"NY|flavored-via-variant-option|q10","region":"NY","product":"flavored-via-variant-option","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
239
239
  {"id":"NY|flavored-via-variant-option|q11","region":"NY","product":"flavored-via-variant-option","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
240
- {"id":"NY|nicotine-format-label|q1","region":"NY","product":"nicotine-format-label","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
241
- {"id":"NY|nicotine-format-label|q9","region":"NY","product":"nicotine-format-label","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
242
- {"id":"NY|nicotine-format-label|q10","region":"NY","product":"nicotine-format-label","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
243
- {"id":"NY|nicotine-format-label|q11","region":"NY","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
240
+ {"id":"NY|nicotine-format-label|q1","region":"NY","product":"nicotine-format-label","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
241
+ {"id":"NY|nicotine-format-label|q9","region":"NY","product":"nicotine-format-label","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
242
+ {"id":"NY|nicotine-format-label|q10","region":"NY","product":"nicotine-format-label","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
243
+ {"id":"NY|nicotine-format-label|q11","region":"NY","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
244
244
  {"id":"NY|zero-mg-flavored-eliquid|q1","region":"NY","product":"zero-mg-flavored-eliquid","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
245
245
  {"id":"NY|zero-mg-flavored-eliquid|q9","region":"NY","product":"zero-mg-flavored-eliquid","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
246
246
  {"id":"NY|zero-mg-flavored-eliquid|q10","region":"NY","product":"zero-mg-flavored-eliquid","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
247
247
  {"id":"NY|zero-mg-flavored-eliquid|q11","region":"NY","product":"zero-mg-flavored-eliquid","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
248
- {"id":"NY|per-product-restricted-tx|q1","region":"NY","product":"per-product-restricted-tx","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
249
- {"id":"NY|per-product-restricted-tx|q9","region":"NY","product":"per-product-restricted-tx","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
250
- {"id":"NY|per-product-restricted-tx|q10","region":"NY","product":"per-product-restricted-tx","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
251
- {"id":"NY|per-product-restricted-tx|q11","region":"NY","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
248
+ {"id":"NY|per-product-restricted-tx|q1","region":"NY","product":"per-product-restricted-tx","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
249
+ {"id":"NY|per-product-restricted-tx|q9","region":"NY","product":"per-product-restricted-tx","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
250
+ {"id":"NY|per-product-restricted-tx|q10","region":"NY","product":"per-product-restricted-tx","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
251
+ {"id":"NY|per-product-restricted-tx|q11","region":"NY","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
252
252
  {"id":"NY|accessory-coil|q1","region":"NY","product":"accessory-coil","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
253
253
  {"id":"NY|accessory-coil|q9","region":"NY","product":"accessory-coil","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
254
254
  {"id":"NY|accessory-coil|q10","region":"NY","product":"accessory-coil","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -257,18 +257,18 @@
257
257
  {"id":"NY|non-regulated|q9","region":"NY","product":"non-regulated","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
258
258
  {"id":"NY|non-regulated|q10","region":"NY","product":"non-regulated","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
259
259
  {"id":"NY|non-regulated|q11","region":"NY","product":"non-regulated","quantity":11,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
260
- {"id":"NJ|unflavored|q1","region":"NJ","product":"unflavored","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
261
- {"id":"NJ|unflavored|q9","region":"NJ","product":"unflavored","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
262
- {"id":"NJ|unflavored|q10","region":"NJ","product":"unflavored","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
263
- {"id":"NJ|unflavored|q11","region":"NJ","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
260
+ {"id":"NJ|unflavored|q1","region":"NJ","product":"unflavored","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
261
+ {"id":"NJ|unflavored|q9","region":"NJ","product":"unflavored","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
262
+ {"id":"NJ|unflavored|q10","region":"NJ","product":"unflavored","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
263
+ {"id":"NJ|unflavored|q11","region":"NJ","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
264
264
  {"id":"NJ|tobacco-only|q1","region":"NJ","product":"tobacco-only","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
265
265
  {"id":"NJ|tobacco-only|q9","region":"NJ","product":"tobacco-only","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
266
266
  {"id":"NJ|tobacco-only|q10","region":"NJ","product":"tobacco-only","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
267
267
  {"id":"NJ|tobacco-only|q11","region":"NJ","product":"tobacco-only","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
268
- {"id":"NJ|menthol|q1","region":"NJ","product":"menthol","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
269
- {"id":"NJ|menthol|q9","region":"NJ","product":"menthol","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
270
- {"id":"NJ|menthol|q10","region":"NJ","product":"menthol","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
271
- {"id":"NJ|menthol|q11","region":"NJ","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
268
+ {"id":"NJ|menthol|q1","region":"NJ","product":"menthol","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
269
+ {"id":"NJ|menthol|q9","region":"NJ","product":"menthol","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
270
+ {"id":"NJ|menthol|q10","region":"NJ","product":"menthol","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
271
+ {"id":"NJ|menthol|q11","region":"NJ","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
272
272
  {"id":"NJ|tobacco-qualified-american|q1","region":"NJ","product":"tobacco-qualified-american","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
273
273
  {"id":"NJ|tobacco-qualified-american|q9","region":"NJ","product":"tobacco-qualified-american","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
274
274
  {"id":"NJ|tobacco-qualified-american|q10","region":"NJ","product":"tobacco-qualified-american","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -285,18 +285,18 @@
285
285
  {"id":"NJ|flavored-via-variant-option|q9","region":"NJ","product":"flavored-via-variant-option","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
286
286
  {"id":"NJ|flavored-via-variant-option|q10","region":"NJ","product":"flavored-via-variant-option","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
287
287
  {"id":"NJ|flavored-via-variant-option|q11","region":"NJ","product":"flavored-via-variant-option","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
288
- {"id":"NJ|nicotine-format-label|q1","region":"NJ","product":"nicotine-format-label","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
289
- {"id":"NJ|nicotine-format-label|q9","region":"NJ","product":"nicotine-format-label","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
290
- {"id":"NJ|nicotine-format-label|q10","region":"NJ","product":"nicotine-format-label","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
291
- {"id":"NJ|nicotine-format-label|q11","region":"NJ","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
288
+ {"id":"NJ|nicotine-format-label|q1","region":"NJ","product":"nicotine-format-label","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
289
+ {"id":"NJ|nicotine-format-label|q9","region":"NJ","product":"nicotine-format-label","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
290
+ {"id":"NJ|nicotine-format-label|q10","region":"NJ","product":"nicotine-format-label","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
291
+ {"id":"NJ|nicotine-format-label|q11","region":"NJ","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
292
292
  {"id":"NJ|zero-mg-flavored-eliquid|q1","region":"NJ","product":"zero-mg-flavored-eliquid","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
293
293
  {"id":"NJ|zero-mg-flavored-eliquid|q9","region":"NJ","product":"zero-mg-flavored-eliquid","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
294
294
  {"id":"NJ|zero-mg-flavored-eliquid|q10","region":"NJ","product":"zero-mg-flavored-eliquid","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
295
295
  {"id":"NJ|zero-mg-flavored-eliquid|q11","region":"NJ","product":"zero-mg-flavored-eliquid","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
296
- {"id":"NJ|per-product-restricted-tx|q1","region":"NJ","product":"per-product-restricted-tx","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
297
- {"id":"NJ|per-product-restricted-tx|q9","region":"NJ","product":"per-product-restricted-tx","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
298
- {"id":"NJ|per-product-restricted-tx|q10","region":"NJ","product":"per-product-restricted-tx","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
299
- {"id":"NJ|per-product-restricted-tx|q11","region":"NJ","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
296
+ {"id":"NJ|per-product-restricted-tx|q1","region":"NJ","product":"per-product-restricted-tx","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
297
+ {"id":"NJ|per-product-restricted-tx|q9","region":"NJ","product":"per-product-restricted-tx","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
298
+ {"id":"NJ|per-product-restricted-tx|q10","region":"NJ","product":"per-product-restricted-tx","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
299
+ {"id":"NJ|per-product-restricted-tx|q11","region":"NJ","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
300
300
  {"id":"NJ|accessory-coil|q1","region":"NJ","product":"accessory-coil","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
301
301
  {"id":"NJ|accessory-coil|q9","region":"NJ","product":"accessory-coil","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
302
302
  {"id":"NJ|accessory-coil|q10","region":"NJ","product":"accessory-coil","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -305,18 +305,18 @@
305
305
  {"id":"NJ|non-regulated|q9","region":"NJ","product":"non-regulated","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
306
306
  {"id":"NJ|non-regulated|q10","region":"NJ","product":"non-regulated","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
307
307
  {"id":"NJ|non-regulated|q11","region":"NJ","product":"non-regulated","quantity":11,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
308
- {"id":"RI|unflavored|q1","region":"RI","product":"unflavored","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
309
- {"id":"RI|unflavored|q9","region":"RI","product":"unflavored","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
310
- {"id":"RI|unflavored|q10","region":"RI","product":"unflavored","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
311
- {"id":"RI|unflavored|q11","region":"RI","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
308
+ {"id":"RI|unflavored|q1","region":"RI","product":"unflavored","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
309
+ {"id":"RI|unflavored|q9","region":"RI","product":"unflavored","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
310
+ {"id":"RI|unflavored|q10","region":"RI","product":"unflavored","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
311
+ {"id":"RI|unflavored|q11","region":"RI","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
312
312
  {"id":"RI|tobacco-only|q1","region":"RI","product":"tobacco-only","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
313
313
  {"id":"RI|tobacco-only|q9","region":"RI","product":"tobacco-only","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
314
314
  {"id":"RI|tobacco-only|q10","region":"RI","product":"tobacco-only","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
315
315
  {"id":"RI|tobacco-only|q11","region":"RI","product":"tobacco-only","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
316
- {"id":"RI|menthol|q1","region":"RI","product":"menthol","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
317
- {"id":"RI|menthol|q9","region":"RI","product":"menthol","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
318
- {"id":"RI|menthol|q10","region":"RI","product":"menthol","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
319
- {"id":"RI|menthol|q11","region":"RI","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
316
+ {"id":"RI|menthol|q1","region":"RI","product":"menthol","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
317
+ {"id":"RI|menthol|q9","region":"RI","product":"menthol","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
318
+ {"id":"RI|menthol|q10","region":"RI","product":"menthol","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
319
+ {"id":"RI|menthol|q11","region":"RI","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
320
320
  {"id":"RI|tobacco-qualified-american|q1","region":"RI","product":"tobacco-qualified-american","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
321
321
  {"id":"RI|tobacco-qualified-american|q9","region":"RI","product":"tobacco-qualified-american","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
322
322
  {"id":"RI|tobacco-qualified-american|q10","region":"RI","product":"tobacco-qualified-american","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -333,18 +333,18 @@
333
333
  {"id":"RI|flavored-via-variant-option|q9","region":"RI","product":"flavored-via-variant-option","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
334
334
  {"id":"RI|flavored-via-variant-option|q10","region":"RI","product":"flavored-via-variant-option","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
335
335
  {"id":"RI|flavored-via-variant-option|q11","region":"RI","product":"flavored-via-variant-option","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
336
- {"id":"RI|nicotine-format-label|q1","region":"RI","product":"nicotine-format-label","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
337
- {"id":"RI|nicotine-format-label|q9","region":"RI","product":"nicotine-format-label","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
338
- {"id":"RI|nicotine-format-label|q10","region":"RI","product":"nicotine-format-label","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
339
- {"id":"RI|nicotine-format-label|q11","region":"RI","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
336
+ {"id":"RI|nicotine-format-label|q1","region":"RI","product":"nicotine-format-label","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
337
+ {"id":"RI|nicotine-format-label|q9","region":"RI","product":"nicotine-format-label","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
338
+ {"id":"RI|nicotine-format-label|q10","region":"RI","product":"nicotine-format-label","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
339
+ {"id":"RI|nicotine-format-label|q11","region":"RI","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
340
340
  {"id":"RI|zero-mg-flavored-eliquid|q1","region":"RI","product":"zero-mg-flavored-eliquid","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
341
341
  {"id":"RI|zero-mg-flavored-eliquid|q9","region":"RI","product":"zero-mg-flavored-eliquid","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
342
342
  {"id":"RI|zero-mg-flavored-eliquid|q10","region":"RI","product":"zero-mg-flavored-eliquid","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
343
343
  {"id":"RI|zero-mg-flavored-eliquid|q11","region":"RI","product":"zero-mg-flavored-eliquid","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
344
- {"id":"RI|per-product-restricted-tx|q1","region":"RI","product":"per-product-restricted-tx","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
345
- {"id":"RI|per-product-restricted-tx|q9","region":"RI","product":"per-product-restricted-tx","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
346
- {"id":"RI|per-product-restricted-tx|q10","region":"RI","product":"per-product-restricted-tx","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
347
- {"id":"RI|per-product-restricted-tx|q11","region":"RI","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
344
+ {"id":"RI|per-product-restricted-tx|q1","region":"RI","product":"per-product-restricted-tx","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
345
+ {"id":"RI|per-product-restricted-tx|q9","region":"RI","product":"per-product-restricted-tx","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
346
+ {"id":"RI|per-product-restricted-tx|q10","region":"RI","product":"per-product-restricted-tx","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
347
+ {"id":"RI|per-product-restricted-tx|q11","region":"RI","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
348
348
  {"id":"RI|accessory-coil|q1","region":"RI","product":"accessory-coil","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
349
349
  {"id":"RI|accessory-coil|q9","region":"RI","product":"accessory-coil","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
350
350
  {"id":"RI|accessory-coil|q10","region":"RI","product":"accessory-coil","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -353,18 +353,18 @@
353
353
  {"id":"RI|non-regulated|q9","region":"RI","product":"non-regulated","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
354
354
  {"id":"RI|non-regulated|q10","region":"RI","product":"non-regulated","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
355
355
  {"id":"RI|non-regulated|q11","region":"RI","product":"non-regulated","quantity":11,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":null,"limitExceeded":false},
356
- {"id":"CA|unflavored|q1","region":"CA","product":"unflavored","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
357
- {"id":"CA|unflavored|q9","region":"CA","product":"unflavored","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
358
- {"id":"CA|unflavored|q10","region":"CA","product":"unflavored","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
359
- {"id":"CA|unflavored|q11","region":"CA","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
356
+ {"id":"CA|unflavored|q1","region":"CA","product":"unflavored","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
357
+ {"id":"CA|unflavored|q9","region":"CA","product":"unflavored","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
358
+ {"id":"CA|unflavored|q10","region":"CA","product":"unflavored","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
359
+ {"id":"CA|unflavored|q11","region":"CA","product":"unflavored","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
360
360
  {"id":"CA|tobacco-only|q1","region":"CA","product":"tobacco-only","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
361
361
  {"id":"CA|tobacco-only|q9","region":"CA","product":"tobacco-only","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
362
362
  {"id":"CA|tobacco-only|q10","region":"CA","product":"tobacco-only","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
363
363
  {"id":"CA|tobacco-only|q11","region":"CA","product":"tobacco-only","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
364
- {"id":"CA|menthol|q1","region":"CA","product":"menthol","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
365
- {"id":"CA|menthol|q9","region":"CA","product":"menthol","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
366
- {"id":"CA|menthol|q10","region":"CA","product":"menthol","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
367
- {"id":"CA|menthol|q11","region":"CA","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
364
+ {"id":"CA|menthol|q1","region":"CA","product":"menthol","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
365
+ {"id":"CA|menthol|q9","region":"CA","product":"menthol","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
366
+ {"id":"CA|menthol|q10","region":"CA","product":"menthol","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
367
+ {"id":"CA|menthol|q11","region":"CA","product":"menthol","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
368
368
  {"id":"CA|tobacco-qualified-american|q1","region":"CA","product":"tobacco-qualified-american","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
369
369
  {"id":"CA|tobacco-qualified-american|q9","region":"CA","product":"tobacco-qualified-american","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
370
370
  {"id":"CA|tobacco-qualified-american|q10","region":"CA","product":"tobacco-qualified-american","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
@@ -381,18 +381,18 @@
381
381
  {"id":"CA|flavored-via-variant-option|q9","region":"CA","product":"flavored-via-variant-option","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
382
382
  {"id":"CA|flavored-via-variant-option|q10","region":"CA","product":"flavored-via-variant-option","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
383
383
  {"id":"CA|flavored-via-variant-option|q11","region":"CA","product":"flavored-via-variant-option","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
384
- {"id":"CA|nicotine-format-label|q1","region":"CA","product":"nicotine-format-label","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
385
- {"id":"CA|nicotine-format-label|q9","region":"CA","product":"nicotine-format-label","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
386
- {"id":"CA|nicotine-format-label|q10","region":"CA","product":"nicotine-format-label","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
387
- {"id":"CA|nicotine-format-label|q11","region":"CA","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
384
+ {"id":"CA|nicotine-format-label|q1","region":"CA","product":"nicotine-format-label","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
385
+ {"id":"CA|nicotine-format-label|q9","region":"CA","product":"nicotine-format-label","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
386
+ {"id":"CA|nicotine-format-label|q10","region":"CA","product":"nicotine-format-label","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
387
+ {"id":"CA|nicotine-format-label|q11","region":"CA","product":"nicotine-format-label","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
388
388
  {"id":"CA|zero-mg-flavored-eliquid|q1","region":"CA","product":"zero-mg-flavored-eliquid","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
389
389
  {"id":"CA|zero-mg-flavored-eliquid|q9","region":"CA","product":"zero-mg-flavored-eliquid","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
390
390
  {"id":"CA|zero-mg-flavored-eliquid|q10","region":"CA","product":"zero-mg-flavored-eliquid","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
391
391
  {"id":"CA|zero-mg-flavored-eliquid|q11","region":"CA","product":"zero-mg-flavored-eliquid","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
392
- {"id":"CA|per-product-restricted-tx|q1","region":"CA","product":"per-product-restricted-tx","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
393
- {"id":"CA|per-product-restricted-tx|q9","region":"CA","product":"per-product-restricted-tx","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
394
- {"id":"CA|per-product-restricted-tx|q10","region":"CA","product":"per-product-restricted-tx","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
395
- {"id":"CA|per-product-restricted-tx|q11","region":"CA","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
392
+ {"id":"CA|per-product-restricted-tx|q1","region":"CA","product":"per-product-restricted-tx","quantity":1,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
393
+ {"id":"CA|per-product-restricted-tx|q9","region":"CA","product":"per-product-restricted-tx","quantity":9,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
394
+ {"id":"CA|per-product-restricted-tx|q10","region":"CA","product":"per-product-restricted-tx","quantity":10,"allowed":false,"action":"blocked","reasons":["flavored_prohibited"],"adultSignature":true,"limit":10,"limitExceeded":false},
395
+ {"id":"CA|per-product-restricted-tx|q11","region":"CA","product":"per-product-restricted-tx","quantity":11,"allowed":false,"action":"blocked","reasons":["flavored_prohibited","purchase_limit_exceeded"],"adultSignature":true,"limit":10,"limitExceeded":true},
396
396
  {"id":"CA|accessory-coil|q1","region":"CA","product":"accessory-coil","quantity":1,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
397
397
  {"id":"CA|accessory-coil|q9","region":"CA","product":"accessory-coil","quantity":9,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},
398
398
  {"id":"CA|accessory-coil|q10","region":"CA","product":"accessory-coil","quantity":10,"allowed":true,"action":"clear","reasons":[],"adultSignature":true,"limit":10,"limitExceeded":false},