@classytic/commerce-client 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 — 2026-10-20
4
+
5
+ Add tenant-scoped media removal through the guarded media resource, and type media folder, size and display metadata for Fluid library bindings. Report unsupported source formats and browser decoding failures before starting storage writes.
6
+
7
+ Add `@classytic/commerce-client/platform`: `createPlatformClient({ apiUrl, token })` for platform administration with a bearer token and no merchant organization. `organizations.list` pages organizations and `organizations.decide(id, { action, expectedStatus, note })` applies an approve, reject, suspend or reactivate decision (matches `@spinekit/auth` 0.17.0 `./organizations`).
8
+
9
+ Dependencies are already at the latest published (`arc-next` 0.18.0, `media-transform` 0.3.0, `primitives` 0.34.0).
10
+
11
+ ## 0.1.0 — 2026-10-20
12
+
13
+ First publish.
14
+
15
+ Expose storefront tag/collection/sale/price/sort queries and public taxonomy/compare-at prices; type merchant taxonomy and compare-at writes.
16
+
17
+ Add typed category trees, hierarchy choices, parent/description/image fields and category image uploads. Category updates exclude immutable slugs.
18
+
19
+ API-key lifetime options expose optional non-expiry support; creation accepts explicit `expiresInDays: null`.
20
+
21
+ Delivery quote inputs include destination country; the storefront sends it when requesting prices.
22
+
23
+ Add a bounded store sales overview and tenant-scoped delivery settings. Public storefront clients can load delivery districts/areas and request server-priced delivery quotes without merchant credentials.
24
+
25
+ Typed store profiles, catalog reads, guest checkout with explicit retry keys, and private order tracking.
26
+
27
+ The isolated `/management` entry supports merchant catalog/media, order commands
28
+ and cash collection, store settings, Telegram connections and delivery history,
29
+ plus API-key options, creation, rotation and revocation.
30
+
31
+ Platform administration: `./platform` exposes bearer-only organization lists and guarded status decisions without requiring a merchant organization.
package/LICENSE ADDED
@@ -0,0 +1,75 @@
1
+ Classytic Source-Available License (Community & Commercial), Version 1.0
2
+
3
+ Copyright (c) 2026 Classytic LLC. All rights reserved.
4
+
5
+ This software is source-available, not open source. Please read this License
6
+ before using it.
7
+
8
+ 1. DEFINITIONS
9
+ "Software" means the contents of this package and any accompanying source
10
+ code, object code, or documentation, in any form.
11
+ "Licensor" means Classytic LLC, a limited liability company organized in the
12
+ United States.
13
+ "You" means the individual or legal entity exercising rights under this
14
+ License.
15
+ "Evaluation Use" means use for evaluation, development, testing,
16
+ experimentation, or internal non-commercial purposes only.
17
+ "Production Use" means any use of the Software that is live, commercial,
18
+ revenue-generating, or customer-facing, or any use other than Evaluation Use.
19
+ "Commercial License" means a separate written agreement executed with
20
+ Licensor that grants Production Use rights.
21
+
22
+ 2. OWNERSHIP
23
+ The Software is licensed, not sold. Licensor retains all right, title, and
24
+ interest in and to the Software, including all intellectual property rights.
25
+ All rights not expressly granted in this License are reserved by Licensor.
26
+
27
+ 3. EVALUATION GRANT
28
+ Subject to Your compliance with this License, Licensor grants You a
29
+ worldwide, royalty-free, non-exclusive, non-transferable, revocable license
30
+ to download, install, run, and modify the Software solely for Evaluation Use.
31
+
32
+ 4. PRODUCTION USE REQUIRES A COMMERCIAL LICENSE
33
+ Production Use of the Software is NOT permitted under this License and
34
+ requires a Commercial License from Licensor. A Commercial License may be
35
+ granted at Licensor's discretion, with or without a fee, and may confer
36
+ perpetual, irrevocable, and transferable Production Use rights as set out in
37
+ that agreement. Contact licensing@classytic.com to obtain one.
38
+
39
+ 5. RESTRICTIONS
40
+ You may not:
41
+ (a) make any Production Use of the Software without a Commercial License;
42
+ (b) redistribute, resell, rent, sublicense, or otherwise make the Software
43
+ (in whole or in part, original or modified) available to third parties as
44
+ a stand-alone product, framework, library, or hosted/managed service that
45
+ competes with the Software or with Licensor;
46
+ (c) remove, obscure, or alter any copyright, license, or attribution notice.
47
+
48
+ 6. FEEDBACK AND CONTRIBUTIONS
49
+ Any feedback, suggestions, or contributions You provide relating to the
50
+ Software may be used by Licensor for any purpose without obligation to You.
51
+
52
+ 7. TERMINATION
53
+ This License and the Evaluation Grant terminate automatically if You breach
54
+ any term. Upon termination You must cease all use of the Software and destroy
55
+ all copies in Your possession. Rights under a Commercial License are governed
56
+ by that agreement's own terms and survive termination of this License to the
57
+ extent stated there.
58
+
59
+ 8. DISCLAIMER OF WARRANTY
60
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
61
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
62
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
63
+
64
+ 9. LIMITATION OF LIABILITY
65
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL LICENSOR BE LIABLE
66
+ FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
67
+ TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
68
+ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
69
+
70
+ 10. GOVERNING LAW
71
+ This License is governed by the laws of the State of Delaware, United States,
72
+ without regard to its conflict-of-laws principles. (Adjust to Your LLC's state
73
+ of formation.)
74
+
75
+ For commercial licensing, contact: licensing@classytic.com
package/README.md CHANGED
@@ -1,3 +1,130 @@
1
- # Temporary Holding Version
1
+ # @classytic/commerce-client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@classytic/commerce-client` calls the shared Spine storefront resources.
4
+ It exposes profile, catalog, guest checkout and order tracking. The backend must
5
+ compose those resources, as Classytic Commerce does; the ERP's older checkout endpoints have
6
+ a different contract.
7
+
8
+ Install with `npm install @classytic/commerce-client`.
9
+
10
+ Use Arc Next 0.17 or later. This entry uses its server-safe client/API subpaths;
11
+ it imports no React hooks, Next.js APIs, merchant credentials or server kernels.
12
+
13
+ ```ts
14
+ import { createStorefrontClient } from '@classytic/commerce-client';
15
+ import type { GuestCheckoutInput } from '@classytic/commerce-client/types';
16
+
17
+ const shop = createStorefrontClient({
18
+ apiUrl: 'http://localhost:4140/api/v1', // Full API mount, including its prefix.
19
+ store: 'store-alpha',
20
+ });
21
+ const profile = await shop.profile();
22
+ const page = await shop.products({ limit: 20, category: 'mugs' });
23
+ const next = page.next ? await shop.products({ after: page.next, limit: 20, category: 'mugs' }) : null;
24
+ const categories = await shop.categories();
25
+ // category=clothing includes products assigned to its descendants.
26
+ const product = await shop.product('blue-mug');
27
+ ```
28
+
29
+ Each instance captures its API mount and store. Dashboard `configureClient` and
30
+ `configureAuth` settings cannot change its routing or add credentials. Cookies
31
+ are omitted. Construct separate instances for separate stores or SSR requests.
32
+
33
+ ## Checkout from a frontend cart
34
+
35
+ ```ts
36
+ const input: GuestCheckoutInput = {
37
+ lines: [{ productId: product.id, quantity: 2 }],
38
+ customer: { name: 'Buyer', phone: '01712345678' },
39
+ shippingAddress: { line1: 'Road 4', city: 'Dhaka', country: 'BD' },
40
+ };
41
+ // Create once for this attempt, outside any retry handler.
42
+ const idempotencyKey = crypto.randomUUID();
43
+ const placed = await shop.checkout(input, { idempotencyKey });
44
+ const status = await shop.orderStatus(placed);
45
+ ```
46
+
47
+ Keep the input and key unchanged when retrying a lost response. Use a new key
48
+ when the buyer intentionally starts another order. The SDK does not generate a
49
+ fallback key. The server resolves prices, publication, variants, delivery charges
50
+ and totals. Multiple active variants require `variantSku` on each relevant line.
51
+ Do not send prices or lifecycle/payment state.
52
+
53
+ Money stays in integer minor units. Display it using the primitives money helpers;
54
+ do not assume every currency has two decimal places. This client does no pricing
55
+ arithmetic and stores no cart.
56
+
57
+ The checkout result includes an access token bound to that store and order. Keep
58
+ it privately for the buyer; the SDK puts it in `x-order-access`, never the URL.
59
+ It does not persist tokens or place them in logs. Status reads use `no-store` and
60
+ omit buyer contact/address. Shipment and delivery are fulfillment facts; delivery
61
+ alone does not establish COD collection.
62
+
63
+ ## Requests and errors
64
+
65
+ Read methods accept an optional `{ signal }`; checkout takes it alongside its
66
+ required `idempotencyKey`. The client timeout defaults to 15 seconds and can be
67
+ configured with `timeoutMs`. A custom `fetch` transport is available for embedding
68
+ and tests; ordinary browser/Node consumers use the runtime's fetch.
69
+
70
+ ```ts
71
+ import { isArcApiError } from '@classytic/arc-next/client';
72
+
73
+ try {
74
+ await shop.checkout(input, { idempotencyKey });
75
+ } catch (error) {
76
+ if (isArcApiError(error)) {
77
+ // Inspect status/code/fieldErrors; preserve the attempt key for a retry.
78
+ console.error(error.code, error.fieldErrors);
79
+ }
80
+ }
81
+ ```
82
+
83
+ Arc Next owns HTTP parsing, timeouts and the canonical error contract. Category
84
+ reads return the data array; product pages retain their cursor metadata.
85
+ The server remains the authority for validation;
86
+ unknown filters and extra checkout fields fail there.
87
+
88
+ Products use bounded cursor pages. Categories currently return at most 100 active
89
+ entries. Merchant catalog/order CRUD, integrations, key management and React hooks
90
+ are outside this public client.
91
+
92
+ `products({ q, category, limit, after })` searches product names literally,
93
+ case-insensitively, inside the selected store’s published catalog. Search terms
94
+ are limited to 100 characters. Pagination preserves the same search/category
95
+ filters; `next: null` marks the final page.
96
+
97
+ ## Merchant dashboard and backend clients
98
+
99
+ Use the separate [`/management` entry](management.md) for authenticated products,
100
+ categories, media, order commands, store settings, Telegram connections,
101
+ notification delivery history and API keys. The root entry remains isolated for
102
+ public storefronts.
103
+
104
+ Delivery quotes accept `country` alongside `lines` and `areaId`. Send the destination
105
+ country when known so the preview applies the same country restriction as checkout.
106
+
107
+ `/category-tree` exports framework-independent `categoryNavigation` and
108
+ `categoryChoices` helpers. Public categories include `parent`, `parentPath`,
109
+ `description`, `image` and `displayOrder`; navigation omits children whose parents
110
+ are absent from the public category list.
111
+
112
+ ## Storefront product filters
113
+
114
+ ```ts
115
+ const featured = await shop.products({ collection: 'featured', limit: 8 });
116
+ const summerSale = await shop.products({ tag: 'summer', onSale: true, limit: 8 });
117
+ const affordable = await shop.products({ category: 'clothing', minPrice: 0, maxPrice: 100000, sort: 'price-asc' });
118
+ ```
119
+
120
+ Filters combine with AND. A tag or collection matches its exact stored name.
121
+ `category` includes descendants; `q` searches product names literally. Price bounds
122
+ are inclusive integer minor units on the product's base price; variant adjustments
123
+ are not part of that range. Sort values are `newest` (default), `price-asc` and
124
+ `price-desc`. Price sorting includes products with a numeric base price.
125
+
126
+ `next` is an opaque Mongokit cursor: pass it back as `after` with the same filters
127
+ and sort. Restart pagination when filters change. Public calls always enforce the
128
+ resolved store and published/priced visibility, and reject unknown query fields.
129
+ Merchant `products.getAll({ params })` continues to use Arc's normal allowed query
130
+ syntax, including tags, collections and base-price bracket filters.
@@ -0,0 +1,20 @@
1
+ //#region src/category-tree.d.ts
2
+ export interface CategoryBranch {
3
+ name: string;
4
+ slug: string;
5
+ children?: readonly CategoryBranch[];
6
+ }
7
+ export interface CategoryChoice {
8
+ value: string;
9
+ label: string;
10
+ depth: number;
11
+ }
12
+ /** Flatten a category tree into breadcrumb choices, excluding a complete subtree when editing its parent. */
13
+ export declare function categoryChoices(nodes: readonly CategoryBranch[], excludeSlug?: string): CategoryChoice[];
14
+ /** Group the public flat projection into a navigation tree; children of missing parents stay unlisted. */
15
+ export declare function categoryNavigation(rows: readonly {
16
+ name: string;
17
+ slug: string;
18
+ parent?: string | null;
19
+ }[]): CategoryBranch[];
20
+ //#endregion
@@ -0,0 +1,39 @@
1
+ //#region src/category-tree.ts
2
+ /** Flatten a category tree into breadcrumb choices, excluding a complete subtree when editing its parent. */
3
+ function categoryChoices(nodes, excludeSlug) {
4
+ function visit(items, path, seen) {
5
+ return items.flatMap((item) => {
6
+ if (item.slug === excludeSlug) return [];
7
+ if (seen.has(item.slug) || path.length > 20) throw new Error("Invalid category hierarchy.");
8
+ const branch = [...path, item.name];
9
+ return [{
10
+ value: item.slug,
11
+ label: branch.join(" / "),
12
+ depth: path.length
13
+ }, ...visit(item.children ?? [], branch, /* @__PURE__ */ new Set([...seen, item.slug]))];
14
+ });
15
+ }
16
+ return visit(nodes, [], /* @__PURE__ */ new Set());
17
+ }
18
+ /** Group the public flat projection into a navigation tree; children of missing parents stay unlisted. */
19
+ function categoryNavigation(rows) {
20
+ const nodes = /* @__PURE__ */ new Map();
21
+ for (const row of rows) {
22
+ if (nodes.has(row.slug)) throw new Error("Duplicate category slug.");
23
+ nodes.set(row.slug, {
24
+ name: row.name,
25
+ slug: row.slug,
26
+ children: []
27
+ });
28
+ }
29
+ const roots = [];
30
+ for (const row of rows) {
31
+ const node = nodes.get(row.slug);
32
+ if (!node) throw new Error("Category navigation is incomplete.");
33
+ if (row.parent) nodes.get(row.parent)?.children.push(node);
34
+ else roots.push(node);
35
+ }
36
+ return roots;
37
+ }
38
+ //#endregion
39
+ export { categoryChoices, categoryNavigation };
@@ -0,0 +1,48 @@
1
+ //#region src/delivery-types.d.ts
2
+ export interface DeliveryRules {
3
+ mode: 'flat' | 'zones';
4
+ zones: Array<{
5
+ name: string;
6
+ chargeMinor: number;
7
+ districts: string[];
8
+ }>;
9
+ freeOverMinor: number | null;
10
+ }
11
+ export interface DeliverySettings {
12
+ deliveryChargeMinor: number;
13
+ deliveryRules: DeliveryRules;
14
+ version: number | null;
15
+ }
16
+ export interface DeliverySettingsUpdate {
17
+ deliveryChargeMinor: number;
18
+ deliveryRules: DeliveryRules;
19
+ expectedVersion: number | null;
20
+ }
21
+ export interface DeliveryDistrict {
22
+ id: string;
23
+ name: string;
24
+ divisionId: string;
25
+ divisionName: string;
26
+ }
27
+ export interface DeliveryArea {
28
+ id: number;
29
+ name: string;
30
+ districtName: string;
31
+ }
32
+ export interface DeliveryQuoteInput {
33
+ lines: Array<{
34
+ productId: string;
35
+ variantSku?: string;
36
+ quantity: number;
37
+ }>;
38
+ areaId?: number;
39
+ country?: string;
40
+ }
41
+ export interface DeliveryQuote {
42
+ subtotalMinor: number;
43
+ deliveryChargeMinor: number;
44
+ currency: string;
45
+ zone?: string;
46
+ freeDelivery?: boolean;
47
+ }
48
+ //#endregion
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import { DeliveryArea, DeliveryDistrict, DeliveryQuote, DeliveryQuoteInput } from "./delivery-types.js";
2
+ import { a as StorefrontCategory, c as StorefrontProductPage, d as StorefrontRequestOptions, i as GuestOrderAccess, l as StorefrontProductQuery, n as GuestCheckoutOptions, o as StorefrontOrder, r as GuestCheckoutResult, s as StorefrontProduct, t as GuestCheckoutInput, u as StorefrontProfile } from "./types-C0cSi6Pk.js";
3
+ import { ArcClientConfig } from "@classytic/arc-next/client";
4
+ //#region src/index.d.ts
5
+ export interface StorefrontClientOptions {
6
+ /** Full API mount, for example https://api.example.com/api/v1. */
7
+ apiUrl: string;
8
+ store: string;
9
+ timeoutMs?: number;
10
+ fetch?: ArcClientConfig['fetch'];
11
+ }
12
+ /** Build an isolated guest client; never inherit dashboard credentials or global routing. */
13
+ export declare function createStorefrontClient(options: StorefrontClientOptions): {
14
+ profile: (request?: StorefrontRequestOptions) => Promise<StorefrontProfile>;
15
+ products: (query?: StorefrontProductQuery, request?: StorefrontRequestOptions) => Promise<StorefrontProductPage>;
16
+ product: (slug: string, request?: StorefrontRequestOptions) => Promise<StorefrontProduct>;
17
+ categories: (request?: StorefrontRequestOptions) => Promise<StorefrontCategory[]>;
18
+ deliveryDistricts: (request?: StorefrontRequestOptions) => Promise<{
19
+ requiresArea: boolean;
20
+ data: DeliveryDistrict[];
21
+ }>;
22
+ deliveryAreas: (district: string, request?: StorefrontRequestOptions) => Promise<{
23
+ data: DeliveryArea[];
24
+ }>;
25
+ quoteDelivery: (input: DeliveryQuoteInput, request?: StorefrontRequestOptions) => Promise<DeliveryQuote>;
26
+ checkout: (input: GuestCheckoutInput, request: GuestCheckoutOptions) => Promise<GuestCheckoutResult>;
27
+ orderStatus: (access: GuestOrderAccess, request?: StorefrontRequestOptions) => Promise<StorefrontOrder>;
28
+ };
29
+ export type StorefrontClient = ReturnType<typeof createStorefrontClient>;
30
+ //#endregion
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ import { BaseApi } from "@classytic/arc-next/api";
2
+ import { createClient } from "@classytic/arc-next/client";
3
+ //#region src/index.ts
4
+ function segment(value) {
5
+ if (!value || value === "." || value === "..") throw new Error("A nonempty resource identifier is required.");
6
+ return encodeURIComponent(value);
7
+ }
8
+ /** Build an isolated guest client; never inherit dashboard credentials or global routing. */
9
+ function createStorefrontClient(options) {
10
+ const store = options.store;
11
+ const url = new URL(options.apiUrl);
12
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("apiUrl must be an HTTP(S) API mount without credentials, query or fragment.");
13
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(store)) throw new Error("A valid store slug is required.");
14
+ const config = {
15
+ client: createClient({
16
+ baseUrl: url.href.replace(/\/+$/, ""),
17
+ getToken: () => null,
18
+ getOrgId: () => null,
19
+ credentials: "omit",
20
+ autoIdempotency: false,
21
+ timeoutMs: options.timeoutMs ?? 15e3,
22
+ ...options.fetch ? { fetch: options.fetch } : {}
23
+ }),
24
+ basePath: "",
25
+ cache: "no-store"
26
+ };
27
+ const catalog = new BaseApi(`storefront/${segment(store)}`, config);
28
+ const profiles = new BaseApi("stores", config);
29
+ return {
30
+ profile: (request = {}) => profiles.request("GET", `${profiles.baseUrl}/${segment(store)}`, { options: request }),
31
+ products: (query = {}, request = {}) => catalog.request("GET", `${catalog.baseUrl}/products`, {
32
+ params: { ...query },
33
+ options: request
34
+ }),
35
+ product: (slug, request = {}) => catalog.request("GET", `${catalog.baseUrl}/products/${segment(slug)}`, { options: request }),
36
+ categories: async (request = {}) => {
37
+ return (await catalog.request("GET", `${catalog.baseUrl}/categories`, { options: request })).data;
38
+ },
39
+ deliveryDistricts: (request = {}) => catalog.request("GET", `${catalog.baseUrl}/delivery/districts`, { options: request }),
40
+ deliveryAreas: (district, request = {}) => catalog.request("GET", `${catalog.baseUrl}/delivery/areas`, {
41
+ params: { district },
42
+ options: request
43
+ }),
44
+ quoteDelivery: (input, request = {}) => catalog.request("POST", `${catalog.baseUrl}/orders/quote`, {
45
+ data: input,
46
+ options: request
47
+ }),
48
+ checkout: (input, request) => {
49
+ if (!request?.idempotencyKey?.trim()) throw new Error("idempotencyKey is required; preserve it for retries.");
50
+ return catalog.request("POST", `${catalog.baseUrl}/orders`, {
51
+ data: input,
52
+ options: {
53
+ signal: request.signal,
54
+ headerOptions: { "Idempotency-Key": request.idempotencyKey }
55
+ }
56
+ });
57
+ },
58
+ orderStatus: (access, request = {}) => catalog.request("GET", `${catalog.baseUrl}/orders/${segment(access.orderNumber)}/status`, { options: {
59
+ signal: request.signal,
60
+ cache: "no-store",
61
+ headerOptions: { "x-order-access": access.accessToken }
62
+ } })
63
+ };
64
+ }
65
+ //#endregion
66
+ export { createStorefrontClient };
@@ -0,0 +1,230 @@
1
+ import { o as StorefrontOrder, s as StorefrontProduct } from "./types-C0cSi6Pk.js";
2
+ import { Address } from "@classytic/primitives/address";
3
+ import { Money } from "@classytic/primitives/money";
4
+ //#region src/management-types.d.ts
5
+ export interface ProductInput {
6
+ name: string;
7
+ slug: string;
8
+ productType: 'physical';
9
+ status: 'draft' | 'active' | 'archived';
10
+ description?: string;
11
+ categorySlug?: string;
12
+ tags?: string[];
13
+ collections?: string[];
14
+ images?: StorefrontProduct['images'];
15
+ defaultMonetization: {
16
+ type: 'one_time';
17
+ pricing: {
18
+ basePrice: Money;
19
+ currency: string;
20
+ compareAtPrice?: Money;
21
+ };
22
+ };
23
+ variationAttributes?: Array<{
24
+ code: string;
25
+ name: string;
26
+ values: Array<{
27
+ code: string;
28
+ label: string;
29
+ }>;
30
+ }>;
31
+ variants?: Array<{
32
+ sku: string;
33
+ attributes: Record<string, string>;
34
+ priceModifier: number;
35
+ isActive: boolean;
36
+ }>;
37
+ }
38
+ export interface MerchantProduct extends ProductInput {
39
+ _id: string;
40
+ updatedAt: string;
41
+ }
42
+ export interface CategoryInput {
43
+ name: string;
44
+ /** Omit on create to generate from the name. Slugs are immutable after creation. */
45
+ slug?: string;
46
+ parent?: string | null;
47
+ description?: string;
48
+ image?: {
49
+ url: string;
50
+ alt?: string;
51
+ } | null;
52
+ isActive?: boolean;
53
+ displayOrder?: number;
54
+ }
55
+ export type CategoryUpdate = Partial<Omit<CategoryInput, 'slug'>>;
56
+ export interface MerchantCategory extends CategoryInput {
57
+ _id: string;
58
+ slug: string;
59
+ parentPath?: string;
60
+ productCount?: number;
61
+ }
62
+ export interface MerchantCategoryTree extends MerchantCategory {
63
+ children: MerchantCategoryTree[];
64
+ totalProductCount: number;
65
+ }
66
+ export interface MerchantOrder extends StorefrontOrder {
67
+ _id: string;
68
+ customerSnapshot?: {
69
+ name: string;
70
+ phone?: string;
71
+ email?: string;
72
+ };
73
+ shippingAddress?: Address;
74
+ lines: Array<{
75
+ lineId: string;
76
+ quantity: number;
77
+ snapshot: {
78
+ name?: string;
79
+ sku?: string;
80
+ };
81
+ lineTotal?: Money;
82
+ }>;
83
+ paymentState?: {
84
+ chargeStatus: string;
85
+ totalCharged?: Money;
86
+ };
87
+ }
88
+ export type OrderCommand = {
89
+ action: 'confirm' | 'start_processing' | 'ship' | 'deliver';
90
+ } | {
91
+ action: 'cancel';
92
+ reason: string;
93
+ };
94
+ export interface StoreSettings {
95
+ version: number | null;
96
+ currency: string;
97
+ deliveryChargeMinor: number;
98
+ }
99
+ export interface MerchantMedia {
100
+ _id: string;
101
+ folder: string;
102
+ size: number;
103
+ title?: string;
104
+ alt?: string;
105
+ createdAt?: string;
106
+ url: string;
107
+ filename: string;
108
+ mimeType?: string;
109
+ width?: number;
110
+ height?: number;
111
+ variants?: Array<{
112
+ name: string;
113
+ url: string;
114
+ width: number;
115
+ height: number;
116
+ mimeType: string;
117
+ size: number;
118
+ }>;
119
+ }
120
+ export interface IntegrationProvider {
121
+ service: string;
122
+ provider: string;
123
+ label: string;
124
+ oauth: boolean;
125
+ credentialFields: Array<{
126
+ name: string;
127
+ label: string;
128
+ type: 'text' | 'password' | 'number' | 'boolean' | 'select';
129
+ required: boolean;
130
+ secret?: boolean;
131
+ placeholder?: string;
132
+ help?: string;
133
+ options?: Array<{
134
+ label: string;
135
+ value: string;
136
+ }>;
137
+ }>;
138
+ }
139
+ export interface MerchantIntegration {
140
+ _id: string;
141
+ service: string;
142
+ provider: string;
143
+ label?: string;
144
+ enabled: boolean;
145
+ status: 'connected' | 'disabled' | 'error';
146
+ config: Record<string, unknown>;
147
+ secretsSet: Record<string, boolean>;
148
+ lastTestedAt?: string | null;
149
+ lastTestStatus?: 'ok' | 'failed' | 'skipped';
150
+ }
151
+ export interface NotificationDelivery {
152
+ _id: string;
153
+ timestamp: string;
154
+ event: string;
155
+ status: 'delivered' | 'partial' | 'failed' | 'skipped';
156
+ subject?: {
157
+ sourceModel: string;
158
+ sourceId: string;
159
+ };
160
+ }
161
+ export interface MerchantApiKey {
162
+ id: string;
163
+ kind: 'user' | 'service';
164
+ name: string;
165
+ prefix: string;
166
+ ownerId: string;
167
+ scopes: string[];
168
+ status: 'active' | 'expired' | 'revoked';
169
+ expiresAt: string | null;
170
+ lastUsedAt: string | null;
171
+ ceilingRole: string | null;
172
+ }
173
+ export interface ApiKeyOptions {
174
+ scopes: Array<{
175
+ scope: string;
176
+ requires: string[];
177
+ grantable: boolean;
178
+ }>;
179
+ ceilingRoles: Array<{
180
+ role: string;
181
+ scopes: string[];
182
+ }>;
183
+ can: {
184
+ own: boolean;
185
+ createService: boolean;
186
+ readBranch: boolean;
187
+ updateService: boolean;
188
+ deleteOthers: boolean;
189
+ };
190
+ lifetime: {
191
+ defaultDays: number;
192
+ maxDays: number;
193
+ allowNeverExpires?: boolean;
194
+ };
195
+ }
196
+ export interface ApiKeyInput {
197
+ kind: 'user' | 'service';
198
+ name: string;
199
+ scopes: string[];
200
+ /** Null explicitly requests a key that does not expire. */
201
+ expiresInDays: number | null;
202
+ ceilingRole?: string;
203
+ }
204
+ export interface IssuedApiKey {
205
+ key: string;
206
+ record: MerchantApiKey;
207
+ }
208
+ /** Inclusive start and exclusive end as ISO timestamps, at most 92 days apart. */
209
+ export interface AnalyticsWindow {
210
+ from: string;
211
+ to: string;
212
+ }
213
+ /** Order values include delivery charges and are independent of payment collection. */
214
+ export interface MerchantOverview {
215
+ summary: {
216
+ totalOrders: number;
217
+ totalOrderValue: number;
218
+ fulfilledOrders: number;
219
+ fulfilledOrderValue: number;
220
+ canceledOrders: number;
221
+ canceledOrderValue: number;
222
+ averageOrderValue: number;
223
+ };
224
+ statuses: Array<{
225
+ status: string;
226
+ count: number;
227
+ orderValue: number;
228
+ }>;
229
+ }
230
+ //#endregion
@@ -0,0 +1 @@
1
+ export {};