@mohasinac/appkit 4.53.2 → 4.54.0-next.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.
Files changed (179) hide show
  1. package/dist/.build-stamp.json +5 -5
  2. package/dist/_internal/server/features/auth/capabilities.js +7 -5
  3. package/dist/_internal/server/features/checkout/supplier-identity.js +28 -6
  4. package/dist/_internal/server/features/products/listing-tab-counts.js +81 -25
  5. package/dist/_internal/server/features/search/index-build/sources.js +43 -2
  6. package/dist/_internal/server/jobs/core/jobRunners.js +0 -10
  7. package/dist/_internal/server/jobs/core/onOrderCreate.js +10 -104
  8. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  9. package/dist/_internal/shared/actions/action-registry.js +128 -0
  10. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
  11. package/dist/_internal/shared/features/site-settings/supplier-of-record.d.ts +56 -0
  12. package/dist/_internal/shared/features/site-settings/supplier-of-record.js +57 -0
  13. package/dist/_internal/shared/listing-types/tiers.d.ts +94 -0
  14. package/dist/_internal/shared/listing-types/tiers.js +150 -0
  15. package/dist/cli/index.js +0 -2
  16. package/dist/client.d.ts +21 -2
  17. package/dist/client.js +23 -4
  18. package/dist/constants/api-endpoints.d.ts +21 -0
  19. package/dist/constants/api-endpoints.js +7 -0
  20. package/dist/features/account/components/UserOrdersView.js +26 -7
  21. package/dist/features/addresses/utils/postal-autofill.d.ts +39 -0
  22. package/dist/features/addresses/utils/postal-autofill.js +54 -0
  23. package/dist/features/admin/components/AdminStoreEditorView.js +73 -34
  24. package/dist/features/admin/components/OwnerQuickActions.d.ts +29 -0
  25. package/dist/features/admin/components/OwnerQuickActions.js +142 -0
  26. package/dist/features/admin/schemas/admin-editor-forms.d.ts +1 -1
  27. package/dist/features/admin/schemas/admin-editor-forms.js +4 -1
  28. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -1
  29. package/dist/features/auctions/components/MarketplaceAuctionCard.js +9 -2
  30. package/dist/features/auth/permissions/constants.d.ts +29 -2
  31. package/dist/features/auth/permissions/constants.js +68 -1
  32. package/dist/features/blog/components/BlogFeaturedCard.d.ts +11 -1
  33. package/dist/features/blog/components/BlogFeaturedCard.js +9 -2
  34. package/dist/features/cart/components/CartDrawer.js +3 -2
  35. package/dist/features/cart/components/CartGroupLineRow.js +2 -1
  36. package/dist/features/cart/hooks/useAddToCart.js +10 -5
  37. package/dist/features/cart/utils/pending-ops.d.ts +11 -1
  38. package/dist/features/cart/utils/pending-ops.js +14 -2
  39. package/dist/features/categories/components/BrandDetailPageView.js +2 -1
  40. package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
  41. package/dist/features/categories/components/CategoryGrid.d.ts +11 -1
  42. package/dist/features/categories/components/CategoryGrid.js +9 -2
  43. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +10 -5
  44. package/dist/features/checkout/actions/checkout-value-otp-actions.js +2 -88
  45. package/dist/features/events/components/EventCard.d.ts +11 -1
  46. package/dist/features/events/components/EventCard.js +9 -2
  47. package/dist/features/layout/AppLayoutShell.js +2 -1
  48. package/dist/features/layout/BottomActions.d.ts +1 -0
  49. package/dist/features/layout/BottomActions.js +330 -0
  50. package/dist/features/layout/BottomChrome.d.ts +45 -0
  51. package/dist/features/layout/BottomChrome.js +5 -17
  52. package/dist/features/layout/SideActionDock.js +36 -13
  53. package/dist/features/layout/index.d.ts +2 -7
  54. package/dist/features/layout/index.js +7 -7
  55. package/dist/features/layout/useIsDesktop.d.ts +36 -0
  56. package/dist/features/layout/useIsDesktop.js +48 -0
  57. package/dist/features/media/messages/en.json +67 -67
  58. package/dist/features/orders/components/YourPurchaseStrip.d.ts +24 -0
  59. package/dist/features/orders/components/YourPurchaseStrip.js +77 -0
  60. package/dist/features/orders/components/index.d.ts +2 -1
  61. package/dist/features/orders/components/index.js +1 -1
  62. package/dist/features/orders/utils/order-announcement-message.d.ts +18 -0
  63. package/dist/features/orders/utils/order-announcement-message.js +16 -0
  64. package/dist/features/orders/utils/payment-proof-message.d.ts +20 -0
  65. package/dist/features/orders/utils/payment-proof-message.js +17 -0
  66. package/dist/features/parts/schemas/firestore.d.ts +227 -0
  67. package/dist/features/parts/schemas/firestore.js +6 -6
  68. package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +11 -1
  69. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +10 -3
  70. package/dist/features/products/api/[id]/route.js +8 -1
  71. package/dist/features/products/api/route.js +16 -4
  72. package/dist/features/products/components/AvailabilityScopeHint.js +1 -1
  73. package/dist/features/products/components/InteractiveProductCard.d.ts +7 -1
  74. package/dist/features/products/components/InteractiveProductCard.js +16 -4
  75. package/dist/features/products/components/MarketplaceBundleCard.d.ts +11 -1
  76. package/dist/features/products/components/MarketplaceBundleCard.js +9 -2
  77. package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +11 -1
  78. package/dist/features/products/components/MarketplacePrizeDrawCard.js +9 -2
  79. package/dist/features/products/components/ProductDetailPageView.js +27 -2
  80. package/dist/features/products/components/ProductGrid.d.ts +14 -1
  81. package/dist/features/products/components/ProductGrid.js +22 -5
  82. package/dist/features/products/components/ProductsIndexListing.js +2 -2
  83. package/dist/features/reviews/api/[id]/route.js +83 -23
  84. package/dist/features/reviews/components/PurchaseReviewAffordance.d.ts +6 -0
  85. package/dist/features/reviews/components/PurchaseReviewAffordance.js +42 -0
  86. package/dist/features/reviews/components/ReviewFormDrawer.d.ts +34 -0
  87. package/dist/features/reviews/components/ReviewFormDrawer.js +127 -0
  88. package/dist/features/reviews/components/index.d.ts +4 -0
  89. package/dist/features/reviews/components/index.js +2 -0
  90. package/dist/features/seller/actions/seller-actions.d.ts +1 -1
  91. package/dist/features/seller/actions/seller-actions.js +49 -3
  92. package/dist/features/seller/components/SellerPayoutSettingsView.js +30 -2
  93. package/dist/features/seller/components/SellerProductShell.d.ts +17 -1
  94. package/dist/features/seller/hooks/useStoreListingEntitlements.d.ts +8 -0
  95. package/dist/features/seller/hooks/useStoreListingEntitlements.js +70 -0
  96. package/dist/features/stores/components/InteractiveStoreCard.d.ts +11 -1
  97. package/dist/features/stores/components/InteractiveStoreCard.js +9 -2
  98. package/dist/features/stores/components/index.d.ts +0 -2
  99. package/dist/features/stores/components/index.js +0 -1
  100. package/dist/features/stores/schemas/index.d.ts +1 -1
  101. package/dist/features/stores/schemas/index.js +10 -1
  102. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +403 -0
  103. package/dist/index.d.ts +8 -27
  104. package/dist/index.js +14 -35
  105. package/dist/next/routing/route-map.d.ts +2 -0
  106. package/dist/next/routing/route-map.js +9 -0
  107. package/dist/providers/shipping-shiprocket/index.d.ts +98 -0
  108. package/dist/providers/shipping-shiprocket/index.js +353 -0
  109. package/dist/schemas/registry.d.ts +2 -2
  110. package/dist/security/csp.d.ts +16 -1
  111. package/dist/security/csp.js +5 -3
  112. package/dist/seed/_helpers/category-forest.js +27 -0
  113. package/dist/seed/_helpers/category-parts-glossary-map.js +1816 -86
  114. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  115. package/dist/seed/_helpers/category-parts.js +13412 -3020
  116. package/dist/seed/categories-seed-data.js +48 -6
  117. package/dist/seed/homepage-sections-seed-data.js +41 -28
  118. package/dist/seed/model-composition-seed-data.js +6 -8
  119. package/dist/seed/part-glossary/chunk0.d.ts +9 -0
  120. package/dist/seed/part-glossary/chunk0.js +35044 -0
  121. package/dist/seed/part-glossary/chunk1.d.ts +9 -0
  122. package/dist/seed/part-glossary/chunk1.js +39955 -0
  123. package/dist/seed/part-glossary/chunk2.d.ts +9 -0
  124. package/dist/seed/part-glossary/chunk2.js +50335 -0
  125. package/dist/seed/part-glossary/chunk3.d.ts +9 -0
  126. package/dist/seed/part-glossary/chunk3.js +52386 -0
  127. package/dist/seed/part-glossary/chunk4.d.ts +9 -0
  128. package/dist/seed/part-glossary/chunk4.js +50549 -0
  129. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  130. package/dist/seed/part-glossary-seed-data.js +23 -47483
  131. package/dist/server.d.ts +4 -8
  132. package/dist/server.js +8 -20
  133. package/dist/styles.css +516 -358
  134. package/dist/tailwind-utilities.css +1 -1
  135. package/dist/tokens/themes/index.d.ts +3 -1
  136. package/dist/tokens/themes/index.js +11 -1
  137. package/dist/tokens/themes/technical-dark.d.ts +30 -0
  138. package/dist/tokens/themes/technical-dark.js +103 -0
  139. package/dist/tokens/themes/technical-light.d.ts +22 -0
  140. package/dist/tokens/themes/technical-light.js +89 -0
  141. package/dist/tokens/tokens.css +155 -0
  142. package/dist/ui/components/GatedPrice.js +30 -3
  143. package/dist/ui/components/GatedPrice.style.css +22 -19
  144. package/dist/ui/components/QuickCreateModal.style.css +78 -78
  145. package/dist/ui/forms/FormShell.style.css +260 -260
  146. package/package.json +1 -1
  147. package/dist/_internal/server/jobs/core/whatsappCatalogImport.d.ts +0 -27
  148. package/dist/_internal/server/jobs/core/whatsappCatalogImport.js +0 -172
  149. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +0 -35
  150. package/dist/_internal/server/jobs/core/whatsappNotify.js +0 -140
  151. package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +0 -2
  152. package/dist/features/admin/components/AdminWhatsAppGuideView.js +0 -35
  153. package/dist/features/orders/components/MarketplaceOrderCard.d.ts +0 -36
  154. package/dist/features/orders/components/MarketplaceOrderCard.js +0 -57
  155. package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +0 -3
  156. package/dist/features/stores/components/StoreWhatsAppGuideView.js +0 -12
  157. package/dist/features/whatsapp-bot/api/send-status/route.d.ts +0 -12
  158. package/dist/features/whatsapp-bot/api/send-status/route.js +0 -38
  159. package/dist/features/whatsapp-bot/api/webhook/route.d.ts +0 -12
  160. package/dist/features/whatsapp-bot/api/webhook/route.js +0 -43
  161. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +0 -6
  162. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -270
  163. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +0 -9
  164. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +0 -7
  165. package/dist/features/whatsapp-bot/components/index.d.ts +0 -3
  166. package/dist/features/whatsapp-bot/components/index.js +0 -2
  167. package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +0 -14
  168. package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +0 -17
  169. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +0 -52
  170. package/dist/features/whatsapp-bot/helpers/whatsapp.js +0 -283
  171. package/dist/features/whatsapp-bot/index.d.ts +0 -3
  172. package/dist/features/whatsapp-bot/index.js +0 -3
  173. package/dist/features/whatsapp-bot/manifest.d.ts +0 -2
  174. package/dist/features/whatsapp-bot/manifest.js +0 -13
  175. package/dist/features/whatsapp-bot/messages/en.json +0 -7
  176. package/dist/features/whatsapp-bot/server.d.ts +0 -7
  177. package/dist/features/whatsapp-bot/server.js +0 -7
  178. package/dist/features/whatsapp-bot/types/index.d.ts +0 -126
  179. package/dist/features/whatsapp-bot/types/index.js +0 -1
@@ -0,0 +1,353 @@
1
+ /**
2
+ * @mohasinac/shipping-shiprocket — IShippingProvider over Shiprocket's v1 API.
3
+ *
4
+ * ── Read this before trusting it ──────────────────────────────────────────
5
+ *
6
+ * ── What is verified, and what is not ────────────────────────────────────
7
+ *
8
+ * ✅ VERIFIED against the live API (2026-10-20, real API-user credentials):
9
+ * `POST /v1/external/auth/login` returns HTTP 200 with
10
+ * `{ company_id, created_at, email, first_name, id, last_name, token }`.
11
+ * The `token` field is present and non-empty, which is the only field
12
+ * `loginResponse` requires — the rest are ignored, as Zod ignores unknown
13
+ * keys by default.
14
+ *
15
+ * 🛑 NOT VERIFIED: every other endpoint. Order creation, AWB assignment,
16
+ * tracking, serviceability and label generation all MUTATE or bill against
17
+ * a real carrier account, so none was called. Their field names below are
18
+ * claims taken from the published docs, not facts read off a payload.
19
+ *
20
+ * That asymmetry is the reason every response is parsed through Zod rather
21
+ * than cast: an unverified contract that is wrong surfaces as a named error at
22
+ * the boundary, instead of as an `undefined` AWB written to an order and shown
23
+ * to a buyer as a tracking number that tracks nothing.
24
+ *
25
+ * ── Auth: why a dedicated API user, and why the token is cached ───────────
26
+ *
27
+ * Shiprocket's core v1 API has no long-lived key/secret. You POST credentials
28
+ * to `/v1/external/auth/login` and receive a bearer token valid for roughly
29
+ * ten days, then log in again.
30
+ *
31
+ * 🛑 Those credentials should be a DEDICATED API USER (Shiprocket dashboard →
32
+ * Settings → API → Configure), not the dashboard login. Using the dashboard
33
+ * account means the token dies whenever someone changes that password — a
34
+ * shipping integration that breaks on an unrelated admin action — and it hands
35
+ * full account scope to this process. The env var is named
36
+ * `SHIPROCKET_LOGIN_USER_EMAIL` rather than `LOGIN_ID` because the login payload
37
+ * field is `email`; a var called "login id" invites someone to put a username
38
+ * in it and get a 400 they cannot explain.
39
+ *
40
+ * 🛑 The token is cached in module state with its expiry. Logging in per
41
+ * request would mean two round trips for every shipment and would hit
42
+ * Shiprocket's auth rate limit under any real volume. A 401 mid-flight clears
43
+ * the cache and retries ONCE — exactly once, because a credential that is
44
+ * genuinely wrong must fail fast rather than loop.
45
+ *
46
+ * ── What is deliberately NOT here ────────────────────────────────────────
47
+ *
48
+ * Inbound webhooks. Shiprocket pushes status updates to a URL configured in
49
+ * its dashboard and authenticates them with an `x-api-key` header whose value
50
+ * you set there — a THIRD secret, and an inbound route rather than a provider
51
+ * method. `trackShipment` below polls instead, which is correct for a provider
52
+ * contract and wasteful as a status mechanism; the webhook is the better
53
+ * answer and belongs in its own route with its own verification.
54
+ *
55
+ * @tag domain:shipping
56
+ * @tag layer:providers
57
+ * @tag access:server-only
58
+ */
59
+ import { z } from "zod";
60
+ import { IShippingProvider } from "../../contracts/index.js";
61
+ import { ApiError } from "../../errors/index.js";
62
+ import { normalizeError } from "../../errors/normalize.js";
63
+ import { serverLogger } from "../../monitoring/server-logger.js";
64
+ const API_BASE = "https://apiv2.shiprocket.in/v1/external";
65
+ // ── Response contracts ──────────────────────────────────────────────────────
66
+ //
67
+ // Every one of these is a CLAIM about a payload nobody here has seen. Parsing
68
+ // rather than casting is what turns a wrong claim into a named error.
69
+ const loginResponse = z.object({
70
+ token: z.string().min(1),
71
+ });
72
+ const createOrderResponse = z.object({
73
+ order_id: z.union([z.number(), z.string()]),
74
+ shipment_id: z.union([z.number(), z.string()]),
75
+ /** Absent until an AWB is assigned — which is a separate call. */
76
+ awb_code: z.string().nullish(),
77
+ courier_name: z.string().nullish(),
78
+ status: z.string().nullish(),
79
+ });
80
+ const trackingResponse = z.object({
81
+ tracking_data: z.object({
82
+ shipment_status: z.union([z.number(), z.string()]).nullish(),
83
+ shipment_track: z
84
+ .array(z.object({
85
+ current_status: z.string().nullish(),
86
+ delivered_date: z.string().nullish(),
87
+ edd: z.string().nullish(),
88
+ }))
89
+ .nullish(),
90
+ shipment_track_activities: z
91
+ .array(z.object({
92
+ date: z.string().nullish(),
93
+ status: z.string().nullish(),
94
+ activity: z.string().nullish(),
95
+ location: z.string().nullish(),
96
+ }))
97
+ .nullish(),
98
+ }),
99
+ });
100
+ const serviceabilityResponse = z.object({
101
+ data: z.object({
102
+ available_courier_companies: z
103
+ .array(z.object({
104
+ courier_name: z.string(),
105
+ estimated_delivery_days: z.union([z.number(), z.string()]).nullish(),
106
+ rate: z.union([z.number(), z.string()]).nullish(),
107
+ }))
108
+ .nullish(),
109
+ }),
110
+ });
111
+ function num(v, fallback = 0) {
112
+ const n = typeof v === "string" ? Number.parseFloat(v) : v;
113
+ return typeof n === "number" && Number.isFinite(n) ? n : fallback;
114
+ }
115
+ /**
116
+ * Parse an upstream response, or throw a named contract error.
117
+ *
118
+ * 🛑 `.parse()`, not `.safeParse()`. The only branch on failure is to throw,
119
+ * and a `safeParse` whose `.success` is checked once and converted straight
120
+ * into a throw is indistinguishable — to a reader and to
121
+ * `audit-unvalidated-safeparse` — from a result nobody handled.
122
+ *
123
+ * 🛑 Logs the FAILING PATHS, never the payload. A Shiprocket response carries
124
+ * the buyer's name, phone and full address, and an error log is the wrong
125
+ * place for all three.
126
+ */
127
+ function parseUpstream(contract, json, what) {
128
+ try {
129
+ return contract.parse(json);
130
+ }
131
+ catch (err) {
132
+ const issues = err instanceof z.ZodError
133
+ ? err.issues.map((i) => i.path.join("."))
134
+ : [];
135
+ serverLogger.error("shiprocket: response contract mismatch", {
136
+ what,
137
+ issues,
138
+ });
139
+ throw new ApiError(502, `Shiprocket ${what} returned an unexpected shape.`, {
140
+ code: "SHIPROCKET_CONTRACT_MISMATCH",
141
+ });
142
+ }
143
+ }
144
+ export class ShiprocketShippingProvider extends IShippingProvider {
145
+ constructor(config) {
146
+ super();
147
+ this.config = config;
148
+ this.name = "shiprocket";
149
+ this.token = null;
150
+ this.tokenExpiresAt = 0;
151
+ /*
152
+ * 🛑 Refuses at CONSTRUCTION, not at first use. `providers.config.ts`
153
+ * already argues this: a degraded shipping provider hands back a synthetic
154
+ * tracking id for a parcel no carrier was told about, so the seller sees an
155
+ * AWB-shaped string and a success toast while the buyer gets a number that
156
+ * tracks nothing. A loud failure at boot is attributable and recoverable by
157
+ * flipping one setting; the silent version is neither.
158
+ */
159
+ if (!config.email?.trim() || !config.password?.trim()) {
160
+ throw new Error("[shiprocket] SHIPROCKET_LOGIN_USER_EMAIL / SHIPROCKET_API_PASSWORD are empty. " +
161
+ "Create a dedicated API user in Shiprocket (Settings -> API -> Configure) " +
162
+ "and set both, or set siteSettings.shipping.provider back to 'manual'. " +
163
+ "This refuses rather than degrading because a fallback would return a " +
164
+ "synthetic tracking id for a parcel that was never booked.");
165
+ }
166
+ }
167
+ /**
168
+ * A bearer token, logging in only when the cached one is absent or stale.
169
+ *
170
+ * The 60-second safety margin matters: a token that expires mid-request
171
+ * produces a 401 the caller has to interpret, and shaving a minute off a
172
+ * ten-day lifetime costs nothing.
173
+ */
174
+ async getToken(force = false) {
175
+ if (!force && this.token && Date.now() < this.tokenExpiresAt - 60000) {
176
+ return this.token;
177
+ }
178
+ const res = await fetch(`${API_BASE}/auth/login`, {
179
+ method: "POST",
180
+ headers: { "Content-Type": "application/json" },
181
+ body: JSON.stringify({
182
+ email: this.config.email,
183
+ password: this.config.password,
184
+ }),
185
+ });
186
+ if (!res.ok) {
187
+ throw new ApiError(502, `Shiprocket auth failed (${res.status}). Check the API user's credentials.`, { code: "SHIPROCKET_AUTH_FAILED" });
188
+ }
189
+ const parsed = parseUpstream(loginResponse, await res.json(), "auth/login");
190
+ this.token = parsed.token;
191
+ // Their docs say ~10 days; treated as 9 so a boundary never surprises us.
192
+ this.tokenExpiresAt = Date.now() + 9 * 24 * 60 * 60 * 1000;
193
+ return this.token;
194
+ }
195
+ /**
196
+ * One authenticated call, with a single re-auth on 401.
197
+ *
198
+ * 🛑 Exactly one retry. A token that expired early is worth one more attempt;
199
+ * a credential that is wrong must fail rather than loop, because a retry loop
200
+ * against an auth endpoint is how an integration gets rate-limited and then
201
+ * looks like an outage.
202
+ */
203
+ async call(path, init, contract) {
204
+ const attempt = async (force) => {
205
+ const token = await this.getToken(force);
206
+ return fetch(`${API_BASE}${path}`, {
207
+ ...init,
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ Authorization: `Bearer ${token}`,
211
+ ...(init.headers ?? {}),
212
+ },
213
+ });
214
+ };
215
+ let res = await attempt(false);
216
+ if (res.status === 401) {
217
+ this.token = null;
218
+ res = await attempt(true);
219
+ }
220
+ if (!res.ok) {
221
+ throw new ApiError(502, `Shiprocket ${path} failed (${res.status}).`, { code: "SHIPROCKET_REQUEST_FAILED" });
222
+ }
223
+ const json = await res.json();
224
+ return parseUpstream(contract, json, path);
225
+ }
226
+ /** Their order payload wants the address flattened across many keys. */
227
+ addressFields(prefix, a) {
228
+ const p = prefix === "billing" ? "billing" : "shipping";
229
+ return {
230
+ [`${p}_customer_name`]: a.name,
231
+ [`${p}_address`]: a.addressLine1,
232
+ [`${p}_address_2`]: a.addressLine2 ?? "",
233
+ [`${p}_city`]: a.city,
234
+ [`${p}_pincode`]: a.pincode,
235
+ [`${p}_state`]: a.state,
236
+ [`${p}_country`]: a.country,
237
+ [`${p}_phone`]: a.phone,
238
+ };
239
+ }
240
+ async createShipment(data) {
241
+ const body = {
242
+ order_id: data.orderId,
243
+ order_date: new Date().toISOString().slice(0, 10),
244
+ pickup_location: this.config.pickupLocation ?? "Primary",
245
+ ...this.addressFields("billing", data.delivery),
246
+ shipping_is_billing: true,
247
+ /*
248
+ * Shiprocket requires at least one order item. We do not receive the
249
+ * line items on this contract — `CreateShipmentInput` carries only
250
+ * dimensions and the two addresses — so a single synthetic line stands
251
+ * for the parcel. 🛑 This is a known gap, not a simplification: a real
252
+ * integration needs per-item HSN and value for a commercial invoice, and
253
+ * that means widening the shared contract rather than inventing values
254
+ * here.
255
+ */
256
+ order_items: [
257
+ {
258
+ name: `Order ${data.orderId}`,
259
+ sku: data.orderId,
260
+ units: 1,
261
+ selling_price: data.codAmount ?? 0,
262
+ },
263
+ ],
264
+ payment_method: data.isCod ? "COD" : "Prepaid",
265
+ sub_total: data.codAmount ?? 0,
266
+ length: data.dimensions.length,
267
+ breadth: data.dimensions.width,
268
+ height: data.dimensions.height,
269
+ weight: data.dimensions.weight,
270
+ };
271
+ const out = await this.call("/orders/create/adhoc", { method: "POST", body: JSON.stringify(body) }, createOrderResponse);
272
+ return {
273
+ id: String(out.shipment_id),
274
+ /*
275
+ * 🛑 Falls back to the shipment id when no AWB has been assigned yet —
276
+ * assignment is a separate call. It does NOT invent a `manual_…` string:
277
+ * a synthetic tracking number that a buyer can paste into a courier site
278
+ * and get nothing from is the failure mode this whole provider is
279
+ * supposed to avoid.
280
+ */
281
+ trackingId: out.awb_code?.trim() || String(out.shipment_id),
282
+ orderId: data.orderId,
283
+ status: out.status ?? "created",
284
+ courier: out.courier_name ?? undefined,
285
+ createdAt: new Date().toISOString(),
286
+ };
287
+ }
288
+ async trackShipment(trackingId) {
289
+ const out = await this.call(`/courier/track/awb/${encodeURIComponent(trackingId)}`, { method: "GET" }, trackingResponse);
290
+ const t = out.tracking_data;
291
+ const head = t.shipment_track?.[0];
292
+ return {
293
+ trackingId,
294
+ currentStatus: head?.current_status ?? String(t.shipment_status ?? "unknown"),
295
+ estimatedDelivery: head?.edd ?? undefined,
296
+ events: (t.shipment_track_activities ?? []).map((e) => ({
297
+ status: e.status ?? "",
298
+ location: e.location ?? "",
299
+ timestamp: e.date ?? "",
300
+ description: e.activity ?? undefined,
301
+ })),
302
+ };
303
+ }
304
+ async cancelShipment(shipmentId) {
305
+ await this.call("/orders/cancel", { method: "POST", body: JSON.stringify({ ids: [shipmentId] }) },
306
+ // Their cancel returns a message envelope we do not read; accepting any
307
+ // object keeps us from asserting a shape we have not seen.
308
+ z.object({}).passthrough());
309
+ }
310
+ async checkServiceability(pincode, weight) {
311
+ /*
312
+ * 🛑 `pickup_postcode` is required by their API and we do not have it on
313
+ * this contract. Taken from the registered pickup location's name is NOT
314
+ * possible — it is a nickname, not a pincode — so this returns
315
+ * "unserviceable" rather than guessing a origin pincode, which would
316
+ * produce confidently wrong rates.
317
+ */
318
+ const origin = process.env.SHIPROCKET_PICKUP_PINCODE?.trim();
319
+ if (!origin) {
320
+ serverLogger.warn("shiprocket: SHIPROCKET_PICKUP_PINCODE unset — serviceability cannot be checked", { pincode });
321
+ return { isServiceable: false, couriers: [] };
322
+ }
323
+ try {
324
+ const out = await this.call(`/courier/serviceability/?pickup_postcode=${encodeURIComponent(origin)}` +
325
+ `&delivery_postcode=${encodeURIComponent(pincode)}` +
326
+ `&weight=${encodeURIComponent(String(weight))}&cod=0`, { method: "GET" }, serviceabilityResponse);
327
+ const couriers = (out.data.available_courier_companies ?? []).map((c) => ({
328
+ name: c.courier_name,
329
+ estimatedDays: num(c.estimated_delivery_days),
330
+ rate: num(c.rate),
331
+ }));
332
+ return { isServiceable: couriers.length > 0, couriers };
333
+ }
334
+ catch (err) {
335
+ void normalizeError(err);
336
+ /*
337
+ * Serviceability is a QUOTE, not a write. Failing closed here means the
338
+ * checkout says "we cannot ship there" on a transient carrier outage,
339
+ * which is wrong in the safe direction — the alternative is quoting a
340
+ * rate we cannot honour.
341
+ */
342
+ return { isServiceable: false, couriers: [] };
343
+ }
344
+ }
345
+ async generateLabel(shipmentId) {
346
+ const out = await this.call("/courier/generate/label", { method: "POST", body: JSON.stringify({ shipment_id: [shipmentId] }) }, z.object({ label_url: z.string().url() }));
347
+ const res = await fetch(out.label_url);
348
+ if (!res.ok) {
349
+ throw new ApiError(502, `Shiprocket label download failed (${res.status}).`, { code: "SHIPROCKET_LABEL_FAILED" });
350
+ }
351
+ return res.arrayBuffer();
352
+ }
353
+ }
@@ -4097,7 +4097,7 @@ export declare const SCHEMAS: {
4097
4097
  totalReviews: number;
4098
4098
  averageRating?: number | undefined;
4099
4099
  }>>;
4100
- capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
4100
+ capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
4101
4101
  customCommissionRate: import("zod").ZodOptional<import("zod").ZodNumber>;
4102
4102
  googleReviews: import("zod").ZodOptional<import("zod").ZodObject<{
4103
4103
  placeId: import("zod").ZodString;
@@ -5602,7 +5602,7 @@ export declare const SCHEMAS: {
5602
5602
  isVerified: import("zod").ZodOptional<import("zod").ZodBoolean>;
5603
5603
  isFeatured: import("zod").ZodOptional<import("zod").ZodBoolean>;
5604
5604
  suspensionReason: import("zod").ZodOptional<import("zod").ZodString>;
5605
- capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
5605
+ capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
5606
5606
  adminNotes: import("zod").ZodOptional<import("zod").ZodString>;
5607
5607
  }, "strip", import("zod").ZodTypeAny, {
5608
5608
  storeStatus: import("../features/stores").StoreStatus;
@@ -12,5 +12,20 @@
12
12
  */
13
13
  /** Generate a 16-byte base64 nonce for a single request. */
14
14
  export declare function generateNonce(): string;
15
+ export interface BuildCSPOptions {
16
+ /**
17
+ * Add `'wasm-unsafe-eval'` to `script-src`, permitting
18
+ * `WebAssembly.compile`/`instantiate` from bytes.
19
+ *
20
+ * Required by any page that compiles WebAssembly at runtime — without it the
21
+ * production policy (`script-src 'self' 'nonce-…'`) blocks compilation
22
+ * outright, because WebAssembly compilation counts as script evaluation.
23
+ *
24
+ * Narrower than `'unsafe-eval'` on purpose: it unblocks WASM only and still
25
+ * forbids `eval` and `new Function` for JavaScript. Default `false`, so
26
+ * existing consumers are unaffected.
27
+ */
28
+ wasm?: boolean;
29
+ }
15
30
  /** Build the full CSP header value for the given nonce. */
16
- export declare function buildCSP(nonce: string): string;
31
+ export declare function buildCSP(nonce: string, options?: BuildCSPOptions): string;
@@ -17,13 +17,15 @@ export function generateNonce() {
17
17
  return btoa(String.fromCharCode(...bytes));
18
18
  }
19
19
  /** Build the full CSP header value for the given nonce. */
20
- export function buildCSP(nonce) {
20
+ export function buildCSP(nonce, options = {}) {
21
21
  const isDev = process.env.NODE_ENV === "development";
22
+ const wasm = options.wasm === true ? " 'wasm-unsafe-eval'" : "";
22
23
  return [
23
24
  "default-src 'self'",
24
25
  isDev
25
- ? "script-src 'self' 'unsafe-eval' 'unsafe-inline'"
26
- : `script-src 'self' 'nonce-${nonce}'`,
26
+ ? // 'unsafe-eval' already subsumes 'wasm-unsafe-eval' in dev.
27
+ "script-src 'self' 'unsafe-eval' 'unsafe-inline'"
28
+ : `script-src 'self' 'nonce-${nonce}'${wasm}`,
27
29
  "style-src 'self' 'unsafe-inline'",
28
30
  "img-src 'self' data: https: blob:",
29
31
  "font-src 'self' data:",
@@ -174,6 +174,13 @@ const BEYBLADE_ORIGINAL = {
174
174
  node("category-original-attack-rings", "Attack Rings", "The upper contact ring — what actually strikes an opponent, and the part that decides a combo's attack profile.", partDefaults("category-original-attack-rings", "Attack Ring"), PART_TEMPLATES),
175
175
  node("category-original-weight-disks", "Weight Disks", "The metal disk between ring and base. Heavier disks buy stamina; lighter ones buy speed.", partDefaults("category-original-weight-disks", "Weight Disk"), PART_TEMPLATES),
176
176
  node("category-original-blade-bases", "Blade Bases", "The bottom assembly — tip shape, spin gear housing and, on later systems, the magnet or engine.", partDefaults("category-original-blade-bases", "Blade Base"), PART_TEMPLATES),
177
+ /*
178
+ * 🛑 Support Parts had been keyed on THIS BRANCH, which takes explicit
179
+ * children and so never reads `PART_LEAVES` — its 3 leaves were dropped
180
+ * silently. Same cause as the 82 CX leaves under `category-x-custom-cx`;
181
+ * see the note there.
182
+ */
183
+ node("category-original-support-parts", "Support Parts", "Bolt-on extras that fill no stack position of their own — Cross Survivor, Reverse Attack and the other SP pieces.", partDefaults("category-original-support-parts", "Support Parts"), PART_TEMPLATES),
177
184
  /*
178
185
  * D13 — five part-type nodes the forest was missing, so every one of the
179
186
  * 20 part classifications has a home. Measured before adding: 135 of 753
@@ -345,6 +352,26 @@ const BEYBLADE_X = {
345
352
  node("category-x-blades", "Blades", "The upper piece that defines an X combo's attack shape and its ability to hold the rail.", partDefaults("category-x-blades", "Blade"), PART_TEMPLATES),
346
353
  node("category-x-ratchets", "Ratchets", "The middle component. Written as protrusions-height — 3-60, 4-60, 9-60 — and the fastest lever on a build.", partDefaults("category-x-ratchets", "Ratchet"), PART_TEMPLATES),
347
354
  node("category-x-bits", "Bits", "The floor-contact tip, the X-format equivalent of a Driver.", partDefaults("category-x-bits", "Bit"), PART_TEMPLATES),
355
+ /*
356
+ * 🛑 CX PARTS NEED THEIR OWN NODES, and their absence cost 82 leaves.
357
+ *
358
+ * `PART_SLOT_CATEGORY` pointed all five Custom Line slots at
359
+ * `category-x-custom-cx` — which is a `line()`, so it attaches
360
+ * `MODEL_LEAVES[id]` and never reads `PART_LEAVES[id]` at all. The
361
+ * generated map held 82 leaves under that key and every one of them was
362
+ * silently dropped: not mis-tiered, absent. Together with the 3 under
363
+ * `branch("category-original-parts")` that is exactly the 85-leaf
364
+ * shortfall the tier-4 guard had been reporting as a bare number.
365
+ *
366
+ * One node per classification, matching every other generation here —
367
+ * an 82-leaf grab-bag under a single "CX Parts" node would also have
368
+ * satisfied the count while being the wrong shelf for a buyer.
369
+ */
370
+ node("category-x-lock-chips", "Lock Chips", "The CX centrepiece. It locks a Main Blade and an Assist Blade together and carries the beast art.", partDefaults("category-x-lock-chips", "Lock Chip"), PART_TEMPLATES),
371
+ node("category-x-main-blades", "Main Blades", "The primary contact blade of a CX build — the piece that sets its attack shape.", partDefaults("category-x-main-blades", "Main Blade"), PART_TEMPLATES),
372
+ node("category-x-assist-blades", "Assist Blades", "The lower CX blade, tuning weight distribution and how the build sits on the rail.", partDefaults("category-x-assist-blades", "Assist Blade"), PART_TEMPLATES),
373
+ node("category-x-over-blades", "Over Blades", "A CX blade that mounts above the Lock Chip, adding an upper contact surface.", partDefaults("category-x-over-blades", "Over Blade"), PART_TEMPLATES),
374
+ node("category-x-metal-blades", "Metal Blades", "A metal CX blade — the heaviest option in the format and the usual choice for an outspin build.", partDefaults("category-x-metal-blades", "Metal Blade"), PART_TEMPLATES),
348
375
  ]),
349
376
  branch("category-x-launchers", "Beyblade X Launchers", "String launchers, winders and grips for the X format.", [
350
377
  node("category-x-string-launchers", "String Launchers", "The standard X launcher, with a shot angle designed around reaching the Xtreme Line rail.", launcherDefaults("category-x-string-launchers", "Beyblade X"), PART_TEMPLATES),