commerceforai 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `commerceforai/react`: cart and variant-picker hooks that draw nothing, so they fit any design.
3
+ *
4
+ * ```tsx
5
+ * <CartProvider>…</CartProvider> // once, near the root (a client component)
6
+ * const cart = useCart(products); // lines, count, subtotal, add, setQuantity, checkout
7
+ * const picker = useVariantPicker(product); // options, select, variant, inStock
8
+ * ```
9
+ *
10
+ * The cart lives in localStorage (synced across tabs). Checkout posts the lines to your own server
11
+ * endpoint (see `createCheckoutHandler` in `commerceforai`), which holds the API key and creates the
12
+ * Stripe checkout. Prices are never sent from the browser.
13
+ */
14
+ "use client";
15
+ import * as React from "react";
16
+ import { addLine, initialSelection, parseLines, pickVariant, removeLine, resolveCart, setLineQuantity, } from "./cart-core.js";
17
+ export * from "./cart-core.js";
18
+ const EMPTY = [];
19
+ /** A tiny external store: one per storage key, shared by every hook on the page. */
20
+ function createCartStore(key) {
21
+ let lines = EMPTY;
22
+ let loaded = false;
23
+ const listeners = new Set();
24
+ const read = () => {
25
+ try {
26
+ return parseLines(JSON.parse(globalThis.localStorage?.getItem(key) ?? "[]"));
27
+ }
28
+ catch {
29
+ return EMPTY;
30
+ }
31
+ };
32
+ const load = () => { if (!loaded && typeof window !== "undefined") {
33
+ lines = read();
34
+ loaded = true;
35
+ } };
36
+ const emit = () => listeners.forEach((l) => l());
37
+ return {
38
+ get: () => { load(); return lines; },
39
+ set(next) {
40
+ load();
41
+ lines = next;
42
+ try {
43
+ globalThis.localStorage?.setItem(key, JSON.stringify(next));
44
+ }
45
+ catch { /* private mode: in-memory only */ }
46
+ emit();
47
+ },
48
+ subscribe(l) {
49
+ listeners.add(l);
50
+ const onStorage = (e) => { if (e.key === key) {
51
+ lines = read();
52
+ emit();
53
+ } };
54
+ if (typeof window !== "undefined")
55
+ window.addEventListener("storage", onStorage);
56
+ return () => { listeners.delete(l); if (typeof window !== "undefined")
57
+ window.removeEventListener("storage", onStorage); };
58
+ },
59
+ };
60
+ }
61
+ const stores = new Map();
62
+ const storeFor = (key) => { let s = stores.get(key); if (!s) {
63
+ s = createCartStore(key);
64
+ stores.set(key, s);
65
+ } return s; };
66
+ const CartContext = React.createContext(null);
67
+ export function CartProvider({ children, storageKey = "cfa_cart", checkoutEndpoint = "/api/checkout" }) {
68
+ const value = React.useMemo(() => ({ store: storeFor(storageKey), endpoint: checkoutEndpoint }), [storageKey, checkoutEndpoint]);
69
+ return React.createElement(CartContext.Provider, { value }, children);
70
+ }
71
+ /**
72
+ * The cart. Pass the catalog (`products`) to get names, current prices, a subtotal and stock checks;
73
+ * without it you still get lines and a count.
74
+ */
75
+ export function useCart(products) {
76
+ const ctx = React.useContext(CartContext);
77
+ const store = ctx?.store ?? storeFor("cfa_cart");
78
+ const endpoint = ctx?.endpoint ?? "/api/checkout";
79
+ const lines = React.useSyncExternalStore(store.subscribe, store.get, () => EMPTY);
80
+ const [pending, setPending] = React.useState(false);
81
+ const [error, setError] = React.useState(null);
82
+ const resolved = React.useMemo(() => (products ? resolveCart(lines, products) : null), [lines, products]);
83
+ const add = React.useCallback((variantId, quantity = 1, properties) => store.set(addLine(store.get(), variantId, quantity, properties)), [store]);
84
+ const setQuantity = React.useCallback((index, quantity) => store.set(setLineQuantity(store.get(), index, quantity)), [store]);
85
+ const remove = React.useCallback((index) => store.set(removeLine(store.get(), index)), [store]);
86
+ const clear = React.useCallback(() => store.set([]), [store]);
87
+ /** Drop lines whose product is gone, and cap quantities at what's in stock. Needs `products`. */
88
+ const fix = React.useCallback(() => {
89
+ if (!products)
90
+ return;
91
+ const r = resolveCart(store.get(), products);
92
+ store.set(r.lines.map((l) => ({ variant_id: l.variant_id, quantity: l.available === null ? l.quantity : Math.min(l.quantity, l.available), ...(l.properties ? { properties: l.properties } : {}) })).filter((l) => l.quantity > 0));
93
+ }, [products, store]);
94
+ /** Create the checkout on your server and go to Stripe. Resolves with an error instead of throwing. */
95
+ const checkout = React.useCallback(async (opts = {}) => {
96
+ const items = store.get();
97
+ if (!items.length)
98
+ return { ok: false, error: { type: "invalid_request", message: "Your cart is empty." } };
99
+ setPending(true);
100
+ setError(null);
101
+ try {
102
+ const res = await fetch(endpoint, {
103
+ method: "POST", headers: { "content-type": "application/json" },
104
+ body: JSON.stringify({ items, ...(opts.discount_code ? { discount_code: opts.discount_code } : {}) }),
105
+ });
106
+ const body = await res.json().catch(() => null);
107
+ if (!res.ok || !body?.url) {
108
+ const err = body?.error ?? { type: "api_error", message: "Checkout isn't available right now. Please try again." };
109
+ setError(err);
110
+ setPending(false);
111
+ return { ok: false, error: err };
112
+ }
113
+ if (opts.redirect !== false)
114
+ window.location.assign(body.url);
115
+ else
116
+ setPending(false);
117
+ return { ok: true, url: body.url };
118
+ }
119
+ catch {
120
+ const err = { type: "network_error", message: "Couldn't reach checkout. Check your connection and try again." };
121
+ setError(err);
122
+ setPending(false);
123
+ return { ok: false, error: err };
124
+ }
125
+ }, [endpoint, store]);
126
+ return {
127
+ /** Raw lines (ids + quantities). */
128
+ items: lines,
129
+ /** Lines joined to the catalog (when `products` is passed). */
130
+ lines: resolved?.lines ?? [],
131
+ missing: resolved?.missing ?? [],
132
+ count: lines.reduce((n, l) => n + l.quantity, 0),
133
+ subtotal: resolved?.subtotal ?? 0,
134
+ currency: resolved?.currency ?? "usd",
135
+ ready: resolved ? resolved.ready : lines.length > 0,
136
+ add, setQuantity, remove, clear, fix, checkout,
137
+ /** True while creating the checkout / redirecting. */
138
+ pending,
139
+ /** The last checkout error (e.g. out_of_stock with variant_id and available). */
140
+ error,
141
+ };
142
+ }
143
+ /** Selected options → variant, with which choices are still buyable. */
144
+ export function useVariantPicker(product, initial) {
145
+ const [selected, setSelected] = React.useState(() => initial ?? initialSelection(product));
146
+ React.useEffect(() => { setSelected(initial ?? initialSelection(product)); }, [product.id]); // eslint-disable-line react-hooks/exhaustive-deps
147
+ const picked = React.useMemo(() => pickVariant(product, selected), [product, selected]);
148
+ const select = React.useCallback((name, value) => setSelected((s) => ({ ...s, [name]: value })), []);
149
+ return { ...picked, selected, select };
150
+ }
151
+ /**
152
+ * Empty the cart after a successful payment. Call on your success page; it clears only when the URL
153
+ * has the `cfa_checkout` parameter that `createCheckoutHandler` adds to the success URL.
154
+ */
155
+ export function useClearCartOnSuccess(param = "cfa_checkout") {
156
+ const { clear } = useCart();
157
+ React.useEffect(() => {
158
+ if (typeof window !== "undefined" && new URLSearchParams(window.location.search).has(param))
159
+ clear();
160
+ }, [clear, param]);
161
+ }
162
+ export const formatMoney = (cents, currency = "usd", locale = "en-US") => new Intl.NumberFormat(locale, { style: "currency", currency: currency.toUpperCase() }).format(cents / 100);
@@ -0,0 +1,530 @@
1
+ /** All amounts are integers in the currency's minor unit (cents for USD). */
2
+ export type Money = number;
3
+ export interface Inventory {
4
+ tracked: boolean;
5
+ /** Units physically in stock (null when not tracked). */
6
+ on_hand: number | null;
7
+ /** Units held by open checkouts. */
8
+ reserved: number;
9
+ /** on_hand − reserved: what can be sold right now (null = unlimited). */
10
+ available: number | null;
11
+ }
12
+ export interface Variant {
13
+ object: "variant";
14
+ id: string;
15
+ product_id: string;
16
+ name: string;
17
+ sku: string | null;
18
+ options: Record<string, string>;
19
+ price: Money;
20
+ currency: string;
21
+ weight_oz: number | null;
22
+ /** Print-on-demand mapping (null = you ship it yourself). */
23
+ fulfillment: VariantFulfillment | null;
24
+ inventory: Inventory;
25
+ active: boolean;
26
+ created_at: string;
27
+ updated_at: string;
28
+ }
29
+ export interface Product {
30
+ object: "product";
31
+ id: string;
32
+ name: string;
33
+ description: string | null;
34
+ images: string[];
35
+ metadata: Record<string, string>;
36
+ active: boolean;
37
+ /** Default artwork for all variants (a variant's or checkout line's own print_files win). */
38
+ print_files: PrintFile[] | null;
39
+ variants: Variant[];
40
+ created_at: string;
41
+ updated_at: string;
42
+ }
43
+ export interface VariantInput {
44
+ name?: string;
45
+ sku?: string;
46
+ price: Money;
47
+ options?: Record<string, string>;
48
+ /** Units on hand. Omit or `null` for unlimited (not tracked). */
49
+ inventory?: number | null;
50
+ /** Shipping weight in ounces, used to price labels. */
51
+ weight_oz?: number;
52
+ /** Map to a Printful catalog variant to have paid orders printed and shipped by Printful. */
53
+ fulfillment?: VariantFulfillment;
54
+ }
55
+ export interface PrintFile {
56
+ /** Provider placement, e.g. "front", "back", or Printful "embroidery_chest_left". */
57
+ placement: string;
58
+ /** Public https URL of the artwork (PNG recommended). */
59
+ url: string;
60
+ /** Provider file options, e.g. Printful [{ id: "thread_colors", value: ["#FFFFFF"] }]. */
61
+ options?: {
62
+ id: string;
63
+ value: unknown;
64
+ }[];
65
+ }
66
+ /** Print-on-demand mapping: set exactly one of `printful_variant_id`, `printful_sync_variant_id` or `printify`. */
67
+ export interface VariantFulfillment {
68
+ /** Printful catalog variant id (product + color + size), e.g. 4012. */
69
+ printful_variant_id?: number;
70
+ /** A product already set up in your Printful store (sync variant id): no print files needed. */
71
+ printful_sync_variant_id?: number;
72
+ /** Printful item options, e.g. [{ id: "thread_colors_chest_left", value: ["#000000"] }] for embroidery. */
73
+ printful_options?: {
74
+ id: string;
75
+ value: unknown;
76
+ }[];
77
+ /**
78
+ * Printify: for per-order artwork give catalog `blueprint_id` + `print_provider_id` + `variant_id`;
79
+ * to print an existing Printify product give `product_id` + `variant_id`.
80
+ */
81
+ printify?: {
82
+ variant_id: number;
83
+ blueprint_id?: number;
84
+ print_provider_id?: number;
85
+ product_id?: string;
86
+ };
87
+ /** Default artwork; a checkout line's own print_files override it. */
88
+ print_files?: PrintFile[];
89
+ }
90
+ export type ProductCreateParams = {
91
+ name: string;
92
+ /** Default artwork for every variant — artwork is usually per design, not per size. */
93
+ print_files?: PrintFile[];
94
+ description?: string;
95
+ images?: string[];
96
+ metadata?: Record<string, string>;
97
+ } & ({
98
+ variants: VariantInput[];
99
+ price?: never;
100
+ inventory?: never;
101
+ sku?: never;
102
+ } | {
103
+ price: Money;
104
+ inventory?: number | null;
105
+ sku?: string;
106
+ weight_oz?: number;
107
+ variants?: never;
108
+ });
109
+ export interface ProductUpdateParams {
110
+ name?: string;
111
+ print_files?: PrintFile[] | null;
112
+ description?: string | null;
113
+ images?: string[];
114
+ metadata?: Record<string, string>;
115
+ active?: boolean;
116
+ }
117
+ export interface VariantUpdateParams {
118
+ name?: string;
119
+ sku?: string | null;
120
+ price?: Money;
121
+ options?: Record<string, string>;
122
+ active?: boolean;
123
+ weight_oz?: number | null;
124
+ fulfillment?: VariantFulfillment | null;
125
+ }
126
+ export interface CheckoutItem {
127
+ variant_id: string;
128
+ product_id: string;
129
+ product_name: string;
130
+ variant_name: string;
131
+ sku: string | null;
132
+ quantity: number;
133
+ unit_price: Money;
134
+ properties?: Record<string, string>;
135
+ print_files?: PrintFile[];
136
+ }
137
+ export interface Checkout {
138
+ object: "checkout";
139
+ id: string;
140
+ status: "pending" | "open" | "completed" | "expired" | "failed";
141
+ /** Stripe-hosted payment page. Redirect the customer here. */
142
+ url: string | null;
143
+ items: CheckoutItem[];
144
+ currency: string;
145
+ customer_email: string | null;
146
+ metadata: Record<string, string>;
147
+ order_id: string | null;
148
+ expires_at: string;
149
+ created_at: string;
150
+ }
151
+ export interface CheckoutLine {
152
+ variant_id: string;
153
+ quantity: number;
154
+ /** Custom data for this line, e.g. { parks: "Zion, Arches", name: "Riley" }. Up to 20 keys × 500 chars. Shown on Stripe's page. */
155
+ properties?: Record<string, string>;
156
+ /** Per-order artwork for print-on-demand (https URLs). */
157
+ print_files?: PrintFile[];
158
+ }
159
+ export interface CheckoutCreateParams {
160
+ items: CheckoutLine[];
161
+ /** Where Stripe sends the customer after paying. Supports `{CHECKOUT_SESSION_ID}`. */
162
+ success_url?: string;
163
+ cancel_url?: string;
164
+ customer_email?: string;
165
+ /** Default true. */
166
+ collect_shipping_address?: boolean;
167
+ /** ISO country codes. Default ["US"]. */
168
+ shipping_countries?: string[];
169
+ shipping_rates?: {
170
+ name: string;
171
+ amount: Money;
172
+ min_days?: number;
173
+ max_days?: number;
174
+ }[];
175
+ /** How long stock is held, 30–1440. Default 30. */
176
+ expires_in_minutes?: number;
177
+ metadata?: Record<string, string>;
178
+ /** Apply a discount code up front. Without it, shoppers can enter any live code on Stripe's page. */
179
+ discount_code?: string;
180
+ }
181
+ export type OrderStatus = "pending_payment" | "paid" | "fulfilled" | "cancelled" | "refunded";
182
+ export interface Address {
183
+ name?: string | null;
184
+ line1?: string | null;
185
+ line2?: string | null;
186
+ city?: string | null;
187
+ state?: string | null;
188
+ postal_code?: string | null;
189
+ country?: string | null;
190
+ }
191
+ export interface Order {
192
+ object: "order";
193
+ id: string;
194
+ /** Human-friendly, per-store sequential number (e.g. 1001). */
195
+ number: number;
196
+ status: OrderStatus;
197
+ email: string | null;
198
+ customer_name: string | null;
199
+ shipping_address: Address | null;
200
+ items: CheckoutItem[];
201
+ currency: string;
202
+ subtotal: Money;
203
+ shipping: Money;
204
+ tax: Money;
205
+ total: Money;
206
+ /** Taken off by a discount code. */
207
+ discount: Money;
208
+ discount_code: string | null;
209
+ refunded: Money;
210
+ fulfillment: {
211
+ carrier: string | null;
212
+ tracking_number: string | null;
213
+ tracking_url: string | null;
214
+ fulfilled_at: string;
215
+ /** Present when the order shipped in several packages (e.g. Printful + Printify). */
216
+ shipments?: {
217
+ provider?: string;
218
+ carrier: string;
219
+ tracking_number: string;
220
+ tracking_url: string | null;
221
+ }[];
222
+ } | null;
223
+ /** One entry per print-on-demand provider this order was routed to. */
224
+ fulfillment_provider_orders: {
225
+ provider: "printful" | "printify";
226
+ id?: string;
227
+ status: string;
228
+ dashboard_url?: string | null;
229
+ error?: string;
230
+ shipments?: {
231
+ carrier: string;
232
+ tracking_number: string;
233
+ tracking_url: string | null;
234
+ }[];
235
+ }[];
236
+ note: string | null;
237
+ metadata: Record<string, string>;
238
+ checkout_id: string;
239
+ stripe_payment_intent_id: string | null;
240
+ created_at: string;
241
+ updated_at: string;
242
+ }
243
+ export interface Store {
244
+ object: "store";
245
+ id: string;
246
+ name: string;
247
+ currency: string;
248
+ mode: "test" | "live";
249
+ tax_enabled: boolean;
250
+ default_success_url: string | null;
251
+ default_cancel_url: string | null;
252
+ stripe_webhook_url: string | null;
253
+ stripe_webhook_configured: boolean;
254
+ shipping: {
255
+ provider: "easypost" | "shippo";
256
+ ship_from: Address;
257
+ default_parcel: Dimensions | null;
258
+ } | null;
259
+ emails: {
260
+ provider: "resend";
261
+ from: string;
262
+ reply_to: string | null;
263
+ events: string[] | null;
264
+ } | null;
265
+ storefront: StorefrontConfig | null;
266
+ agent_access: boolean;
267
+ /** Shipping every checkout offers unless it passes its own rates. */
268
+ checkout_shipping: {
269
+ countries: string[];
270
+ rates: {
271
+ name: string;
272
+ amount: Money;
273
+ min_days?: number;
274
+ max_days?: number;
275
+ }[];
276
+ free_over: Money | null;
277
+ } | null;
278
+ /** Connected print-on-demand providers; each variant routes to the one it's mapped to. */
279
+ fulfillment: {
280
+ provider: "printful" | "printify";
281
+ auto_confirm: boolean;
282
+ printful_store_id?: string | null;
283
+ printify_shop_id?: string | null;
284
+ }[];
285
+ feeds: {
286
+ acp: string;
287
+ google: string;
288
+ agent: string | null;
289
+ } | null;
290
+ }
291
+ export interface Dimensions {
292
+ length: number;
293
+ width: number;
294
+ height: number;
295
+ }
296
+ export interface Parcel extends Partial<Dimensions> {
297
+ weight_oz?: number;
298
+ }
299
+ export interface ShipFromAddress {
300
+ name?: string;
301
+ company?: string;
302
+ line1: string;
303
+ line2?: string;
304
+ city: string;
305
+ state?: string;
306
+ postal_code: string;
307
+ country: string;
308
+ phone?: string;
309
+ email?: string;
310
+ }
311
+ export interface StorefrontConfig {
312
+ /** Your product page URL with `{handle}` or `{id}`, e.g. "https://acme.com/products/{handle}". */
313
+ product_url_template: string;
314
+ brand?: string;
315
+ seller_url?: string;
316
+ privacy_policy_url?: string;
317
+ terms_url?: string;
318
+ return_policy_url?: string;
319
+ return_window_days?: number;
320
+ }
321
+ export interface ShippingRate {
322
+ object: "shipping_rate";
323
+ id: string;
324
+ carrier: string;
325
+ service: string;
326
+ amount: Money;
327
+ currency: string;
328
+ est_days: number | null;
329
+ return_id: string | null;
330
+ }
331
+ export interface Label {
332
+ object: "label";
333
+ id: string;
334
+ order_id: string;
335
+ return_id: string | null;
336
+ provider: "easypost" | "shippo";
337
+ carrier: string;
338
+ service: string;
339
+ cost: Money;
340
+ currency: string;
341
+ tracking_number: string | null;
342
+ tracking_url: string | null;
343
+ /** PDF/PNG to print. */
344
+ label_url: string;
345
+ created_at: string;
346
+ }
347
+ export interface Return {
348
+ object: "return";
349
+ id: string;
350
+ /** Per-store sequential number, shown to customers as R1, R2, ... */
351
+ number: number;
352
+ status: "requested" | "approved" | "rejected" | "completed";
353
+ order_id: string;
354
+ order_number: number | null;
355
+ email: string | null;
356
+ currency: string | null;
357
+ items: {
358
+ variant_id: string;
359
+ product_name: string;
360
+ variant_name: string;
361
+ quantity: number;
362
+ unit_price: Money;
363
+ }[];
364
+ reason: string | null;
365
+ note: string | null;
366
+ refund_amount: Money | null;
367
+ restocked: boolean;
368
+ label: Label | null;
369
+ created_at: string;
370
+ updated_at: string;
371
+ }
372
+ export interface Email {
373
+ object: "email";
374
+ id: string;
375
+ order_id: string | null;
376
+ template: "order_confirmation" | "order_shipped" | "order_refunded" | "return_approved";
377
+ to: string;
378
+ subject: string;
379
+ status: "pending" | "sent" | "failed";
380
+ attempts: number;
381
+ last_error: string | null;
382
+ created_at: string;
383
+ }
384
+ export type EventType = "order.created" | "order.paid" | "order.fulfilled" | "order.updated" | "order.cancelled" | "order.refunded" | "checkout.expired" | "checkout.abandoned" | "label.created" | "order.shipment_created" | "fulfillment.submitted" | "fulfillment.failed" | "fulfillment.canceled" | "fulfillment.on_hold" | "return.requested" | "return.approved" | "return.rejected" | "return.completed" | "inventory.updated" | "product.created" | "product.updated";
385
+ export interface Event<T = unknown> {
386
+ id: string;
387
+ object: "event";
388
+ type: EventType;
389
+ created_at: string;
390
+ data: T;
391
+ }
392
+ export interface WebhookEndpoint {
393
+ object: "webhook_endpoint";
394
+ id: string;
395
+ url: string;
396
+ events: string[];
397
+ /** Only returned when the endpoint is created. */
398
+ secret?: string;
399
+ created_at?: string;
400
+ }
401
+ export interface List<T> {
402
+ object: "list";
403
+ data: T[];
404
+ has_more: boolean;
405
+ }
406
+ export interface ErrorResponse {
407
+ type: "invalid_request" | "authentication_error" | "not_found" | "conflict" | "out_of_stock" | "stripe_error" | "idempotency_error" | "internal_error" | "network_error";
408
+ message: string;
409
+ param?: string;
410
+ [k: string]: unknown;
411
+ }
412
+ export type Result<T> = {
413
+ data: T;
414
+ error: null;
415
+ } | {
416
+ data: null;
417
+ error: ErrorResponse;
418
+ };
419
+ export interface Collection {
420
+ object: "collection";
421
+ id: string;
422
+ name: string;
423
+ /** URL-safe, unique per store. Use it anywhere an id is accepted. */
424
+ handle: string;
425
+ description: string | null;
426
+ image: string | null;
427
+ position: number;
428
+ product_count: number;
429
+ /** In display order. */
430
+ product_ids: string[];
431
+ created_at: string;
432
+ updated_at: string;
433
+ }
434
+ export interface CollectionParams {
435
+ name?: string;
436
+ handle?: string;
437
+ description?: string | null;
438
+ image?: string | null;
439
+ position?: number;
440
+ product_ids?: string[];
441
+ }
442
+ export interface Customer {
443
+ object: "customer";
444
+ email: string;
445
+ name: string | null;
446
+ /** Paid orders (simulated ones never count). */
447
+ orders: number;
448
+ /** After refunds. */
449
+ total_spent: Money;
450
+ currency: string;
451
+ first_order_at: string;
452
+ last_order_at: string;
453
+ }
454
+ export interface CustomerDetail extends Customer {
455
+ last_shipping_address: Address | null;
456
+ order_history: Order[];
457
+ }
458
+ export interface SalesReport {
459
+ object: "sales_report";
460
+ days: number;
461
+ interval: "day" | "week" | "month";
462
+ currency: string;
463
+ totals: {
464
+ orders: number;
465
+ gross: Money;
466
+ net: Money;
467
+ refunds: Money;
468
+ discounts: Money;
469
+ average_order: Money;
470
+ customers: number;
471
+ returning_customers: number;
472
+ };
473
+ previous: {
474
+ orders: number;
475
+ gross: Money;
476
+ net: Money;
477
+ refunds: Money;
478
+ discounts: Money;
479
+ change_pct: {
480
+ orders: number | null;
481
+ gross: number | null;
482
+ net: number | null;
483
+ };
484
+ };
485
+ series: {
486
+ period: string;
487
+ orders: number;
488
+ gross: Money;
489
+ discounts: Money;
490
+ shipping: Money;
491
+ tax: Money;
492
+ refunds: Money;
493
+ net: Money;
494
+ }[];
495
+ top_products: {
496
+ product_id: string;
497
+ name: string;
498
+ units: number;
499
+ revenue: Money;
500
+ }[];
501
+ }
502
+ export interface Discount {
503
+ object: "discount";
504
+ id: string;
505
+ /** Uppercase; what shoppers type. */
506
+ code: string;
507
+ kind: "percent" | "amount";
508
+ /** 1–100 for percent; cents for amount. */
509
+ value: number;
510
+ currency: string;
511
+ description: string;
512
+ min_subtotal: Money | null;
513
+ max_redemptions: number | null;
514
+ times_used: number;
515
+ starts_at: string | null;
516
+ ends_at: string | null;
517
+ active: boolean;
518
+ status: "live" | "scheduled" | "expired" | "used_up" | "off";
519
+ created_at: string;
520
+ }
521
+ export interface DiscountParams {
522
+ code: string;
523
+ kind: "percent" | "amount";
524
+ value: number;
525
+ min_subtotal?: Money | null;
526
+ max_redemptions?: number | null;
527
+ starts_at?: string | null;
528
+ ends_at?: string | null;
529
+ active?: boolean;
530
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};