@growupsell/commerce 0.0.0-stage → 1.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1900 @@
1
+ import { C as Cart, l as OrderType, d as CartTotals, U as TaxDetails, j as DeliveryEstimate, g as CategoryTree, f as Category, B as Brand, e as CatalogSettings, x as ProductSection, p as ProductBlock, y as ProductSectionItem, w as ProductSearchOptions, P as Page, t as ProductListItem, u as ProductOverview, q as ProductDetail, r as ProductFilterConfig, V as VariantGroup, S as SearchSuggestion, O as Offer, J as PromoCode, G as GiftCard, Q as RewardPointSettings, L as ReviewListItem, N as ReviewSummary, K as ReviewEligibility, R as Review, E as ProductSku, i as CustomerProfile, h as CheckoutSettings, o as PlaceOrderResult, A as AdvanceOrder, n as PendingPayment, m as PaymentMethodType } from './checkout-CwpFl6qt.js';
2
+
3
+ /** The editor the merchant wrote the post with; decides how `body` sections are styled. */
4
+ type BlogEditor = 'quill' | 'ckeditor' | 'froala';
5
+ interface BlogCategory {
6
+ id: number;
7
+ name: string | null;
8
+ slug: string;
9
+ image: string | null;
10
+ seoDescription: string | null;
11
+ }
12
+ /** The staff member who wrote the post. Their email is never exposed. */
13
+ interface BlogAuthor {
14
+ id: number;
15
+ name: string | null;
16
+ photo: string | null;
17
+ }
18
+ interface BlogPostSummary {
19
+ id: number;
20
+ slug: string;
21
+ title: string;
22
+ shortDescription: string | null;
23
+ image: string | null;
24
+ category: BlogCategory | null;
25
+ tags: string[];
26
+ /** Null when the author's account was deleted. */
27
+ author: BlogAuthor | null;
28
+ /** The merchant panel has no publish switch yet, so this is false for most posts (W29). */
29
+ isPublished: boolean;
30
+ createdAt: string;
31
+ updatedAt: string;
32
+ }
33
+ interface BlogPost extends BlogPostSummary {
34
+ /** HTML sections, in order. Raw merchant HTML: sanitise before rendering. */
35
+ body: string[];
36
+ editor: BlogEditor;
37
+ }
38
+
39
+ /**
40
+ * Structural types for the HTTP layer. `commerce` has no DOM or Node types (ADR-009), so the
41
+ * parts of `fetch` it uses are described here. Any standard `fetch` satisfies `FetchLike`.
42
+ */
43
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
44
+ /** Anything with `append`, e.g. the platform's `FormData` (React Native's included). */
45
+ interface FormDataLike {
46
+ append(name: string, value: unknown, fileName?: string): void;
47
+ }
48
+ interface FetchInitLike {
49
+ method: HttpMethod;
50
+ headers: Record<string, string>;
51
+ body?: string | FormDataLike;
52
+ /** An `AbortSignal` when the runtime has `AbortController`. */
53
+ signal?: unknown;
54
+ }
55
+ interface FetchResponseLike {
56
+ readonly ok: boolean;
57
+ readonly status: number;
58
+ text(): Promise<string>;
59
+ /** Read for the content type of binary responses. Optional, so older custom fetches fit. */
60
+ readonly headers?: {
61
+ get(name: string): string | null;
62
+ };
63
+ /** Needed only for `responseType: 'binary'` (PDF downloads). */
64
+ arrayBuffer?(): Promise<ArrayBuffer>;
65
+ }
66
+ /** What a request with `responseType: 'binary'` resolves with. */
67
+ interface BinaryResponse {
68
+ /** The response's `content-type`, e.g. `application/pdf`; `null` when unknown. */
69
+ contentType: string | null;
70
+ data: ArrayBuffer;
71
+ }
72
+ /** What the client calls. Write custom fetch functions (logging, guards, tests) to this type. */
73
+ type FetchLike = (url: string, init: FetchInitLike) => Promise<FetchResponseLike>;
74
+ /**
75
+ * What the `fetch` option accepts: `FetchLike`, or the platform's own `fetch` (browser,
76
+ * React Native, Node) without a cast. The platform's `RequestInit` type is stricter than
77
+ * `FetchInitLike` (it needs DOM types `commerce` cannot use), so `init` is typed `never`
78
+ * here, which every fetch implementation accepts. The client always passes a `FetchInitLike`.
79
+ * Proven by a type test in tests/http/client.test.ts.
80
+ */
81
+ type AnyFetch = (url: string, init: never) => Promise<FetchResponseLike>;
82
+ /**
83
+ * Who the request is for. Read before every request, so tokens can change and can come from
84
+ * async storage (Expo SecureStore).
85
+ */
86
+ interface Credentials {
87
+ /** Sent as the `business` header. */
88
+ businessId?: number | string | null;
89
+ /** Logged-in customer: `x-customer-token`. */
90
+ customerToken?: string | null;
91
+ /** Guest or order session: `x-session-token`. */
92
+ sessionToken?: string | null;
93
+ /** Merchant: `Authorization: Token …`, only on requests made with `merchant: true`. */
94
+ merchantToken?: string | null;
95
+ }
96
+ interface UnauthorizedEvent {
97
+ /**
98
+ * Which token the backend rejected. `session`: the guest session (`x-session-token`) is
99
+ * unknown to the backend; drop it and create a new one with `session.create()`.
100
+ */
101
+ kind: 'customer' | 'merchant' | 'session';
102
+ /**
103
+ * The rejected token, so a holder of several logins can tell whether the current one expired
104
+ * or a request sent with an older one came back late. Compare it and drop it: never persist,
105
+ * log or send it anywhere.
106
+ */
107
+ token: string;
108
+ }
109
+ interface ClientOptions {
110
+ /** Backend base URL, e.g. `https://api.example.com`. Required: no default backend. */
111
+ baseURL: string;
112
+ credentials?: () => Credentials | Promise<Credentials>;
113
+ /**
114
+ * Called once per rejected token (never for requests that sent no token). The app decides
115
+ * what to do: reload on web, go to the login screen on mobile.
116
+ */
117
+ onUnauthorized?: (event: UnauthorizedEvent) => void;
118
+ /** Defaults to the global `fetch`. Inject one to add logging, guards or a test double. */
119
+ fetch?: AnyFetch;
120
+ /** Abort requests slower than this. Default 15000 ms. */
121
+ timeoutMs?: number;
122
+ }
123
+ type QueryValue = string | number | boolean | null | undefined | ReadonlyArray<string | number | boolean>;
124
+ type Query = Record<string, QueryValue>;
125
+ interface RequestOptions {
126
+ query?: Query;
127
+ /** Sent as JSON. */
128
+ json?: unknown;
129
+ /** Sent as multipart form data (file uploads). */
130
+ formData?: FormDataLike;
131
+ /** Merchant request: sends the merchant token, and is refused without one. */
132
+ merchant?: boolean;
133
+ /**
134
+ * Leave out the customer token. For public endpoints (catalog, policies): the backend
135
+ * rejects any request carrying an expired token, so a lapsed login must not break browsing.
136
+ */
137
+ anonymous?: boolean;
138
+ /**
139
+ * The endpoint resolves the business from the `business` header and fails without it.
140
+ * The client then refuses to send the request without `credentials().businessId`.
141
+ */
142
+ business?: 'required';
143
+ /**
144
+ * The endpoint behaves differently without a guest session (`x-session-token`), e.g. the
145
+ * product listing then includes drafts. The client refuses the request without one.
146
+ * `omit`: never send the session token, e.g. when creating a new session to replace it.
147
+ */
148
+ session?: 'required' | 'omit';
149
+ /**
150
+ * Only for a logged-in customer (wishlist, own reviews, orders). The client refuses the
151
+ * request without `credentials().customerToken`.
152
+ */
153
+ customer?: 'required';
154
+ /**
155
+ * Works for a logged-in customer or a guest (cart, offers, promo codes): the backend finds
156
+ * the shopper by `x-customer-token` or `x-session-token`. The client refuses the request when
157
+ * it would send neither (a customer token left out by `anonymous` does not count).
158
+ */
159
+ customerOrSession?: 'required';
160
+ /** Abort this request after this many ms instead of `ClientOptions.timeoutMs`. */
161
+ timeoutMs?: number;
162
+ /**
163
+ * `'binary'` resolves a successful response as a `BinaryResponse` (PDF downloads) instead of
164
+ * parsing JSON. Error responses are still read as text and thrown as `GrowupsellApiError`.
165
+ * Default `'json'`.
166
+ */
167
+ responseType?: 'json' | 'binary';
168
+ }
169
+ /**
170
+ * Low-level request. Domain modules (business, product, cart…) are built on it; prefer those.
171
+ * Resolves with the parsed JSON body, or `undefined` for an empty response.
172
+ */
173
+ type RequestFn = <T = unknown>(method: HttpMethod, path: string, options?: RequestOptions) => Promise<T>;
174
+
175
+ interface BlogApi {
176
+ /** Newest first; all of the business's posts (the backend does not page this list). */
177
+ list(options?: {
178
+ categorySlug?: string;
179
+ }): Promise<BlogPostSummary[]>;
180
+ /** Throws GrowupsellApiError with status 404 for an unknown slug. */
181
+ get(slug: string): Promise<BlogPost>;
182
+ /** Merchant (theme edit): replaces the post's HTML sections. */
183
+ updateBody(slug: string, body: string[]): Promise<void>;
184
+ }
185
+
186
+ /**
187
+ * Business domain types: camelCase, no backend quirks (empty strings become null).
188
+ * Built from wire responses in http/business.ts.
189
+ */
190
+ /** A merchant business on the Growupsell platform: what `/business/detail/{domain}` resolves. */
191
+ interface Business {
192
+ id: number;
193
+ name: string | null;
194
+ description: string | null;
195
+ legalName: string | null;
196
+ gstNumber: string | null;
197
+ panNumber: string | null;
198
+ isTemplate: boolean;
199
+ /** `type` is the kind of business, e.g. `retail`, `restaurant`, `services`. */
200
+ category: {
201
+ id: number;
202
+ name: string;
203
+ type: string;
204
+ } | null;
205
+ legalForm: {
206
+ id: number;
207
+ name: string;
208
+ } | null;
209
+ /**
210
+ * Platform features enabled for this business, e.g. `ecommerce`, `giftcard`, `blog`,
211
+ * `deals-discount`. Use them to show or hide functionality.
212
+ */
213
+ features: string[];
214
+ /** Google Tag Manager container to load, or null when tag tracking is off. */
215
+ googleTagManagerId: string | null;
216
+ /** ISO 8601. */
217
+ createdAt: string;
218
+ /** ISO 8601. */
219
+ updatedAt: string;
220
+ }
221
+ interface Currency {
222
+ /** ISO 4217, e.g. `INR`. */
223
+ code: string;
224
+ /** e.g. `₹`. */
225
+ symbol: string;
226
+ }
227
+ interface BusinessSettings {
228
+ currency: Currency | null;
229
+ domain: string | null;
230
+ }
231
+ interface BusinessMedia {
232
+ logoSquare: string | null;
233
+ logoRectangle: string | null;
234
+ }
235
+ interface SocialLinks {
236
+ facebook: string | null;
237
+ instagram: string | null;
238
+ twitter: string | null;
239
+ telegram: string | null;
240
+ whatsapp: string | null;
241
+ linkedin: string | null;
242
+ }
243
+ /** A way to pay by direct bank or UPI transfer, shown at checkout. */
244
+ interface BankTransferOption {
245
+ id: number;
246
+ type: string | null;
247
+ description: string | null;
248
+ /** e.g. a QR code or bank logo. */
249
+ photo: string | null;
250
+ paymentMethodId: number | null;
251
+ }
252
+ /**
253
+ * The storefront's look, and the id its theme content is stored under. One per business,
254
+ * created by the backend on first read.
255
+ */
256
+ interface ThemeSettings {
257
+ /**
258
+ * The theme settings id. Pass it as `themeId` to `client.themeContent`: theme content
259
+ * belongs to this row, not to `websiteThemeId`.
260
+ */
261
+ id: number;
262
+ /** CSS colour, e.g. `#00484C`. The backend's default is `#CCC`. */
263
+ primaryColor: string;
264
+ accentColor: string | null;
265
+ /** A font name chosen by the storefront, e.g. `poppins`; the backend stores it as given. */
266
+ fontFamily: string | null;
267
+ /** The platform theme (template) chosen for the website, if any. Not a theme content id. */
268
+ websiteThemeId: number | null;
269
+ /** The platform theme (template) chosen for the mobile app, if any. */
270
+ appThemeId: number | null;
271
+ }
272
+ /** Merchant: the theme settings fields to change. Fields left out keep their value. */
273
+ interface ThemeSettingsUpdate {
274
+ /** At most 8 characters, e.g. `#00484C`. Cannot be cleared. */
275
+ primaryColor?: string;
276
+ /** At most 8 characters; null clears it. */
277
+ accentColor?: string | null;
278
+ /** At most 256 characters; null clears it. */
279
+ fontFamily?: string | null;
280
+ websiteThemeId?: number | null;
281
+ appThemeId?: number | null;
282
+ }
283
+
284
+ interface BusinessApi {
285
+ /**
286
+ * The business a storefront domain belongs to. The backend ignores a `:port` and maps
287
+ * `localhost` / `127.0.0.1` to its development business. Call this first: its `id` is the
288
+ * `businessId` every other business-scoped request needs.
289
+ */
290
+ detail(domain: string): Promise<Business>;
291
+ /** Currency and primary domain. */
292
+ settings(): Promise<BusinessSettings>;
293
+ /** Logos. */
294
+ media(): Promise<BusinessMedia>;
295
+ socialLinks(): Promise<SocialLinks>;
296
+ /** Direct bank / UPI transfer options offered at checkout (deleted ones excluded). */
297
+ bankTransferOptions(): Promise<BankTransferOption[]>;
298
+ /**
299
+ * Colours and font, and the theme settings `id` that `client.themeContent` takes as
300
+ * `themeId`. Every storefront page needs it. Public.
301
+ */
302
+ themeSettings(): Promise<ThemeSettings>;
303
+ /**
304
+ * Merchant (manager role): changes the given fields of the merchant's own theme settings and
305
+ * returns the result.
306
+ */
307
+ updateThemeSettings(changes: ThemeSettingsUpdate): Promise<ThemeSettings>;
308
+ }
309
+
310
+ interface CartAddressesInput {
311
+ /** One of the customer's own addresses. */
312
+ shippingAddressId?: number;
313
+ billingAddressId?: number;
314
+ }
315
+ interface AddCartItemInput {
316
+ skuId: number;
317
+ /** A whole number, 1 or more. */
318
+ quantity: number;
319
+ /** Answers to the product's custom fields (e.g. a ring size). */
320
+ customData?: Record<string, unknown>;
321
+ }
322
+ /**
323
+ * The shopper's cart. The backend finds it from the login (`customerToken`) or, for a guest,
324
+ * from the session (`sessionToken`) plus the business; it calculates every total on every
325
+ * change, so refetch the cart with `get()` after a change instead of adjusting totals locally.
326
+ */
327
+ interface CartApi {
328
+ /**
329
+ * The cart, with its lines and the backend's totals. Creates the cart on first call (the
330
+ * backend does): call once per screen, not in a loop.
331
+ */
332
+ get(): Promise<Cart>;
333
+ /**
334
+ * Adds a SKU (pick the variant first). Adding a SKU already in the cart adds the quantity to
335
+ * its line, so a repeated request adds twice: disable the button while one is pending.
336
+ * Made-to-order advance products are not added to the cart: use
337
+ * `checkout.createAdvanceOrder()`.
338
+ */
339
+ addItem(input: AddCartItemInput): Promise<{
340
+ lineId: number;
341
+ }>;
342
+ /** Sets a line's quantity (`CartLine.id`); 0 removes the line. Safe to repeat. */
343
+ setQuantity(lineId: number, quantity: number): Promise<void>;
344
+ /** Removes a line (`CartLine.id`). */
345
+ removeItem(lineId: number): Promise<void>;
346
+ /**
347
+ * Switches between online payment (`delivery`) and cash on delivery (`cod`); the backend
348
+ * recalculates the delivery charge. Offers apply to `delivery` carts only.
349
+ */
350
+ setOrderType(type: OrderType): Promise<CartTotals>;
351
+ /**
352
+ * Sets the shipping and/or billing address: ids of the logged-in customer's own addresses
353
+ * (another customer's address is a 404). Needs a customer login: guests cannot set addresses.
354
+ */
355
+ setAddresses(input: CartAddressesInput): Promise<{
356
+ shippingAddressId: number | null;
357
+ billingAddressId: number | null;
358
+ }>;
359
+ /**
360
+ * Applies an offer by id (`Offer.id`, from `promotions.eligibleOffers()`), or removes it with
361
+ * `null`. Clears any promo code: offers and promo codes do not combine. Refetch the cart for
362
+ * the new totals.
363
+ */
364
+ applyOffer(offerId: number | null): Promise<void>;
365
+ /**
366
+ * Applies a promo code by id (`PromoCode.id`, from `promotions.promoCode(code)`), or removes
367
+ * it with `null`. Clears any offer. Expired or already redeemed codes are refused (400 with
368
+ * the backend's message). Refetch the cart for the new totals.
369
+ */
370
+ applyPromoCode(promoCodeId: number | null): Promise<void>;
371
+ /**
372
+ * Business tax details for the invoice (legal name, GSTIN). Creates or replaces them, so it
373
+ * is safe to repeat. The backend requires a guest session token here, even for customers.
374
+ */
375
+ setTaxDetails(input: TaxDetails): Promise<TaxDetails>;
376
+ /**
377
+ * A delivery partner's quote for the cart (`Cart.id`). Only meaningful when
378
+ * `cart.deliveryChargeType === 'as_per_delivery_partner'` (otherwise a 400). Without a
379
+ * pincode the backend uses the cart's shipping address. The backend stores the quote in the
380
+ * cart's totals, so refetch the cart afterwards. Slow: the backend asks the partner.
381
+ */
382
+ deliveryEstimate(cartId: number, options?: {
383
+ pincode?: string;
384
+ }): Promise<DeliveryEstimate>;
385
+ }
386
+
387
+ interface CatalogApi {
388
+ /**
389
+ * Top-level categories with their sub-categories. With `search`, the categories whose name
390
+ * or description match; when only sub-categories match, their parents with just those
391
+ * sub-categories.
392
+ */
393
+ categories(options?: {
394
+ search?: string;
395
+ }): Promise<CategoryTree[]>;
396
+ /** One category, without sub-categories. */
397
+ category(id: number): Promise<Category>;
398
+ /** Several categories by id, without sub-categories. An empty list resolves to `[]`. */
399
+ categoriesByIds(ids: readonly number[]): Promise<Category[]>;
400
+ /** Brands; with `categoryIds`, those with products in these categories or their sub-categories. */
401
+ brands(options?: {
402
+ categoryIds?: readonly number[];
403
+ }): Promise<Brand[]>;
404
+ /** Which catalog features the merchant has switched on. */
405
+ settings(): Promise<CatalogSettings>;
406
+ }
407
+
408
+ /** The store's frequently asked questions. Built from wire responses in http/faq.ts. */
409
+ interface Faq {
410
+ id: number;
411
+ /** The backend allows an empty question or answer; both are null when empty. */
412
+ question: string | null;
413
+ /** HTML written by the merchant. */
414
+ answer: string | null;
415
+ /** Position in the list, ascending. Not contiguous: gaps are normal. */
416
+ displayOrder: number;
417
+ }
418
+
419
+ /**
420
+ * Store policies and terms (privacy policy, terms of use, refunds, shipping, warranty), each a
421
+ * list of headed sections. Built from wire responses in http/policies.ts.
422
+ */
423
+ type PolicyType = 'privacy' | 'terms-of-use' | 'cancellation-refund' | 'shipping' | 'warranty';
424
+ /** One headed section of a policy page. */
425
+ interface Policy {
426
+ id: number;
427
+ type: PolicyType;
428
+ heading: string;
429
+ /** HTML written by the merchant; null when empty. */
430
+ description: string | null;
431
+ /** Position within its type, ascending. Not contiguous: gaps are normal. */
432
+ displayOrder: number;
433
+ }
434
+
435
+ /** What a merchant writes for one policy section. */
436
+ interface PolicyInput {
437
+ type: PolicyType;
438
+ heading: string;
439
+ /** HTML. */
440
+ description?: string | null;
441
+ }
442
+ /** Move a row one place towards the top (`up`) or the bottom (`down`) of its list. */
443
+ type MoveDirection = 'up' | 'down';
444
+ interface PoliciesApi {
445
+ /** Every policy section, or one type's, in display order. Public. */
446
+ list(options?: {
447
+ type?: PolicyType;
448
+ }): Promise<Policy[]>;
449
+ /** Merchant: adds a section at the end of its type. */
450
+ create(input: PolicyInput): Promise<Policy>;
451
+ /**
452
+ * Merchant: adds several sections at once, in order (e.g. a default policy set). They are
453
+ * numbered after the last section of the first input's type. An empty list sends nothing.
454
+ */
455
+ createMany(inputs: readonly PolicyInput[]): Promise<Policy[]>;
456
+ /** Merchant: changes only the given fields. */
457
+ update(id: number, changes: Partial<PolicyInput>): Promise<Policy>;
458
+ /**
459
+ * Merchant: swaps the section with its neighbour of the same type. Rejects with a 400
460
+ * `GrowupsellApiError` when it is already first (`up`) or last (`down`).
461
+ */
462
+ move(id: number, direction: MoveDirection): Promise<void>;
463
+ /** Merchant: deletes the section for good. */
464
+ remove(id: number): Promise<void>;
465
+ }
466
+
467
+ /** What a merchant writes for one question. */
468
+ interface FaqInput {
469
+ question: string;
470
+ /** HTML. */
471
+ answer: string;
472
+ }
473
+ interface FaqApi {
474
+ /** The questions in display order. Public. */
475
+ list(): Promise<Faq[]>;
476
+ /** Merchant (manager role): adds a question at the end. */
477
+ create(input: FaqInput): Promise<Faq>;
478
+ /** Merchant: changes only the given fields. */
479
+ update(id: number, changes: Partial<FaqInput>): Promise<Faq>;
480
+ /**
481
+ * Merchant: swaps the question with its neighbour. Rejects with a 400 `GrowupsellApiError`
482
+ * when it is already first (`up`) or last (`down`).
483
+ */
484
+ move(id: number, direction: MoveDirection): Promise<void>;
485
+ /** Merchant: removes the question from the list. */
486
+ remove(id: number): Promise<void>;
487
+ }
488
+
489
+ type FormFieldType = 'text' | 'boolean' | 'date' | 'number' | 'email' | 'phone' | 'select' | 'radio' | 'image' | 'file';
490
+ interface FormFieldOption {
491
+ label: string;
492
+ value: string;
493
+ }
494
+ interface FormField {
495
+ /** The key to submit the value under. */
496
+ name: string;
497
+ type: FormFieldType;
498
+ required: boolean;
499
+ /** The merchant's label, or the name with `_` turned into spaces. */
500
+ label: string;
501
+ /** Only for `select` and `radio`; otherwise empty. */
502
+ options: FormFieldOption[];
503
+ }
504
+ /** A merchant-built form, as a shopper fills it in. */
505
+ interface PublicForm {
506
+ id: number;
507
+ slug: string;
508
+ name: string;
509
+ description: string | null;
510
+ /** What to show after a successful submit; null means the app's default. */
511
+ successMessage: string | null;
512
+ fields: FormField[];
513
+ }
514
+ /** A form for the merchant to pick (e.g. for a banner button). */
515
+ interface FormSummary {
516
+ id: number;
517
+ name: string;
518
+ slug: string;
519
+ }
520
+ /**
521
+ * A submitted value. `image` / `file` fields take a URL string: upload the file first. Guests
522
+ * cannot fill them today, because the only upload endpoint needs a customer login (ADR-023).
523
+ */
524
+ type FormValue = string | number | boolean | null;
525
+
526
+ interface FormsApi {
527
+ /** An active form. Throws GrowupsellApiError 404 when it does not exist or is inactive. */
528
+ get(slug: string): Promise<PublicForm>;
529
+ /**
530
+ * Submits the values, keyed by field name. A logged-in customer is attributed automatically.
531
+ * Rejected values come back in the error's `fieldErrors.data`. `image` / `file` fields take a
532
+ * URL string.
533
+ */
534
+ submit(slug: string, values: Record<string, FormValue>): Promise<void>;
535
+ /** Merchant (theme edit): active forms to pick from, newest first. */
536
+ listForMerchant(): Promise<FormSummary[]>;
537
+ }
538
+
539
+ /** A contact-us message. */
540
+ interface InquiryInput {
541
+ subject: string;
542
+ message: string;
543
+ /** For guests; a logged-in customer is attributed automatically. */
544
+ name?: string;
545
+ email?: string;
546
+ /** International format, e.g. "+919876543210" (the backend rejects other formats). */
547
+ phone?: string;
548
+ }
549
+ interface HelpdeskApi {
550
+ /**
551
+ * Sends a contact-us inquiry; the business may email an acknowledgement to `email`.
552
+ * Rejected values come back in the error's `fieldErrors` (e.g. `phone_number`).
553
+ */
554
+ createInquiry(input: InquiryInput): Promise<void>;
555
+ }
556
+
557
+ /** Email newsletter sign-up (backend `/subscribers/`). */
558
+ interface NewsletterApi {
559
+ /**
560
+ * Emails a one-time code to confirm the address (again, if one is pending). An address that
561
+ * is already subscribed rejects with a 400 `GrowupsellApiError` ("This email is already
562
+ * subscribed.").
563
+ */
564
+ subscribe(email: string): Promise<void>;
565
+ /**
566
+ * Confirms the address with the emailed code. A wrong code comes back in `fieldErrors.otp`;
567
+ * an address that never signed up is a 500 from the backend today (C21).
568
+ */
569
+ verify(email: string, otp: string): Promise<void>;
570
+ }
571
+
572
+ /**
573
+ * A browser's web push subscription as plain data: what `PushSubscription.toJSON()` returns,
574
+ * so `client.push.subscribe(subscription.toJSON())` type-checks. `commerce` has no DOM types,
575
+ * so this mirrors the DOM's `PushSubscriptionJSON` (every field optional there too). The
576
+ * library refuses one without `endpoint`, `keys.p256dh` or `keys.auth` before sending.
577
+ */
578
+ interface PushSubscriptionJson {
579
+ endpoint?: string;
580
+ expirationTime?: number | null;
581
+ keys?: Record<string, string>;
582
+ }
583
+
584
+ /**
585
+ * Web push subscriptions (backend `/subscribers/push/…`). The app does the browser work
586
+ * (permission, service worker, `pushManager.subscribe` with the VAPID key) and passes
587
+ * `PushSubscription.toJSON()` in; the library never touches platform APIs.
588
+ */
589
+ interface PushApi {
590
+ /**
591
+ * The server's VAPID public key (base64url), for `pushManager.subscribe`'s
592
+ * `applicationServerKey`. Null when the backend has none configured.
593
+ */
594
+ vapidPublicKey(): Promise<string | null>;
595
+ /**
596
+ * Registers the browser's subscription for the business. When a customer is logged in, the
597
+ * backend links it to the customer (one subscription per customer: a new browser replaces the
598
+ * old one). Safe to repeat: an existing subscription is updated. Rejects without a request
599
+ * (`TypeError`) when `endpoint`, `keys.p256dh` or `keys.auth` is missing.
600
+ */
601
+ subscribe(subscription: PushSubscriptionJson): Promise<void>;
602
+ /**
603
+ * Switches the subscription off for the business. Resolves also when the backend had no such
604
+ * subscription. Call it before `PushSubscription.unsubscribe()` in the browser. Rejects
605
+ * without a request (`TypeError`) like `subscribe`.
606
+ */
607
+ unsubscribe(subscription: PushSubscriptionJson): Promise<void>;
608
+ }
609
+
610
+ /** The merchant's chat button. WhatsApp is the only channel the backend offers. */
611
+ interface LiveSupportWidget {
612
+ channel: 'whatsapp';
613
+ /** As the merchant typed it, e.g. "919876543210" (country code, no `+`). */
614
+ whatsappNumber: string;
615
+ }
616
+
617
+ interface LiveSupportApi {
618
+ /** The chat button to show, or null when the merchant turned it off or never set a number. */
619
+ widget(): Promise<LiveSupportWidget | null>;
620
+ }
621
+
622
+ /**
623
+ * Who sees a new or changed section item:
624
+ * - `'all'`: every variant.
625
+ * - `{ variantIds }`: one SKU, named by its variant options (one per variant type, e.g.
626
+ * `[128 GB, Blue]`). The backend answers 400 if they are not a whole SKU.
627
+ * - `{ variantId }`: every SKU with this variant option.
628
+ */
629
+ type ProductSectionItemTargetInput = 'all' | {
630
+ variantIds: readonly number[];
631
+ } | {
632
+ variantId: number;
633
+ };
634
+ interface ProductPageApi {
635
+ /** The product's sections with their items. Public. */
636
+ sections(productId: number): Promise<ProductSection[]>;
637
+ /** The product's slug-keyed blocks (e.g. `hero`, `showcase`), in display order. Public. */
638
+ blocks(productId: number, options?: {
639
+ slug?: string;
640
+ }): Promise<ProductBlock[]>;
641
+ /** Adds an empty section to the product. */
642
+ createSection(input: {
643
+ productId: number;
644
+ title: string;
645
+ }): Promise<ProductSection>;
646
+ /** Deletes a section and its items. */
647
+ deleteSection(id: number): Promise<void>;
648
+ /** Adds an item at the end of a section. `target` defaults to `'all'`. */
649
+ addSectionItem(input: {
650
+ sectionId: number;
651
+ data: unknown;
652
+ target?: ProductSectionItemTargetInput;
653
+ }): Promise<ProductSectionItem>;
654
+ /**
655
+ * Changes an item's data and/or target (left out: unchanged). `sectionId` is the item's
656
+ * section: the backend requires it on every update.
657
+ */
658
+ updateSectionItem(id: number, input: {
659
+ sectionId: number;
660
+ data?: unknown;
661
+ target?: ProductSectionItemTargetInput;
662
+ }): Promise<ProductSectionItem>;
663
+ /**
664
+ * Swaps the item with its neighbour. Rejects with a 400 when it is already first (`up`) or
665
+ * last (`down`).
666
+ */
667
+ moveSectionItem(id: number, direction: 'up' | 'down'): Promise<void>;
668
+ deleteSectionItem(id: number): Promise<void>;
669
+ /** Adds a block. See `ProductBlock` for the checks the backend makes on `hero`/`showcase`. */
670
+ createBlock(input: {
671
+ productId: number;
672
+ slug: string;
673
+ data: unknown;
674
+ }): Promise<ProductBlock>;
675
+ updateBlock(id: number, input: {
676
+ slug?: string;
677
+ data?: unknown;
678
+ }): Promise<ProductBlock>;
679
+ deleteBlock(id: number): Promise<void>;
680
+ }
681
+
682
+ interface ProductsApi {
683
+ /**
684
+ * The product listing: one item per sellable SKU, only products available on the website.
685
+ * Needs a guest session: without one the backend also returns drafts and hidden products.
686
+ */
687
+ search(options?: ProductSearchOptions): Promise<Page<ProductListItem>>;
688
+ /** Products (not SKUs), e.g. for home-page sliders. Only available products. */
689
+ list(options?: {
690
+ ids?: readonly number[];
691
+ categoryIds?: readonly number[];
692
+ limit?: number;
693
+ offset?: number;
694
+ }): Promise<Page<ProductOverview>>;
695
+ detail(id: number): Promise<ProductDetail>;
696
+ /** Price range and specification filters, for the business or one category. */
697
+ filterConfig(options?: {
698
+ categoryId?: number;
699
+ }): Promise<ProductFilterConfig>;
700
+ /** Variant groups available in these categories (and their sub-categories), for filters. */
701
+ variantFilters(categoryIds: readonly number[]): Promise<VariantGroup[]>;
702
+ /** Quick search suggestions (products and categories) for a search box. */
703
+ suggestions(query: string): Promise<SearchSuggestion[]>;
704
+ }
705
+
706
+ interface PromotionsApi {
707
+ /**
708
+ * One page of the business's offers. Includes expired and inactive offers, with their coupon
709
+ * codes: for the merchant's theme editor, not for shoppers (use `eligibleOffers()`).
710
+ */
711
+ offers(options?: {
712
+ limit?: number;
713
+ offset?: number;
714
+ }): Promise<Page<Offer>>;
715
+ /**
716
+ * The offers the shopper's cart qualifies for now. Empty for a cash-on-delivery cart (the
717
+ * backend only offers them on `delivery` carts). Apply one with `cart.applyOffer(id)`.
718
+ */
719
+ eligibleOffers(): Promise<Offer[]>;
720
+ /**
721
+ * Looks a promo code up (trimmed, upper-cased: codes are stored upper-case). `null` for an
722
+ * unknown or blank code. Check `status` before applying it with `cart.applyPromoCode(id)`.
723
+ */
724
+ promoCode(code: string): Promise<PromoCode | null>;
725
+ /**
726
+ * Checks a gift card's code and PIN. Rejects with a 400 and a field error (`code` or `pin`)
727
+ * when the card is unknown, the PIN is wrong, or the card is used up or expired. The PIN is
728
+ * not kept in the result; pass the card's `id` to checkout.
729
+ */
730
+ validateGiftCard(input: {
731
+ code: string;
732
+ pin: string;
733
+ }): Promise<GiftCard>;
734
+ /** The business's reward point configuration. */
735
+ rewardPointSettings(): Promise<RewardPointSettings>;
736
+ }
737
+
738
+ /**
739
+ * A file to upload, passed through to the platform's `FormData` untouched: a `File` or `Blob`
740
+ * on the web, `{ uri, name, type }` in React Native.
741
+ */
742
+ type UploadFile = unknown;
743
+
744
+ /**
745
+ * A photo to attach: a `File` or `Blob` on the web, `{ uri, name, type }` in React Native.
746
+ * Sent as multipart form data with the platform's `FormData`.
747
+ */
748
+ type ReviewPhoto = UploadFile;
749
+ interface CreateReviewInput {
750
+ /**
751
+ * The logged-in customer's id. The backend takes the author from the request body, not from
752
+ * the login, so it must be sent.
753
+ */
754
+ customerId: number;
755
+ productId: number;
756
+ /** From `eligibility()`: the ordered item the review is for. */
757
+ orderProductId?: number;
758
+ title: string;
759
+ message?: string;
760
+ /** 1 to 5, in steps of 0.5. */
761
+ rating: number;
762
+ photo?: ReviewPhoto;
763
+ }
764
+ interface UpdateReviewInput {
765
+ title?: string;
766
+ message?: string;
767
+ rating?: number;
768
+ /** Replaces the photo. */
769
+ photo?: ReviewPhoto;
770
+ }
771
+ interface ReviewsApi {
772
+ /** A product's published reviews (all of them: the backend does not page this list). */
773
+ forProduct(productId: number): Promise<ReviewListItem[]>;
774
+ /** The business's reviews, newest first, e.g. for a home-page slider. */
775
+ latest(options?: {
776
+ /** Only ratings above this, e.g. 3 for 3.5 and up. */
777
+ minRating?: number;
778
+ /** Matches the title, the message and the customer's name. */
779
+ search?: string;
780
+ /** Default 20. */
781
+ limit?: number;
782
+ offset?: number;
783
+ }): Promise<Page<ReviewListItem>>;
784
+ /** Review count, average rating and the number of reviews per star. */
785
+ summary(productId: number): Promise<ReviewSummary>;
786
+ /**
787
+ * Whether the logged-in customer may review a product: they ordered it and have not reviewed
788
+ * it yet. The backend does not enforce this on `create()`: check it first.
789
+ */
790
+ eligibility(productId: number): Promise<ReviewEligibility>;
791
+ create(input: CreateReviewInput): Promise<Review>;
792
+ /** Changes the customer's own review; only the given fields change. */
793
+ update(reviewId: number, changes: UpdateReviewInput): Promise<Review>;
794
+ /** Deletes the customer's own review. */
795
+ remove(reviewId: number): Promise<void>;
796
+ }
797
+
798
+ /** Wishlist domain types. Built from wire responses in http/wishlist.ts. */
799
+
800
+ /** A saved SKU in the logged-in customer's wishlist. */
801
+ interface WishlistItem {
802
+ /** The wishlist entry id: pass it to `wishlist.remove()`. */
803
+ id: number;
804
+ /** Product name with the SKU's variants, e.g. "Iphone 15 (128 GB, Blue)". */
805
+ name: string;
806
+ photo: string | null;
807
+ productId: number;
808
+ productSlug: string;
809
+ /** Null only for old entries saved without a SKU; those cannot be added to the cart. */
810
+ sku: ProductSku | null;
811
+ /**
812
+ * False when the product or SKU was deleted, deactivated or set back to draft since it was
813
+ * saved (the backend still lists those). Do not offer "move to cart" then.
814
+ */
815
+ available: boolean;
816
+ /** `none` for stocked goods; custom modes are made to order. */
817
+ fulfillmentMode: string;
818
+ /** Advance payment required for made-to-order products. */
819
+ advanceAmount: number | null;
820
+ hasDigitalDelivery: boolean;
821
+ }
822
+
823
+ interface WishlistApi {
824
+ /** The logged-in customer's saved SKUs. */
825
+ list(): Promise<WishlistItem[]>;
826
+ /**
827
+ * Saves a SKU (not a product: pick the variant first). Rejects with a 400 when the SKU is
828
+ * already saved.
829
+ */
830
+ add(skuId: number): Promise<{
831
+ id: number;
832
+ productId: number;
833
+ }>;
834
+ /** Removes an entry by its wishlist id (`WishlistItem.id`), not by SKU. */
835
+ remove(wishlistId: number): Promise<void>;
836
+ }
837
+
838
+ /**
839
+ * Theme content: the merchant's free-form JSON document for each storefront section (header,
840
+ * footer, home, announcement…), and images uploaded for it. Built in http/theme-content.ts.
841
+ */
842
+ /** Which app the content was saved from. Always passed in by the app, never guessed (ADR-009). */
843
+ type ContentPlatform = 'website' | 'app';
844
+ /**
845
+ * One section's document. Its shape is decided by the storefront that wrote it, not by the
846
+ * backend, so `data` is typed by the caller (`unknown` by default): validate it before use.
847
+ */
848
+ interface ThemeContent<T = unknown> {
849
+ id: number;
850
+ /** The business's theme settings id the content belongs to. */
851
+ themeId: number;
852
+ /** The section, e.g. `header`, `footer`, `home`, `announcement`. */
853
+ slug: string;
854
+ /**
855
+ * The platform of the last save. A label only: website and app share one document per slug,
856
+ * so saving from one replaces what the other saved.
857
+ */
858
+ platform: ContentPlatform;
859
+ data: T;
860
+ }
861
+ /** What an uploaded image is for. */
862
+ type ContentImageUsage = 'banner' | 'slider' | 'header-logo' | 'footer-logo' | 'other';
863
+ interface ContentImage {
864
+ id: number;
865
+ /** Public URL of the stored file, to put into a theme content document. */
866
+ url: string;
867
+ usage: ContentImageUsage | null;
868
+ }
869
+
870
+ /** An image to upload for theme content. */
871
+ interface ContentImageUpload {
872
+ /** An empty form from the platform (`new FormData()`); the library fills it in. */
873
+ form: FormDataLike;
874
+ /** The file: a `Blob`/`File` on the web, `{uri, name, type}` in React Native. */
875
+ file: unknown;
876
+ /** Its name with extension (the backend checks it): jpg, jpeg, png, webp, avif, pdf, mp4, mov, mkv. */
877
+ fileName?: string;
878
+ usage?: ContentImageUsage;
879
+ }
880
+ interface ThemeContentApi {
881
+ /**
882
+ * One section's document, or null if it was never saved. Public.
883
+ * @param themeId The business's theme settings id (`ThemeSetting.id`).
884
+ * @param slug The section, e.g. `header`, `footer`, `home`.
885
+ */
886
+ get<T = unknown>(themeId: number, slug: string): Promise<ThemeContent<T> | null>;
887
+ /**
888
+ * Several sections in one request: one entry per requested slug, in the same order, null
889
+ * for a section never saved (a slug asked twice is answered twice). No slugs: `[]` without a
890
+ * request. Public.
891
+ * @param themeId The business's theme settings id (`ThemeSetting.id`).
892
+ * @param slugs e.g. `['header', 'footer', 'announcement']`. A slug cannot contain a comma.
893
+ * @example const [header, footer] = await client.themeContent.getMany(id, ['header', 'footer']);
894
+ */
895
+ getMany<T = unknown, const S extends readonly string[] = readonly string[]>(themeId: number, slugs: S): Promise<{
896
+ -readonly [K in keyof S]: ThemeContent<T> | null;
897
+ }>;
898
+ /**
899
+ * Merchant: saves a section's whole document, creating it on first save. `platform` must be
900
+ * passed in by the app; it is not guessed (ADR-009).
901
+ */
902
+ update<T>(themeId: number, slug: string, content: {
903
+ platform: ContentPlatform;
904
+ data: T;
905
+ }): Promise<ThemeContent<T>>;
906
+ /** Merchant (manager role): uploads an image or video to use in theme content. */
907
+ uploadImage(upload: ContentImageUpload): Promise<ContentImage>;
908
+ }
909
+
910
+ /**
911
+ * Custom pages: storefront pages the merchant builds from sections (e.g. `/about-us`), with an
912
+ * optional hero at the top. Built in http/customPages.ts.
913
+ */
914
+ /**
915
+ * The hero at the top of a page. `type` names the storefront component that renders it (e.g.
916
+ * `slider`, `banner`); `data` is that component's free-form settings, written by the storefront
917
+ * editor and not checked by the backend: validate it before use. A slider's `data` is its list
918
+ * of slides.
919
+ */
920
+ interface CustomPageHero {
921
+ type: string;
922
+ data: unknown;
923
+ }
924
+ /**
925
+ * One section of a page, exactly as the storefront editor saved it. The backend stores any JSON
926
+ * object here; the editor writes `slug` (which component renders it) and `content` (its
927
+ * settings), sometimes with more keys. Keep unknown keys when saving it back.
928
+ */
929
+ type CustomPageSection = Record<string, unknown>;
930
+ /** An active page, as a shopper sees it. */
931
+ interface CustomPage {
932
+ /** Pass to `customPages.content()` / `updateContent()` to edit it. */
933
+ id: number;
934
+ title: string;
935
+ /** Path without leading or trailing slash, e.g. `about-us` or `company/team`. */
936
+ path: string;
937
+ seoTitle: string | null;
938
+ seoDescription: string | null;
939
+ seoKeywords: string[];
940
+ hero: CustomPageHero | null;
941
+ /** In display order. */
942
+ sections: CustomPageSection[];
943
+ }
944
+ /** A page's editable document (merchant). */
945
+ interface CustomPageContent {
946
+ id: number;
947
+ pageId: number;
948
+ hero: CustomPageHero | null;
949
+ sections: CustomPageSection[];
950
+ createdAt: string;
951
+ updatedAt: string;
952
+ }
953
+
954
+ /** A page's whole editable document, as saved by `customPages.updateContent()`. */
955
+ interface CustomPageContentInput {
956
+ /** In display order. Saved as given; unknown keys are kept. */
957
+ sections: CustomPageSection[];
958
+ /** `null` removes the hero. */
959
+ hero: CustomPageHero | null;
960
+ }
961
+ interface CustomPagesApi {
962
+ /**
963
+ * The business's active page at a storefront path (`about-us`, `/company/team/`), or null when
964
+ * there is none (unknown path or inactive page). Public. Paths are case-sensitive; slashes
965
+ * around and between segments are ignored, as the backend does.
966
+ */
967
+ byPath(path: string): Promise<CustomPage | null>;
968
+ /**
969
+ * Merchant (manager role): a page's editable document, inactive pages included. The backend
970
+ * creates an empty document the first time a page is opened.
971
+ */
972
+ content(pageId: number): Promise<CustomPageContent>;
973
+ /**
974
+ * Merchant (manager role): saves a page's sections and hero together and returns the stored
975
+ * document. Both are always sent: saving one alone could drop a hero that an older editor
976
+ * kept among the sections.
977
+ */
978
+ updateContent(pageId: number, input: CustomPageContentInput): Promise<CustomPageContent>;
979
+ }
980
+
981
+ /** Guest session domain types. Built from wire responses in http/session.ts. */
982
+ /**
983
+ * A guest session: identifies a shopper who is not logged in (guest cart) and is sent with
984
+ * login so the guest cart moves to the customer. Store `token` and return it from
985
+ * `credentials().sessionToken`.
986
+ */
987
+ interface GuestSession {
988
+ id: number;
989
+ token: string;
990
+ }
991
+
992
+ interface SessionApi {
993
+ /**
994
+ * Starts a new guest session. Call it when no session is stored, and again when the client
995
+ * reports `onUnauthorized({ kind: 'session', token })`. The library stores nothing: keep the token
996
+ * and return it from `credentials().sessionToken`. There is no logout endpoint.
997
+ */
998
+ create(): Promise<GuestSession>;
999
+ }
1000
+
1001
+ /** Lookup domain types (address form). Built from wire responses in http/lookup.ts. */
1002
+ interface Country {
1003
+ /** ISO 3166-1 alpha-2, e.g. "IN". Send it as an address's `country`. */
1004
+ code: string;
1005
+ name: string;
1006
+ }
1007
+ interface CountryState {
1008
+ /** Send it as an address's `state`. */
1009
+ id: number;
1010
+ name: string;
1011
+ /** e.g. "GJ". */
1012
+ shortName: string;
1013
+ /** The Indian GST state code, e.g. "24"; a string, keep leading zeros. */
1014
+ code: string;
1015
+ /** Country code, e.g. "IN". */
1016
+ country: string | null;
1017
+ }
1018
+
1019
+ interface LookupApi {
1020
+ /** All countries, or those whose code or name contains `search` (case-insensitive). */
1021
+ countries(search?: string): Promise<Country[]>;
1022
+ /**
1023
+ * The states of a country (`Country.code`). `[]` for a country without states; an unknown
1024
+ * code rejects with a 400 on `country`.
1025
+ */
1026
+ states(countryCode: string): Promise<CountryState[]>;
1027
+ }
1028
+
1029
+ /** Sign-up and login domain types. Built from wire responses in http/auth.ts. */
1030
+
1031
+ /**
1032
+ * How the business lets customers sign up and log in. Only what a storefront's auth forms read
1033
+ * (ADR-023): the per-business dynamic fields are not exposed yet (ADR-018).
1034
+ */
1035
+ interface CustomerSettings {
1036
+ /** Whether the login username is a phone number, an email, or either; also where OTPs go. */
1037
+ authType: 'phone' | 'email' | 'both';
1038
+ /** Offer "sign up as a business" (business name and tax number). */
1039
+ allowBusinessSignup: boolean;
1040
+ /** Sign-up and first login need an OTP before the customer is signed in. */
1041
+ requireVerification: boolean;
1042
+ lastNameRequired: boolean;
1043
+ }
1044
+ /** A signed-in customer. Store `token` and return it from `credentials().customerToken`. */
1045
+ interface SignedInCustomer {
1046
+ customer: CustomerProfile;
1047
+ /** The customer login (`x-customer-token`). Valid for 24 hours; there is no refresh. */
1048
+ token: string;
1049
+ /** Available reward points, or null when the customer has none on record. */
1050
+ rewardPoints: number | null;
1051
+ }
1052
+ /**
1053
+ * The result of `auth.login()` and `auth.register()` (ADR-023). `verification-required`: the
1054
+ * account is not verified yet and the backend has sent an OTP; ask for it and call
1055
+ * `auth.verifyOtp({ customerId, otp })`. No token is given out for an unverified account.
1056
+ */
1057
+ type AuthResult = {
1058
+ status: 'signed-in';
1059
+ session: SignedInCustomer;
1060
+ } | {
1061
+ status: 'verification-required';
1062
+ customerId: number;
1063
+ /**
1064
+ * Where the OTP was sent, to show the shopper and to pass to `auth.sendOtp()` for a new
1065
+ * code: the username entered at login; the phone (else the email) at sign-up.
1066
+ */
1067
+ contact: string | null;
1068
+ };
1069
+
1070
+ interface LoginInput {
1071
+ /** A phone number in E.164 (`+919876543210`) or an email, as `settings().authType` allows. */
1072
+ username: string;
1073
+ password: string;
1074
+ }
1075
+ interface VerifyOtpInput {
1076
+ /** `customerId` from a `verification-required` result. */
1077
+ customerId: number;
1078
+ otp: string;
1079
+ }
1080
+ /**
1081
+ * Signed-out customer flows: settings, sign-up, login, OTP and password reset. The library
1082
+ * stores nothing (ADR-015): keep the token a signed-in result returns. There is no logout
1083
+ * endpoint; logging out is forgetting the token.
1084
+ */
1085
+ interface AuthApi {
1086
+ /** How this business lets customers sign up and log in. */
1087
+ settings(): Promise<CustomerSettings>;
1088
+ /**
1089
+ * Logs in. Needs a guest session (`session.create()`): the backend moves the guest cart to
1090
+ * the customer. An unverified account gets `verification-required` (an OTP was sent).
1091
+ */
1092
+ login(input: LoginInput): Promise<AuthResult>;
1093
+ /** Verifies the account with the OTP and signs the customer in. */
1094
+ verifyOtp(input: VerifyOtpInput): Promise<SignedInCustomer>;
1095
+ /**
1096
+ * Signs up. Needs a guest session, like `login()`. When the business requires verification
1097
+ * the result is `verification-required` and an OTP was sent; otherwise `signed-in`.
1098
+ */
1099
+ register(input: RegisterInput): Promise<AuthResult>;
1100
+ /** Sends an OTP to the account's phone or email: for verification and forgotten passwords. */
1101
+ sendOtp(username: string): Promise<{
1102
+ message: string;
1103
+ }>;
1104
+ /** Sets a new password with an OTP from `sendOtp()`. */
1105
+ resetPassword(input: ResetPasswordInput): Promise<{
1106
+ message: string;
1107
+ }>;
1108
+ }
1109
+ interface RegisterInput {
1110
+ /** Required when `settings().authType` is `email`. */
1111
+ email?: string;
1112
+ /** E.164. Required when `settings().authType` is `phone`. */
1113
+ phone?: string;
1114
+ /** At least 8 characters; the backend also applies Django's password rules. */
1115
+ password: string;
1116
+ type: 'individual' | 'business';
1117
+ /** Required for `individual`. */
1118
+ firstName?: string;
1119
+ /** Required for `individual`. */
1120
+ lastName?: string;
1121
+ /** Required for `business`. */
1122
+ businessName?: string;
1123
+ /** Required for `business`. */
1124
+ taxNumber?: string;
1125
+ referralCode?: string;
1126
+ }
1127
+ interface ResetPasswordInput {
1128
+ username: string;
1129
+ otp: string;
1130
+ newPassword: string;
1131
+ confirmPassword: string;
1132
+ }
1133
+
1134
+ /** Only the given keys are sent; `null` clears a field. */
1135
+ interface UpdateProfileInput {
1136
+ firstName?: string | null;
1137
+ lastName?: string | null;
1138
+ businessName?: string | null;
1139
+ taxNumber?: string | null;
1140
+ }
1141
+ interface ChangePasswordInput {
1142
+ oldPassword: string;
1143
+ newPassword: string;
1144
+ confirmPassword: string;
1145
+ }
1146
+ interface ConfirmEmailChangeInput {
1147
+ /** The new email, as sent to `requestEmailChange()`. */
1148
+ email: string;
1149
+ otp: string;
1150
+ }
1151
+ interface ConfirmPhoneChangeInput {
1152
+ /** The new phone, as sent to `requestPhoneChange()`. */
1153
+ phone: string;
1154
+ otp: string;
1155
+ }
1156
+ /** The logged-in customer's own account (ADR-023: signed-out flows are in `client.auth`). */
1157
+ interface CustomerApi {
1158
+ /** Changes the name, business name or tax number. `id` is `CustomerProfile.id`. */
1159
+ updateProfile(id: number, input: UpdateProfileInput): Promise<CustomerProfile>;
1160
+ changePassword(input: ChangePasswordInput): Promise<{
1161
+ message: string;
1162
+ }>;
1163
+ /** Sends an OTP to the new email; confirm with `confirmEmailChange()`. */
1164
+ requestEmailChange(email: string): Promise<void>;
1165
+ confirmEmailChange(input: ConfirmEmailChangeInput): Promise<{
1166
+ message: string;
1167
+ newEmail: string;
1168
+ }>;
1169
+ /** Sends an OTP to the new phone (E.164); confirm with `confirmPhoneChange()`. */
1170
+ requestPhoneChange(phone: string): Promise<void>;
1171
+ confirmPhoneChange(input: ConfirmPhoneChangeInput): Promise<{
1172
+ message: string;
1173
+ newPhone: string;
1174
+ }>;
1175
+ }
1176
+
1177
+ /** Customer address domain types. Built from wire responses in http/addresses.ts. */
1178
+ /** A saved address of the logged-in customer. */
1179
+ interface Address {
1180
+ /**
1181
+ * The address id. It can change: editing an address an order already used replaces it with
1182
+ * a copy under a new id, so keep the id `addresses.update()` returns.
1183
+ */
1184
+ id: number;
1185
+ addressLine1: string;
1186
+ addressLine2: string;
1187
+ postalCode: string | null;
1188
+ city: string;
1189
+ /** Free text set by the storefront, e.g. "home" or "work". */
1190
+ type: string;
1191
+ /** Country code (`Country.code`). */
1192
+ country: string | null;
1193
+ /** State id (`CountryState.id`). */
1194
+ state: number | null;
1195
+ stateName: string | null;
1196
+ landmark: string;
1197
+ contactNo: string | null;
1198
+ latitude: number | null;
1199
+ longitude: number | null;
1200
+ isDefault: boolean;
1201
+ /** True when an order used it (then editing creates a copy); null when unknown. */
1202
+ isUsed: boolean | null;
1203
+ }
1204
+ /** What `addresses.create()` and `addresses.update()` send. */
1205
+ interface AddressInput {
1206
+ addressLine1: string;
1207
+ addressLine2: string;
1208
+ /** At most 6 characters (the backend's limit). */
1209
+ postalCode?: string;
1210
+ city: string;
1211
+ type: string;
1212
+ landmark: string;
1213
+ country?: string | null;
1214
+ state?: number | null;
1215
+ /** E.164. */
1216
+ contactNo?: string | null;
1217
+ latitude?: number | null;
1218
+ longitude?: number | null;
1219
+ /** Make this the default address (the previous default is unset). Default false. */
1220
+ isDefault?: boolean;
1221
+ }
1222
+
1223
+ interface AddressesApi {
1224
+ /** The logged-in customer's addresses, oldest first. */
1225
+ list(): Promise<Address[]>;
1226
+ create(input: AddressInput): Promise<Address>;
1227
+ /**
1228
+ * Replaces an address. Use the returned address's id from then on: an address an order
1229
+ * already used is kept for that order, and the edit is saved as a copy with a new id.
1230
+ */
1231
+ update(id: number, input: AddressInput): Promise<Address>;
1232
+ /** Deletes an address (kept hidden for orders that used it). */
1233
+ remove(id: number): Promise<void>;
1234
+ }
1235
+
1236
+ interface PlaceOrderOptions {
1237
+ /**
1238
+ * Gift cards to spend, by id (from `promotions.validateGiftCard()`), in this order. Not
1239
+ * allowed for cash on delivery.
1240
+ */
1241
+ giftCardIds?: readonly number[];
1242
+ /** Spend the customer's reward points. Not allowed for cash on delivery. */
1243
+ useRewardPoints?: boolean;
1244
+ }
1245
+ /** A made-to-order product, ordered on its own (one unit) with an advance payment. */
1246
+ interface CreateAdvanceOrderInput {
1247
+ productId: number;
1248
+ /** The variant, when the product has several. */
1249
+ skuId?: number;
1250
+ /** The product's custom fields, as configured by the merchant. */
1251
+ customData?: Record<string, unknown>;
1252
+ /** One of the customer's addresses. */
1253
+ shippingAddressId: number;
1254
+ /** Defaults to the shipping address. */
1255
+ billingAddressId?: number;
1256
+ /** Default `delivery`. */
1257
+ type?: OrderType;
1258
+ }
1259
+ interface CheckoutApi {
1260
+ /** The business's order settings: cash on delivery, cancellation, quantity limits. */
1261
+ settings(): Promise<CheckoutSettings>;
1262
+ /**
1263
+ * Places the order for the cart `cartId`: spends the gift cards and reward points, creates
1264
+ * the payment and takes the cart out of `cart` status. Needs a logged-in customer.
1265
+ *
1266
+ * - `placed`: nothing left to pay online (cash on delivery, or fully covered by gift cards
1267
+ * or points).
1268
+ * - `payment-required`: run the payment SDK for `payment.method`, then confirm through
1269
+ * `client.payments`.
1270
+ *
1271
+ * Gift cards and reward points cannot be combined (the backend over-charges the gift cards
1272
+ * when both are used): this rejects before sending.
1273
+ *
1274
+ * One request per cart at a time: calling again for the same cart while a request is in
1275
+ * flight returns the same promise (a double tap places one order; the first call's options
1276
+ * win). Waits up to 60 s, because the backend books shipments before it answers.
1277
+ *
1278
+ * **Never retried**, by the library or by you: the backend does not detect a repeated
1279
+ * request. After a timeout or network error, read `client.orders.detail(cartId)`: a status
1280
+ * other than `cart` means the order was placed.
1281
+ */
1282
+ placeOrder(cartId: number, options?: PlaceOrderOptions): Promise<PlaceOrderResult>;
1283
+ /**
1284
+ * Creates the order for a made-to-order product and its advance payment in one step, outside
1285
+ * the cart. The advance amount is the product's, set by the merchant: it is never sent. Pay
1286
+ * `payment` like a `payment-required` result of `placeOrder`. Needs a logged-in customer.
1287
+ * Not idempotent: each call creates an order.
1288
+ */
1289
+ createAdvanceOrder(input: CreateAdvanceOrderInput): Promise<AdvanceOrder>;
1290
+ }
1291
+
1292
+ /**
1293
+ * What the backend reported after a payment confirmation. A hint only: read the order for the
1294
+ * real payment status.
1295
+ */
1296
+ interface PaymentConfirmation {
1297
+ status: 'paid' | 'unpaid';
1298
+ }
1299
+
1300
+ /** What Razorpay Checkout's `handler` receives on success. */
1301
+ interface RazorpayResult {
1302
+ razorpayPaymentId: string;
1303
+ razorpayOrderId: string;
1304
+ razorpaySignature: string;
1305
+ }
1306
+ /**
1307
+ * Confirms a payment after the provider's SDK or page finished (the `payment` of a
1308
+ * `payment-required` result). The backend's answer is only a hint: Razorpay's endpoint says
1309
+ * success even when the capture failed, and Stripe's does not check that the intent belongs to
1310
+ * the order. **Read `client.orders.detail(orderId)` afterwards for the real payment status.**
1311
+ *
1312
+ * One request per payment at a time: calling again for the same payment while a confirmation
1313
+ * is in flight (a page refresh, a double tap) returns the same promise.
1314
+ */
1315
+ interface PaymentsApi {
1316
+ /** After the PhonePe redirect (`/payment-redirect?payment_id=…`): asks PhonePe for the status. */
1317
+ confirmPhonePe(paymentId: number): Promise<PaymentConfirmation>;
1318
+ /**
1319
+ * After Stripe Elements' `confirmPayment`. Sends the pending payment's own PaymentIntent, so
1320
+ * an app cannot confirm with another intent. A failed verification is `unpaid`, not an
1321
+ * error. Rejects with a `TypeError`, without a request, when `providerOrderId` is missing.
1322
+ */
1323
+ confirmStripe(payment: Pick<PendingPayment, 'id' | 'providerOrderId'>): Promise<PaymentConfirmation>;
1324
+ /** After Razorpay Checkout succeeded: passes Razorpay's result to the backend. */
1325
+ confirmRazorpay(payment: Pick<PendingPayment, 'id'>, result: RazorpayResult): Promise<void>;
1326
+ /**
1327
+ * Direct (bank) transfer: uploads the customer's screenshot of the transfer. The payment
1328
+ * becomes `unverified` until the merchant checks it. Needs a logged-in customer.
1329
+ * `screenshot`: a `File` or `Blob` on the web, `{ uri, name, type }` in React Native.
1330
+ */
1331
+ submitTransferProof(paymentId: number, screenshot: UploadFile): Promise<void>;
1332
+ }
1333
+
1334
+ /** A file uploaded for a custom-manufacturing form field. */
1335
+ interface UploadedFile {
1336
+ id: number;
1337
+ /** Put this in the field's value. */
1338
+ url: string;
1339
+ }
1340
+
1341
+ interface CustomManufacturingApi {
1342
+ /**
1343
+ * Uploads a file for an image/file field of a custom-manufacturing form; put `url` in the
1344
+ * field's value. `file` is a `File` or `Blob` on the web, `{ uri, name, type }` in React
1345
+ * Native. Needs a logged-in customer.
1346
+ */
1347
+ uploadFile(file: UploadFile, fileName?: string): Promise<UploadedFile>;
1348
+ }
1349
+
1350
+ /** A customer's product subscription (regular deliveries of one or more SKUs). */
1351
+ type SubscriptionStatus = 'created' | 'accepted' | 'paid' | 'in-progress' | 'hold' | 'completed' | 'cancelled' | 'declined';
1352
+ type SubscriptionInterval = 'daily' | 'alternate-day' | 'weekly' | 'monthly';
1353
+ type Weekday = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
1354
+ interface Subscription {
1355
+ id: number;
1356
+ /** e.g. "SUB-0001"; null until the backend assigns it. */
1357
+ number: string | null;
1358
+ status: SubscriptionStatus;
1359
+ interval: SubscriptionInterval | null;
1360
+ /** ISO dates (YYYY-MM-DD). */
1361
+ startDate: string | null;
1362
+ endDate: string | null;
1363
+ /** Delivery days of a weekly subscription; empty for other intervals. */
1364
+ weekdays: Weekday[];
1365
+ /** ISO date whose day of the month is the delivery day (monthly). */
1366
+ monthDay: string | null;
1367
+ addressId: number | null;
1368
+ /** The merchant's decline reason or the customer's cancellation reason. */
1369
+ reason: string | null;
1370
+ createdAt: string;
1371
+ }
1372
+ type DeliveryStatus = 'scheduled' | 'delivered' | 'partially-delivered' | 'un-delivered' | 'invoiced';
1373
+ interface SubscriptionDeliveryItem {
1374
+ id: number;
1375
+ skuId: number;
1376
+ name: string;
1377
+ quantity: number;
1378
+ unit: string | null;
1379
+ price: number | null;
1380
+ status: 'scheduled' | 'delivered' | 'un-delivered';
1381
+ /** The product was removed from the subscription after this delivery. */
1382
+ removed: boolean;
1383
+ }
1384
+ interface SubscriptionDelivery {
1385
+ id: number;
1386
+ /** ISO date. */
1387
+ date: string | null;
1388
+ status: DeliveryStatus;
1389
+ items: SubscriptionDeliveryItem[];
1390
+ }
1391
+ /** A pause period; `endDate` is null while the subscription is still on hold. */
1392
+ interface SubscriptionHold {
1393
+ id: number;
1394
+ startDate: string | null;
1395
+ endDate: string | null;
1396
+ }
1397
+ type InvoicePaymentStatus = 'paid' | 'unpaid' | 'refund' | 'unverified' | 'partially-paid';
1398
+ interface SubscriptionInvoice {
1399
+ id: number;
1400
+ orderId: number | null;
1401
+ invoiceNumber: string | null;
1402
+ invoiceDate: string | null;
1403
+ dueDate: string | null;
1404
+ /** Display text from the backend, e.g. "1 Oct - 31 Oct 2026". */
1405
+ billingPeriod: string;
1406
+ amount: number | null;
1407
+ /** 'unpaid' when no payment was started. */
1408
+ paymentStatus: InvoicePaymentStatus;
1409
+ }
1410
+ /**
1411
+ * The result of `subscriptions.payInvoice`: the invoice is already paid, or there is a payment
1412
+ * to make. Then run the provider's SDK for `payment.method` and confirm through
1413
+ * `client.payments`, as after placing an order.
1414
+ */
1415
+ type InvoicePayment = {
1416
+ status: 'paid';
1417
+ } | {
1418
+ status: 'payment-required';
1419
+ payment: PendingPayment;
1420
+ };
1421
+
1422
+ interface SubscriptionsApi {
1423
+ /** The customer's subscriptions, newest first. Default limit 20. */
1424
+ list(options?: {
1425
+ limit?: number;
1426
+ offset?: number;
1427
+ }): Promise<Page<Subscription>>;
1428
+ /** Throws GrowupsellApiError with status 404 for a subscription that is not the customer's. */
1429
+ detail(id: number): Promise<Subscription>;
1430
+ /** Scheduled and past deliveries, oldest first. */
1431
+ deliveries(id: number): Promise<SubscriptionDelivery[]>;
1432
+ /** Pause periods, newest first. */
1433
+ holds(id: number): Promise<SubscriptionHold[]>;
1434
+ invoices(id: number): Promise<SubscriptionInvoice[]>;
1435
+ /**
1436
+ * Starts (or resumes) paying an invoice, e.g. from the "pay now" link in the invoice email.
1437
+ * Returns `{status: 'paid'}`, or the payment to make: run the SDK for `payment.method`, then
1438
+ * confirm through `client.payments`.
1439
+ *
1440
+ * Not a read, although the backend serves it as a GET: the first call for an invoice
1441
+ * creates a payment and a gateway order; later calls return that same payment until it is
1442
+ * paid. Call it only when the shopper chooses to pay, never to prefetch or poll.
1443
+ *
1444
+ * Every backend failure is a GrowupsellApiError with status 500 and the message
1445
+ * "Something Went Wrong": unknown invoice, the business has no active online payment
1446
+ * method, or the invoice's order is not completed. Show it as "this invoice cannot be paid
1447
+ * online right now" rather than as a crash.
1448
+ */
1449
+ payInvoice(invoiceId: number): Promise<InvoicePayment>;
1450
+ /**
1451
+ * Subscribes the customer to a SKU. The merchant usually has to accept it (status
1452
+ * `created` until then). Inputs the backend would silently accept but never deliver
1453
+ * (weekly without weekdays) are refused before sending.
1454
+ */
1455
+ create(input: CreateSubscriptionInput): Promise<Subscription>;
1456
+ /** Pauses an accepted, paid or running subscription; future deliveries are removed. */
1457
+ hold(id: number): Promise<Subscription>;
1458
+ /** Resumes a paused subscription; deliveries are scheduled again. */
1459
+ resume(id: number): Promise<Subscription>;
1460
+ /** Cancels with the customer's reason (required by the backend). */
1461
+ cancel(id: number, reason: string): Promise<Subscription>;
1462
+ }
1463
+ interface CreateSubscriptionInput {
1464
+ /** The logged-in customer's id: the backend takes it from the body, not from the login. */
1465
+ customerId: number;
1466
+ /** One of the customer's addresses. */
1467
+ addressId: number;
1468
+ skuId: number;
1469
+ /** Per delivery; at least 1. */
1470
+ quantity: number;
1471
+ /**
1472
+ * The SKU's current selling price (from the product detail). The backend stores and
1473
+ * invoices what is sent; it does not look the price up.
1474
+ */
1475
+ price: number;
1476
+ interval: SubscriptionInterval;
1477
+ /** ISO dates (YYYY-MM-DD); the start must be after today (the backend checks). */
1478
+ startDate: string;
1479
+ endDate: string;
1480
+ /** Required for 'weekly'; ignored otherwise. */
1481
+ weekdays?: Weekday[];
1482
+ /** Required for 'monthly': an ISO date whose day is the delivery day. Ignored otherwise. */
1483
+ monthDay?: string;
1484
+ }
1485
+
1486
+ /** Order history domain types. Built from wire responses in http/orders.ts. */
1487
+ /** Open string: the backend may add statuses. A cart is never an order (`cart` never appears). */
1488
+ type OrderStatus = 'created' | 'unpaid' | 'in_progress' | 'accepted' | 'shipping' | 'completed' | 'declined' | 'cancelled' | (string & {});
1489
+ type PaymentStatus = 'paid' | 'unpaid' | 'refund' | 'unverified' | 'partially-paid' | (string & {});
1490
+ /** The address as it was on the order. */
1491
+ interface OrderAddress {
1492
+ id: number;
1493
+ line1: string | null;
1494
+ line2: string | null;
1495
+ landmark: string | null;
1496
+ city: string | null;
1497
+ /** State name, e.g. "Maharashtra". */
1498
+ state: string | null;
1499
+ postalCode: string | null;
1500
+ country: string | null;
1501
+ /** The customer's label for the address, e.g. "home". */
1502
+ type: string | null;
1503
+ }
1504
+ /** A row of the order history (`orders.list()`). */
1505
+ interface OrderListItem {
1506
+ id: number;
1507
+ /** Shown to the shopper; null until the merchant numbers the order. */
1508
+ orderNumber: string | null;
1509
+ /** `delivery`, `cod`, … */
1510
+ type: string;
1511
+ status: OrderStatus;
1512
+ payable: number;
1513
+ placedAt: string;
1514
+ }
1515
+ /** A line of a placed order. */
1516
+ interface OrderLine {
1517
+ id: number;
1518
+ /** Null for lines without a SKU (made by the merchant). */
1519
+ skuId: number | null;
1520
+ productId: number | null;
1521
+ /** Product name with variants. */
1522
+ name: string;
1523
+ photo: string | null;
1524
+ unitPrice: number;
1525
+ quantity: number;
1526
+ lineTotal: number;
1527
+ /** Product/category/brand-level offer amount on this line; 0 otherwise. */
1528
+ offerDiscount: number;
1529
+ taxPercent: number | null;
1530
+ isDigital: boolean;
1531
+ customData: Record<string, unknown> | null;
1532
+ /** The shopper may ask for a replacement (same SKU) of this line. */
1533
+ canReplace: boolean;
1534
+ /** The shopper may ask for an exchange (sibling SKU) of this line. */
1535
+ canExchange: boolean;
1536
+ /** How many units can still be replaced or exchanged. */
1537
+ maxReplaceOrExchangeQuantity: number;
1538
+ }
1539
+ /** One part of the order's payment: online, gift card, reward points, cash on delivery… */
1540
+ interface OrderPaymentEntry {
1541
+ id: number;
1542
+ /** `pg`, `cod`, `giftcard`, `reward_point`, `cash`, `advance`; null if unknown. */
1543
+ type: string | null;
1544
+ amount: number;
1545
+ status: string;
1546
+ method: PaymentMethodType | null;
1547
+ /** Human-readable, e.g. "Razorpay" or "Giftcard". */
1548
+ label: string;
1549
+ }
1550
+ /** A placed order (`orders.detail()`). */
1551
+ interface OrderDetail {
1552
+ id: number;
1553
+ orderNumber: string | null;
1554
+ invoiceNumber: string | null;
1555
+ type: string;
1556
+ status: OrderStatus;
1557
+ /** The merchant's or the shopper's note on the last status change. */
1558
+ statusMessage: string | null;
1559
+ placedAt: string;
1560
+ lines: OrderLine[];
1561
+ subtotal: number;
1562
+ offerDiscount: number;
1563
+ promoDiscount: number;
1564
+ deliveryCharge: number;
1565
+ payable: number;
1566
+ /** Null for digital-only orders. */
1567
+ shippingAddress: OrderAddress | null;
1568
+ billingAddress: OrderAddress | null;
1569
+ payments: OrderPaymentEntry[];
1570
+ hasExchangeOrReplacement: boolean;
1571
+ }
1572
+ /** The light order card of the history list (`orders.summary()`). */
1573
+ interface OrderSummary {
1574
+ id: number;
1575
+ orderNumber: string | null;
1576
+ invoiceNumber: string | null;
1577
+ status: OrderStatus;
1578
+ subtotal: number;
1579
+ payable: number;
1580
+ placedAt: string;
1581
+ items: {
1582
+ name: string;
1583
+ /** Variant labels, e.g. "128 GB, Blue"; null without variants. */
1584
+ variant: string | null;
1585
+ photo: string | null;
1586
+ quantity: number;
1587
+ unitPrice: number;
1588
+ }[];
1589
+ /** Null for digital-only orders. */
1590
+ deliveryAddress: OrderAddress | null;
1591
+ /** Null when the order has no payment record. */
1592
+ payment: {
1593
+ id: number;
1594
+ status: PaymentStatus;
1595
+ amount: number;
1596
+ mode: string | null;
1597
+ } | null;
1598
+ }
1599
+ /** A shipment of some of the order's lines. */
1600
+ interface Shipment {
1601
+ id: number;
1602
+ /** Delivery partner, e.g. "Shiprocket"; "Self" when the merchant delivers. */
1603
+ partner: string;
1604
+ trackingNumber: string | null;
1605
+ /** As the backend labels it, e.g. "Out for delivery". */
1606
+ status: string;
1607
+ createdAt: string;
1608
+ items: {
1609
+ orderLineId: number;
1610
+ name: string;
1611
+ photo: string | null;
1612
+ quantity: number;
1613
+ }[];
1614
+ }
1615
+ /** Tracking for a partner shipment. */
1616
+ interface TrackingInfo {
1617
+ trackingUrl: string | null;
1618
+ /** The partner's tracking data as sent (Shiprocket). Not yet typed: no real response seen. */
1619
+ events: unknown;
1620
+ }
1621
+ type InvoiceFormat = 'a4' | 'thermal_58mm' | 'thermal_108mm';
1622
+
1623
+ interface OrderListOptions {
1624
+ /** Page size. Default 10. */
1625
+ limit?: number;
1626
+ offset?: number;
1627
+ /** Only orders in these statuses. */
1628
+ status?: readonly OrderStatus[];
1629
+ /** Matches the order number. */
1630
+ search?: string;
1631
+ }
1632
+ interface DocumentOptions {
1633
+ /** Paper format. Default `a4`. */
1634
+ format?: InvoiceFormat;
1635
+ }
1636
+ interface CancelCodItemsInput {
1637
+ orderId: number;
1638
+ /** Lines to cancel (`OrderLine.id`) and how many units of each. */
1639
+ items: readonly {
1640
+ orderLineId: number;
1641
+ quantity: number;
1642
+ }[];
1643
+ /** Shown to the merchant as the order's status message. */
1644
+ reason: string;
1645
+ }
1646
+ interface OrdersApi {
1647
+ /** The logged-in customer's placed orders, newest first. */
1648
+ list(options?: OrderListOptions): Promise<Page<OrderListItem>>;
1649
+ /**
1650
+ * A placed order with its lines, payments and addresses. Rejects with a 404 for an order of
1651
+ * another customer. Also the way to check an order after a payment or a timed-out
1652
+ * `placeOrder`: a status other than `cart` means it was placed.
1653
+ */
1654
+ detail(orderId: number): Promise<OrderDetail>;
1655
+ /** The light order card the history list shows. */
1656
+ summary(orderId: number): Promise<OrderSummary>;
1657
+ /** The order's shipments, newest first, each with the lines it carries. */
1658
+ shipments(orderId: number): Promise<Shipment[]>;
1659
+ /**
1660
+ * Partner tracking for a shipment's `trackingNumber`. Rejects with a 400 (no message) for
1661
+ * shipments the merchant delivers itself: only Shiprocket shipments can be tracked.
1662
+ */
1663
+ tracking(trackingNumber: string): Promise<TrackingInfo>;
1664
+ /**
1665
+ * The order's invoice as PDF bytes (`contentType` `application/pdf`), e.g. to open in a tab
1666
+ * or share. Rejects with a 400 "Invoice not generated yet" until the merchant invoices it.
1667
+ */
1668
+ invoice(orderId: number, options?: DocumentOptions): Promise<BinaryResponse>;
1669
+ /** E-mails the invoice to the customer instead. */
1670
+ emailInvoice(orderId: number, options?: DocumentOptions): Promise<void>;
1671
+ /** The credit note of a return (`ReturnRequest.id`) as PDF bytes. */
1672
+ creditNote(returnId: number, options?: DocumentOptions): Promise<BinaryResponse>;
1673
+ /** E-mails the credit note to the customer instead. */
1674
+ emailCreditNote(returnId: number, options?: DocumentOptions): Promise<void>;
1675
+ /**
1676
+ * Cancels some units, or all, of an accepted cash-on-delivery order. Rejects with a 400 for
1677
+ * other orders. Not idempotent: disable the button while it runs. Rejects without a request
1678
+ * (`TypeError`, `RangeError`) when `items` is empty, a quantity is below 1 or `reason` is
1679
+ * blank.
1680
+ */
1681
+ cancelCodItems(input: CancelCodItemsInput): Promise<void>;
1682
+ }
1683
+
1684
+ /** Return and exchange domain types. Built from wire responses in http/returns.ts and
1685
+ * http/exchanges.ts. */
1686
+
1687
+ /** An order line that can still be returned. */
1688
+ interface ReturnableItem {
1689
+ /** The order line (`OrderLine.id`): pass it to `returns.create()`. */
1690
+ orderLineId: number;
1691
+ name: string;
1692
+ photo: string | null;
1693
+ unitPrice: number;
1694
+ /** Units bought minus units already returned (accepted or completed returns). */
1695
+ maxQuantity: number;
1696
+ /** Serial-tracked units of the line not yet returned; empty for untracked products. */
1697
+ unitTrackingIds: number[];
1698
+ }
1699
+ /** Open string: the backend may add statuses. */
1700
+ type ReturnStatus = 'requested' | 'accepted' | 'declined' | 'in_progress' | 'completed' | (string & {});
1701
+ /** A return request of an order. */
1702
+ interface ReturnRequest {
1703
+ /** Pass it to `orders.creditNote()` once the return is refunded. */
1704
+ id: number;
1705
+ status: ReturnStatus;
1706
+ createdAt: string;
1707
+ /** Refund amount after offers and promo codes. */
1708
+ amount: number;
1709
+ items: {
1710
+ /** The return line id. The backend does not send which order line it belongs to. */
1711
+ id: number;
1712
+ name: string;
1713
+ quantity: number;
1714
+ status: ReturnStatus;
1715
+ }[];
1716
+ }
1717
+ /** `replace`: the same SKU again; `exchange`: a sibling SKU of the same product. */
1718
+ type ExchangeType = 'replace' | 'exchange';
1719
+ /**
1720
+ * A SKU the shopper can exchange a bought SKU for: an active, in-stock sibling of the same
1721
+ * product (another variant), never the bought SKU itself.
1722
+ */
1723
+ interface ExchangeTargetSku {
1724
+ skuId: number;
1725
+ skuCode: string;
1726
+ /** Product name with the SKU's variants, e.g. "Iphone 15 (256 GB, Blue)". */
1727
+ name: string;
1728
+ /** The SKU's first photo; null when the product has none. */
1729
+ photo: string | null;
1730
+ price: number;
1731
+ /** Always above 0: SKUs out of stock are not offered. */
1732
+ stockQty: number;
1733
+ }
1734
+ /** An exchange or replacement request of an order. */
1735
+ interface ExchangeListItem {
1736
+ id: number;
1737
+ type: ExchangeType;
1738
+ /** `requested`, `accepted`, `declined`, `in_progress` or `completed`. */
1739
+ status: ReturnStatus;
1740
+ createdAt: string;
1741
+ /** The extra payment when the new SKU costs more; null otherwise. */
1742
+ paymentId: number | null;
1743
+ itemCount: number;
1744
+ }
1745
+ interface ExchangeDetail extends ExchangeListItem {
1746
+ items: {
1747
+ orderLineId: number;
1748
+ /** The originally bought SKU's name with variants; null for lines without a SKU. */
1749
+ name: string | null;
1750
+ /** The SKU the shopper gets (the same SKU for a replacement). */
1751
+ targetSkuId: number | null;
1752
+ quantity: number;
1753
+ /** Positive: the shopper pays the difference; negative: the store refunds it. */
1754
+ priceDifference: number;
1755
+ status: ReturnStatus;
1756
+ }[];
1757
+ }
1758
+ /**
1759
+ * The result of `exchanges.create()`. `payment-required` when the new SKU costs more and the
1760
+ * difference is paid online: collect it like a checkout payment. Cash-on-delivery orders pay
1761
+ * the difference on delivery, so they are `submitted`.
1762
+ */
1763
+ type ExchangeResult = {
1764
+ status: 'submitted';
1765
+ id: number;
1766
+ message: string | null;
1767
+ } | {
1768
+ status: 'payment-required';
1769
+ id: number;
1770
+ payment: PendingPayment;
1771
+ };
1772
+
1773
+ interface CreateReturnInput {
1774
+ orderId: number;
1775
+ /** The shopper's reason, shown to the merchant. */
1776
+ reason: string;
1777
+ items: readonly {
1778
+ /** From `eligibleItems()`. */
1779
+ orderLineId: number;
1780
+ quantity: number;
1781
+ /** For serial-tracked products: which units (from `eligibleItems()`). */
1782
+ unitTrackingIds?: readonly number[];
1783
+ }[];
1784
+ }
1785
+ interface ReturnsApi {
1786
+ /** The order's lines that can still be returned (digital products never can). */
1787
+ eligibleItems(orderId: number): Promise<ReturnableItem[]>;
1788
+ /** The order's return requests, newest first. */
1789
+ list(orderId: number): Promise<ReturnRequest[]>;
1790
+ /**
1791
+ * Requests a return; the merchant accepts or declines it. Not idempotent: disable the button
1792
+ * while it runs. Rejects without a request (`TypeError`, `RangeError`) when `items` is empty,
1793
+ * `reason` is blank or a quantity is below 1. Quantities are not checked against
1794
+ * `eligibleItems()` here, and the backend does not check them either: the caller must.
1795
+ */
1796
+ create(input: CreateReturnInput): Promise<{
1797
+ id: number;
1798
+ }>;
1799
+ }
1800
+
1801
+ interface CreateExchangeInput {
1802
+ orderId: number;
1803
+ type: ExchangeType;
1804
+ /** The shopper's reason for the whole request. */
1805
+ reason?: string;
1806
+ items: readonly {
1807
+ /** A line with `canReplace` / `canExchange` (`OrderLine.id`). */
1808
+ orderLineId: number;
1809
+ /** At most `OrderLine.maxReplaceOrExchangeQuantity`. */
1810
+ quantity: number;
1811
+ /** Required for `exchange`: a sibling SKU of the same product. Ignored for `replace`. */
1812
+ targetSkuId?: number;
1813
+ /** A reason for this line. */
1814
+ reason?: string;
1815
+ /** A photo of the item: a `File`/`Blob` on the web, `{ uri, name, type }` in React Native. */
1816
+ photo?: UploadFile;
1817
+ }[];
1818
+ }
1819
+ interface ExchangesApi {
1820
+ /**
1821
+ * Requests a replacement (same SKU) or an exchange (sibling SKU). When the new SKU costs more
1822
+ * and the order was paid online, resolves `payment-required`: collect the payment like a
1823
+ * checkout payment. Not idempotent: disable the button while it runs. Rejects without a
1824
+ * request (`TypeError`, `RangeError`) when `items` is empty, a quantity is below 1 or an
1825
+ * `exchange` item has no `targetSkuId`. Sent as multipart form data when any item has a photo.
1826
+ */
1827
+ create(input: CreateExchangeInput): Promise<ExchangeResult>;
1828
+ /** The order's exchange and replacement requests, newest first. */
1829
+ list(orderId: number): Promise<ExchangeListItem[]>;
1830
+ /** One request with its lines. Rejects with a 404 for another customer's request. */
1831
+ detail(exchangeId: number): Promise<ExchangeDetail>;
1832
+ /**
1833
+ * The SKUs a bought SKU can be exchanged for (`targetSkuId` in `create`): the product's other
1834
+ * active, in-stock variants. Takes the bought SKU's id (`OrderLine.skuId`), not the order line:
1835
+ * the backend checks no line eligibility, so offer it only for lines with `canExchange`.
1836
+ * Rejects with a 404 when the SKU is unknown or deleted.
1837
+ */
1838
+ targetSkus(skuId: number): Promise<ExchangeTargetSku[]>;
1839
+ }
1840
+
1841
+ interface Client {
1842
+ /** Low-level request; prefer the domain modules below. */
1843
+ request: RequestFn;
1844
+ business: BusinessApi;
1845
+ catalog: CatalogApi;
1846
+ products: ProductsApi;
1847
+ reviews: ReviewsApi;
1848
+ wishlist: WishlistApi;
1849
+ policies: PoliciesApi;
1850
+ faq: FaqApi;
1851
+ themeContent: ThemeContentApi;
1852
+ /** Merchant-built product-page content: sections and blocks. */
1853
+ productPage: ProductPageApi;
1854
+ /** Merchant-built storefront pages (e.g. `/about-us`): hero and sections. */
1855
+ customPages: CustomPagesApi;
1856
+ /** Guest sessions (`x-session-token`). */
1857
+ session: SessionApi;
1858
+ /** Countries and states for the address form. */
1859
+ lookup: LookupApi;
1860
+ /** Signed-out customer flows: settings, sign-up, login, OTP, password reset. */
1861
+ auth: AuthApi;
1862
+ /** The logged-in customer's own account: profile, password, email and phone. */
1863
+ customer: CustomerApi;
1864
+ /** The logged-in customer's saved addresses. */
1865
+ addresses: AddressesApi;
1866
+ /** Checkout: order settings, placing the order, made-to-order advance orders. */
1867
+ checkout: CheckoutApi;
1868
+ /** Confirming payments: PhonePe, Stripe, Razorpay, bank transfer proof. */
1869
+ payments: PaymentsApi;
1870
+ blog: BlogApi;
1871
+ forms: FormsApi;
1872
+ helpdesk: HelpdeskApi;
1873
+ /** Email newsletter sign-up (backend `/subscribers/`). */
1874
+ newsletter: NewsletterApi;
1875
+ /** Web push subscriptions; the app does the browser work and passes the subscription in. */
1876
+ push: PushApi;
1877
+ liveSupport: LiveSupportApi;
1878
+ /** File uploads for custom-manufacturing form fields (logged-in customer). */
1879
+ customManufacturing: CustomManufacturingApi;
1880
+ /** A customer's product subscriptions: deliveries, pauses, invoices. */
1881
+ subscriptions: SubscriptionsApi;
1882
+ /** The shopper's cart (customer or guest session). */
1883
+ cart: CartApi;
1884
+ /** Offers, promo codes, gift cards and reward point settings. */
1885
+ promotions: PromotionsApi;
1886
+ /** The logged-in customer's placed orders: history, shipments, invoices, COD cancellation. */
1887
+ orders: OrdersApi;
1888
+ /** Return requests of the logged-in customer's orders. */
1889
+ returns: ReturnsApi;
1890
+ /** Replacement and exchange requests of the logged-in customer's orders. */
1891
+ exchanges: ExchangesApi;
1892
+ }
1893
+ /**
1894
+ * Creates an HTTP client for the Growupsell backend (ADR-008, ADR-015).
1895
+ * Stateless apart from remembering which rejected token it already reported.
1896
+ */
1897
+ declare function createClient(options: ClientOptions): Client;
1898
+
1899
+ export { createClient as bu };
1900
+ export type { ExchangeDetail as $, AddCartItemInput as A, BankTransferOption as B, CancelCodItemsInput as C, ContentImage as D, ContentImageUpload as E, ContentImageUsage as F, ContentPlatform as G, Country as H, CountryState as I, CreateAdvanceOrderInput as J, CreateExchangeInput as K, CreateReturnInput as L, CreateReviewInput as M, CreateSubscriptionInput as N, Credentials as O, Currency as P, CustomManufacturingApi as Q, CustomPage as R, CustomPageContent as S, CustomPageContentInput as T, CustomPageHero as U, CustomPageSection as V, CustomPagesApi as W, CustomerApi as X, CustomerSettings as Y, DeliveryStatus as Z, DocumentOptions as _, Address as a, ReturnStatus as a$, ExchangeListItem as a0, ExchangeResult as a1, ExchangeTargetSku as a2, ExchangeType as a3, ExchangesApi as a4, Faq as a5, FaqApi as a6, FaqInput as a7, FetchInitLike as a8, FetchLike as a9, OrderPaymentEntry as aA, OrderStatus as aB, OrderSummary as aC, OrdersApi as aD, PaymentConfirmation as aE, PaymentStatus as aF, PaymentsApi as aG, PlaceOrderOptions as aH, PoliciesApi as aI, Policy as aJ, PolicyInput as aK, PolicyType as aL, ProductPageApi as aM, ProductSectionItemTargetInput as aN, ProductsApi as aO, PromotionsApi as aP, PublicForm as aQ, PushApi as aR, PushSubscriptionJson as aS, Query as aT, QueryValue as aU, RazorpayResult as aV, RegisterInput as aW, RequestFn as aX, RequestOptions as aY, ResetPasswordInput as aZ, ReturnRequest as a_, FetchResponseLike as aa, FormDataLike as ab, FormField as ac, FormFieldOption as ad, FormFieldType as ae, FormSummary as af, FormValue as ag, FormsApi as ah, GuestSession as ai, HelpdeskApi as aj, HttpMethod as ak, InquiryInput as al, InvoiceFormat as am, InvoicePayment as an, InvoicePaymentStatus as ao, LiveSupportApi as ap, LiveSupportWidget as aq, LoginInput as ar, LookupApi as as, MoveDirection as at, NewsletterApi as au, OrderAddress as av, OrderDetail as aw, OrderLine as ax, OrderListItem as ay, OrderListOptions as az, AddressInput as b, ReturnableItem as b0, ReturnsApi as b1, ReviewPhoto as b2, ReviewsApi as b3, SessionApi as b4, Shipment as b5, SignedInCustomer as b6, SocialLinks as b7, Subscription as b8, SubscriptionDelivery as b9, SubscriptionDeliveryItem as ba, SubscriptionHold as bb, SubscriptionInterval as bc, SubscriptionInvoice as bd, SubscriptionStatus as be, SubscriptionsApi as bf, ThemeContent as bg, ThemeContentApi as bh, ThemeSettings as bi, ThemeSettingsUpdate as bj, TrackingInfo as bk, UnauthorizedEvent as bl, UpdateProfileInput as bm, UpdateReviewInput as bn, UploadFile as bo, UploadedFile as bp, VerifyOtpInput as bq, Weekday as br, WishlistApi as bs, WishlistItem as bt, AddressesApi as c, AnyFetch as d, AuthApi as e, AuthResult as f, BinaryResponse as g, BlogApi as h, BlogAuthor as i, BlogCategory as j, BlogEditor as k, BlogPost as l, BlogPostSummary as m, Business as n, BusinessApi as o, BusinessMedia as p, BusinessSettings as q, CartAddressesInput as r, CartApi as s, CatalogApi as t, ChangePasswordInput as u, CheckoutApi as v, Client as w, ClientOptions as x, ConfirmEmailChangeInput as y, ConfirmPhoneChangeInput as z };