@skuio/storefront 0.5.0 → 0.6.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/db/migrations/0007_payments_scope.sql +8 -0
- package/dist/api.d.ts +17 -6
- package/dist/api.js +25 -8
- package/dist/api.js.map +1 -1
- package/dist/{chunk-QWQA4NBG.js → chunk-X7VXO4QU.js} +13 -7
- package/dist/chunk-X7VXO4QU.js.map +1 -0
- package/dist/{config-HNzNP1O_.d.ts → config-C4YCi7m4.d.ts} +8 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mail.d.ts +1 -1
- package/dist/payments/bitcoin.d.ts +1 -1
- package/dist/payments/btcpay.d.ts +1 -1
- package/dist/payments/manual.d.ts +1 -1
- package/dist/payments/paypal.d.ts +1 -1
- package/dist/payments/stripe.d.ts +1 -1
- package/dist/payments.d.ts +1 -1
- package/dist/store.d.ts +22 -2
- package/dist/store.js +35 -2
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-QWQA4NBG.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/routes.ts","../src/config.ts","../src/http.ts"],"sourcesContent":["import type { CategoryNode, Product } from './types.js'\nimport type { Catalog } from './catalog.js'\n\n/**\n * URL patterns. Tokens:\n * :slug product slug\n * :sku product SKU\n * :path category slug path joined with '/', e.g. toyota/tacoma-95-04\n *\n * Defaults are conventional; a migrated store sets them to match its legacy\n * URLs so nothing indexed ever moves. Patterns should start and end with '/'.\n */\nexport interface RoutePatterns {\n product: string\n category: string\n /** Where the full catalogue lives. */\n shop: string\n}\n\nexport const DEFAULT_ROUTES: RoutePatterns = {\n product: '/products/:slug/',\n category: '/collections/:path/',\n shop: '/shop/',\n}\n\nexport interface Routes {\n product(product: Product, catalog: Catalog): string\n category(node: CategoryNode): string\n shop(): string\n patterns: RoutePatterns\n}\n\nexport function defineRoutes(partial: Partial<RoutePatterns> = {}): Routes {\n const patterns: RoutePatterns = { ...DEFAULT_ROUTES, ...partial }\n\n const fill = (pattern: string, vars: Record<string, string>) =>\n pattern.replace(/:(\\w+)/g, (_, key: string) => {\n const v = vars[key]\n if (v === undefined) throw new Error(`Route pattern \"${pattern}\" needs :${key}`)\n return v\n })\n\n return {\n patterns,\n product(product, catalog) {\n const crumbs = catalog.breadcrumbs(product)\n return fill(patterns.product, {\n slug: product.slug,\n sku: encodeURIComponent(product.sku),\n path: crumbs.map((c) => c.slug).join('/'),\n })\n },\n category(node) {\n return fill(patterns.category, { path: node.path.join('/') })\n },\n shop() {\n return patterns.shop\n },\n }\n}\n","import { defineRoutes, type RoutePatterns, type Routes } from './routes'\nimport type { ShippingOption } from './shipping'\nimport { validateDiscounts, validateQuantityDiscounts, type DiscountCode, type PartnerProfile, type QuantityDiscount, type ReferralSettings } from './discounts'\n\nexport interface StorefrontInput {\n /** Store name, used in titles, emails and the Store API health check. */\n name: string\n /** Canonical public origin, e.g. https://www.example.com (no trailing slash). */\n url: string\n /** ISO 4217, lower case: 'usd'. */\n currency: string\n /** BCP 47 for formatting: 'en-US'. */\n locale?: string\n routes?: Partial<RoutePatterns>\n /** At least one option. Options that name `countries` are only offered to those countries. */\n shipping: ShippingOption[]\n /** ISO 3166-1 alpha-2 codes the store ships to. */\n shipToCountries: string[]\n contact?: {\n phone?: string\n phoneE164?: string\n email?: string\n }\n checkout?: {\n /** Per line. */\n maxQuantity?: number\n allowPromotionCodes?: boolean\n collectPhone?: boolean\n }\n inventory?: {\n /** `available <= lowStockThreshold` shows as low stock. */\n lowStockThreshold?: number\n }\n orders?: {\n /** Prefix for human order numbers: 'VLD-' → VLD-1001. */\n numberPrefix?: string\n }\n /**\n * Discount codes the store prices itself (see discounts.ts). A code with a\n * `partner` attributes the order to that partner and snapshots the\n * commission owed. Prefer these over processor promotion codes: they work\n * under every payment adapter and land on the order.\n */\n discounts?: DiscountCode[]\n /** Percent off by units in the order (see discounts.ts). Never stacks with a code: the larger saving wins. */\n quantityDiscounts?: QuantityDiscount[]\n /** Profiles for the partners codes name: welcome banner + portal sign-in. */\n partners?: PartnerProfile[]\n /** `?code=` links that apply a code for a while. Defaults: param \"code\", 30 days. */\n referral?: Partial<ReferralSettings>\n}\n\nexport interface StorefrontConfig extends Omit<StorefrontInput, 'routes' | 'locale' | 'checkout' | 'inventory' | 'orders' | 'discounts' | 'quantityDiscounts' | 'partners' | 'referral'> {\n locale: string\n routes: Routes\n checkout: {\n maxQuantity: number\n allowPromotionCodes: boolean\n collectPhone: boolean\n }\n inventory: {\n lowStockThreshold: number\n }\n orders: {\n numberPrefix: string\n }\n discounts: DiscountCode[]\n quantityDiscounts: QuantityDiscount[]\n partners: PartnerProfile[]\n referral: ReferralSettings\n}\n\n/**\n * Validate and fill in a store's configuration. Every store has exactly one\n * of these, exported from storefront.config.ts.\n */\nexport function defineStorefront(input: StorefrontInput): StorefrontConfig {\n if (!/^https?:\\/\\//.test(input.url)) throw new Error(`storefront.url must be absolute, got ${input.url}`)\n if (input.url.endsWith('/')) throw new Error('storefront.url must not end with a slash')\n if (!input.shipping.length) throw new Error('storefront.shipping needs at least one option')\n if (!input.shipToCountries.length) throw new Error('storefront.shipToCountries needs at least one country')\n validateDiscounts(input.discounts ?? [], input.currency)\n validateQuantityDiscounts(input.quantityDiscounts ?? [])\n const partnerIds = new Set<string>()\n for (const p of input.partners ?? []) {\n if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(p.id)) throw new Error(`partner id \"${p.id}\" must be lower-case letters, digits and dashes`)\n if (partnerIds.has(p.id)) throw new Error(`partner ${p.id} is defined twice`)\n if (p.email && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(p.email)) throw new Error(`partner ${p.id}: email is not an address`)\n partnerIds.add(p.id)\n }\n const referralParam = input.referral?.param ?? 'code'\n if (!/^[a-z][a-z0-9_]{0,31}$/.test(referralParam)) throw new Error(`storefront.referral.param \"${referralParam}\" must be a short lower-case query parameter`)\n\n return {\n ...input,\n locale: input.locale ?? 'en-US',\n routes: defineRoutes(input.routes),\n checkout: {\n maxQuantity: input.checkout?.maxQuantity ?? 10,\n allowPromotionCodes: input.checkout?.allowPromotionCodes ?? true,\n collectPhone: input.checkout?.collectPhone ?? true,\n },\n inventory: {\n lowStockThreshold: input.inventory?.lowStockThreshold ?? 3,\n },\n orders: {\n numberPrefix: input.orders?.numberPrefix ?? '',\n },\n discounts: input.discounts ?? [],\n quantityDiscounts: input.quantityDiscounts ?? [],\n partners: input.partners ?? [],\n referral: {\n param: referralParam,\n cookieDays: input.referral?.cookieDays ?? 30,\n },\n }\n}\n","/**\n * Framework-agnostic request helpers for browser form endpoints. The store\n * disables Astro's global origin check so the bearer-authenticated Store API\n * (machine traffic, no Origin header) is never rejected; these keep the\n * browser form POSTs same-origin instead.\n */\n\n/**\n * True when the request is a same-origin browser navigation/form post, or a\n * non-browser call (no Origin, no Sec-Fetch-Site — e.g. the Store API). False\n * only when a cross-site browser context is explicit. Use it to guard mutating\n * form handlers: `if (!sameOrigin(request, url)) return reject`.\n */\nexport function sameOrigin(request: Request, url: URL): boolean {\n const site = request.headers.get('sec-fetch-site')\n if (site && site !== 'same-origin' && site !== 'none') return false\n const origin = request.headers.get('origin')\n if (origin && origin !== url.origin) return false\n return true\n}\n\n/** The caller's address, from Cloudflare's header first, then a proxy's. */\nexport function clientAddress(request: Request): string {\n return request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/routes.ts","../src/config.ts","../src/http.ts"],"sourcesContent":["import type { CategoryNode, Product } from './types.js'\nimport type { Catalog } from './catalog.js'\n\n/**\n * URL patterns. Tokens:\n * :slug product slug\n * :sku product SKU\n * :path category slug path joined with '/', e.g. toyota/tacoma-95-04\n *\n * Defaults are conventional; a migrated store sets them to match its legacy\n * URLs so nothing indexed ever moves. Patterns should start and end with '/'.\n */\nexport interface RoutePatterns {\n product: string\n category: string\n /** Where the full catalogue lives. */\n shop: string\n}\n\nexport const DEFAULT_ROUTES: RoutePatterns = {\n product: '/products/:slug/',\n category: '/collections/:path/',\n shop: '/shop/',\n}\n\nexport interface Routes {\n product(product: Product, catalog: Catalog): string\n category(node: CategoryNode): string\n shop(): string\n patterns: RoutePatterns\n}\n\nexport function defineRoutes(partial: Partial<RoutePatterns> = {}): Routes {\n const patterns: RoutePatterns = { ...DEFAULT_ROUTES, ...partial }\n\n const fill = (pattern: string, vars: Record<string, string>) =>\n pattern.replace(/:(\\w+)/g, (_, key: string) => {\n const v = vars[key]\n if (v === undefined) throw new Error(`Route pattern \"${pattern}\" needs :${key}`)\n return v\n })\n\n return {\n patterns,\n product(product, catalog) {\n const crumbs = catalog.breadcrumbs(product)\n return fill(patterns.product, {\n slug: product.slug,\n sku: encodeURIComponent(product.sku),\n path: crumbs.map((c) => c.slug).join('/'),\n })\n },\n category(node) {\n return fill(patterns.category, { path: node.path.join('/') })\n },\n shop() {\n return patterns.shop\n },\n }\n}\n","import { defineRoutes, type RoutePatterns, type Routes } from './routes'\nimport type { ShippingOption } from './shipping'\nimport { validateDiscounts, validateQuantityDiscounts, type DiscountCode, type PartnerProfile, type QuantityDiscount, type ReferralSettings } from './discounts'\n\nexport interface StorefrontInput {\n /** Store name, used in titles, emails and the Store API health check. */\n name: string\n /** Canonical public origin, e.g. https://www.example.com (no trailing slash). */\n url: string\n /** ISO 4217, lower case: 'usd'. */\n currency: string\n /** BCP 47 for formatting: 'en-US'. */\n locale?: string\n routes?: Partial<RoutePatterns>\n /** At least one option. Options that name `countries` are only offered to those countries. */\n shipping: ShippingOption[]\n /** ISO 3166-1 alpha-2 codes the store ships to. */\n shipToCountries: string[]\n contact?: {\n phone?: string\n phoneE164?: string\n email?: string\n }\n checkout?: {\n /** Per line. */\n maxQuantity?: number\n allowPromotionCodes?: boolean\n collectPhone?: boolean\n }\n inventory?: {\n /** `available <= lowStockThreshold` shows as low stock. */\n lowStockThreshold?: number\n }\n orders?: {\n /** Prefix for human order numbers: 'VLD-' → VLD-1001. Shown on pages, emails and receipts, and sent to SKU.io. */\n numberPrefix?: string\n /**\n * The lowest number a new order may take (default 1001). New orders take the next number above the highest\n * order on file, or this, whichever is larger: set it above numbers an earlier platform used (4200 after\n * WooCommerce's VLD4154) so a new order never repeats an old one.\n */\n numberStart?: number\n }\n /**\n * Discount codes the store prices itself (see discounts.ts). A code with a\n * `partner` attributes the order to that partner and snapshots the\n * commission owed. Prefer these over processor promotion codes: they work\n * under every payment adapter and land on the order.\n */\n discounts?: DiscountCode[]\n /** Percent off by units in the order (see discounts.ts). Never stacks with a code: the larger saving wins. */\n quantityDiscounts?: QuantityDiscount[]\n /** Profiles for the partners codes name: welcome banner + portal sign-in. */\n partners?: PartnerProfile[]\n /** `?code=` links that apply a code for a while. Defaults: param \"code\", 30 days. */\n referral?: Partial<ReferralSettings>\n}\n\nexport interface StorefrontConfig extends Omit<StorefrontInput, 'routes' | 'locale' | 'checkout' | 'inventory' | 'orders' | 'discounts' | 'quantityDiscounts' | 'partners' | 'referral'> {\n locale: string\n routes: Routes\n checkout: {\n maxQuantity: number\n allowPromotionCodes: boolean\n collectPhone: boolean\n }\n inventory: {\n lowStockThreshold: number\n }\n orders: {\n numberPrefix: string\n numberStart: number\n }\n discounts: DiscountCode[]\n quantityDiscounts: QuantityDiscount[]\n partners: PartnerProfile[]\n referral: ReferralSettings\n}\n\n/**\n * Validate and fill in a store's configuration. Every store has exactly one\n * of these, exported from storefront.config.ts.\n */\nexport function defineStorefront(input: StorefrontInput): StorefrontConfig {\n if (!/^https?:\\/\\//.test(input.url)) throw new Error(`storefront.url must be absolute, got ${input.url}`)\n if (input.url.endsWith('/')) throw new Error('storefront.url must not end with a slash')\n if (!input.shipping.length) throw new Error('storefront.shipping needs at least one option')\n if (!input.shipToCountries.length) throw new Error('storefront.shipToCountries needs at least one country')\n validateDiscounts(input.discounts ?? [], input.currency)\n validateQuantityDiscounts(input.quantityDiscounts ?? [])\n const partnerIds = new Set<string>()\n for (const p of input.partners ?? []) {\n if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(p.id)) throw new Error(`partner id \"${p.id}\" must be lower-case letters, digits and dashes`)\n if (partnerIds.has(p.id)) throw new Error(`partner ${p.id} is defined twice`)\n if (p.email && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(p.email)) throw new Error(`partner ${p.id}: email is not an address`)\n partnerIds.add(p.id)\n }\n const referralParam = input.referral?.param ?? 'code'\n if (!/^[a-z][a-z0-9_]{0,31}$/.test(referralParam)) throw new Error(`storefront.referral.param \"${referralParam}\" must be a short lower-case query parameter`)\n\n return {\n ...input,\n locale: input.locale ?? 'en-US',\n routes: defineRoutes(input.routes),\n checkout: {\n maxQuantity: input.checkout?.maxQuantity ?? 10,\n allowPromotionCodes: input.checkout?.allowPromotionCodes ?? true,\n collectPhone: input.checkout?.collectPhone ?? true,\n },\n inventory: {\n lowStockThreshold: input.inventory?.lowStockThreshold ?? 3,\n },\n orders: {\n numberPrefix: input.orders?.numberPrefix ?? '',\n numberStart: Math.max(1, Math.trunc(input.orders?.numberStart ?? 1001)),\n },\n discounts: input.discounts ?? [],\n quantityDiscounts: input.quantityDiscounts ?? [],\n partners: input.partners ?? [],\n referral: {\n param: referralParam,\n cookieDays: input.referral?.cookieDays ?? 30,\n },\n }\n}\n","/**\n * Framework-agnostic request helpers for browser form endpoints. The store\n * disables Astro's global origin check so the bearer-authenticated Store API\n * (machine traffic, no Origin header) is never rejected; these keep the\n * browser form POSTs same-origin instead.\n */\n\n/**\n * True when the request is a same-origin browser navigation/form post, or a\n * non-browser call (no Origin, no Sec-Fetch-Site — e.g. the Store API). False\n * only when a cross-site browser context is explicit. Use it to guard mutating\n * form handlers: `if (!sameOrigin(request, url)) return reject`.\n */\nexport function sameOrigin(request: Request, url: URL): boolean {\n const site = request.headers.get('sec-fetch-site')\n if (site && site !== 'same-origin' && site !== 'none') return false\n const origin = request.headers.get('origin')\n if (origin && origin !== url.origin) return false\n return true\n}\n\n/** The caller's address, from Cloudflare's header first, then a proxy's. */\nexport function clientAddress(request: Request): string {\n return request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'\n}\n"],"mappings":"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","names":[]}
|
package/dist/mail.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { M as MailAdapter, a as MailMessage } from './types-CWreCyxc.js';
|
|
2
|
-
import { w as StorefrontConfig, O as Order, F as Fulfillment } from './config-
|
|
2
|
+
import { w as StorefrontConfig, O as Order, F as Fulfillment } from './config-C4YCi7m4.js';
|
|
3
3
|
export { ConsoleMailOptions, consoleMail } from './mail/console.js';
|
|
4
4
|
export { MailSendError, SendgridMailOptions, sendgridMail } from './mail/sendgrid.js';
|
|
5
5
|
export { CloudflareMailOptions, CloudflareSendEmailBinding, cloudflareMail } from './mail/cloudflare.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Stripe from 'stripe';
|
|
2
|
-
import { O as Order } from '../config-
|
|
2
|
+
import { O as Order } from '../config-C4YCi7m4.js';
|
|
3
3
|
import { PaymentContext, PaymentEvent, PaymentAdapter } from '../payments.js';
|
|
4
4
|
import '../store.js';
|
|
5
5
|
import '../driver-Cr4l6GgG.js';
|
package/dist/payments.d.ts
CHANGED
package/dist/store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { w as StorefrontConfig, C as Catalog, M as Money, a as Availability, j as OrderEvent, O as Order, F as Fulfillment, u as ShippingOption, g as DiscountCode, Q as QuantityDiscount, A as Address, P as PartnerProfile, q as PublicDiscount, e as Customer } from './config-
|
|
1
|
+
import { w as StorefrontConfig, C as Catalog, M as Money, a as Availability, j as OrderEvent, O as Order, m as OrderStatus, F as Fulfillment, u as ShippingOption, g as DiscountCode, Q as QuantityDiscount, A as Address, P as PartnerProfile, q as PublicDiscount, e as Customer } from './config-C4YCi7m4.js';
|
|
2
2
|
import { D as Database, a as DbStatement } from './driver-Cr4l6GgG.js';
|
|
3
3
|
export { b as DbValue, c as bindable } from './driver-Cr4l6GgG.js';
|
|
4
4
|
import { M as MailAdapter } from './types-CWreCyxc.js';
|
|
@@ -260,6 +260,24 @@ declare class OrdersService {
|
|
|
260
260
|
limit?: number;
|
|
261
261
|
includeUnpaid?: boolean;
|
|
262
262
|
}): Promise<Order[]>;
|
|
263
|
+
/**
|
|
264
|
+
* The owner's order list: newest first, filtered and paged, with the total for paging. `query` matches the
|
|
265
|
+
* buyer's email or name, the order number with or without the store's prefix ("VLD4200", "4200", "#4200"), or an
|
|
266
|
+
* imported order's original number. Orders copied from an earlier platform carry `metadata.imported_from` (e.g.
|
|
267
|
+
* "woocommerce") and `metadata.original_number`; `source` separates them from the store's own. Unpaid processor
|
|
268
|
+
* checkouts (abandoned) are left out unless `includeUnpaid`.
|
|
269
|
+
*/
|
|
270
|
+
search(opts?: {
|
|
271
|
+
query?: string;
|
|
272
|
+
status?: OrderStatus;
|
|
273
|
+
source?: 'store' | 'imported';
|
|
274
|
+
includeUnpaid?: boolean;
|
|
275
|
+
limit?: number;
|
|
276
|
+
offset?: number;
|
|
277
|
+
}): Promise<{
|
|
278
|
+
orders: Order[];
|
|
279
|
+
total: number;
|
|
280
|
+
}>;
|
|
263
281
|
count(): Promise<number>;
|
|
264
282
|
/**
|
|
265
283
|
* Orders changed since a watermark, oldest first, keyset-paged on
|
|
@@ -392,7 +410,7 @@ declare class CheckoutService {
|
|
|
392
410
|
|
|
393
411
|
declare const CONNECT_CODE_MINUTES = 10;
|
|
394
412
|
declare const API_KEY_PREFIX = "sk_store_";
|
|
395
|
-
declare const CONNECT_SCOPES: readonly ["orders", "fulfillments", "inventory", "catalog"];
|
|
413
|
+
declare const CONNECT_SCOPES: readonly ["orders", "payments", "fulfillments", "inventory", "catalog"];
|
|
396
414
|
type ConnectScope = (typeof CONNECT_SCOPES)[number];
|
|
397
415
|
declare const SCOPE_DESCRIPTIONS: Record<ConnectScope, string>;
|
|
398
416
|
interface Connection {
|
|
@@ -418,6 +436,8 @@ interface AuthorizeParams {
|
|
|
418
436
|
/** A validated connect request, ready to show the owner. */
|
|
419
437
|
interface AuthorizeRequest {
|
|
420
438
|
clientId: string;
|
|
439
|
+
/** Display name: "SKU.io" for skuio, else the client id. */
|
|
440
|
+
clientName: string;
|
|
421
441
|
redirectUri: string;
|
|
422
442
|
redirectHost: string;
|
|
423
443
|
state: string;
|
package/dist/store.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
randomToken,
|
|
37
37
|
secretsEqual,
|
|
38
38
|
sha256Hex
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-X7VXO4QU.js";
|
|
40
40
|
import {
|
|
41
41
|
magicLinkMail,
|
|
42
42
|
merchantNewOrderMail,
|
|
@@ -690,10 +690,11 @@ var CheckoutService = class {
|
|
|
690
690
|
`INSERT INTO orders (id, number, status, payment_status, currency, subtotal, shipping, tax, discount, total, customer_id, email,
|
|
691
691
|
shipping_address_json, billing_address_json, shipping_method_code, shipping_method_name, notes, metadata_json, access_key, created_at, updated_at,
|
|
692
692
|
discount_code, discount_partner, partner_commission)
|
|
693
|
-
VALUES (?, (SELECT COALESCE(MAX(number),
|
|
693
|
+
VALUES (?, (SELECT MAX(COALESCE(MAX(number), 0) + 1, ?) FROM orders), 'pending_payment', 'unpaid', ?, ?, ?, 0, ?, ?, (SELECT id FROM customers WHERE email = ?), ?,
|
|
694
694
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
695
695
|
).bind(
|
|
696
696
|
orderId,
|
|
697
|
+
this.ctx.config.orders.numberStart,
|
|
697
698
|
quote.currency,
|
|
698
699
|
quote.subtotal,
|
|
699
700
|
quote.shipping,
|
|
@@ -1078,6 +1079,38 @@ var OrdersService = class {
|
|
|
1078
1079
|
const where = opts.includeUnpaid ? "" : ` WHERE o.payment_status != 'unpaid' OR COALESCE(o.payment_provider, 'manual') = 'manual'`;
|
|
1079
1080
|
return this.hydrate(`${ORDER_SELECT}${where} ORDER BY o.created_at DESC, o.number DESC LIMIT ?`, [limit]);
|
|
1080
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* The owner's order list: newest first, filtered and paged, with the total for paging. `query` matches the
|
|
1084
|
+
* buyer's email or name, the order number with or without the store's prefix ("VLD4200", "4200", "#4200"), or an
|
|
1085
|
+
* imported order's original number. Orders copied from an earlier platform carry `metadata.imported_from` (e.g.
|
|
1086
|
+
* "woocommerce") and `metadata.original_number`; `source` separates them from the store's own. Unpaid processor
|
|
1087
|
+
* checkouts (abandoned) are left out unless `includeUnpaid`.
|
|
1088
|
+
*/
|
|
1089
|
+
async search(opts = {}) {
|
|
1090
|
+
const where = [];
|
|
1091
|
+
const args = [];
|
|
1092
|
+
const query = opts.query?.trim();
|
|
1093
|
+
if (query) {
|
|
1094
|
+
const prefix = this.ctx.config.orders.numberPrefix.toLowerCase();
|
|
1095
|
+
let number = query.replace(/^#/, "").trim();
|
|
1096
|
+
if (prefix && number.toLowerCase().startsWith(prefix)) number = number.slice(prefix.length);
|
|
1097
|
+
where.push(`(o.email LIKE ? OR c.name LIKE ? OR CAST(o.number AS TEXT) = ? OR json_extract(o.metadata_json, '$.original_number') LIKE ?)`);
|
|
1098
|
+
args.push(`%${query.toLowerCase()}%`, `%${query}%`, number.trim(), `%${query}%`);
|
|
1099
|
+
}
|
|
1100
|
+
if (opts.status) {
|
|
1101
|
+
where.push("o.status = ?");
|
|
1102
|
+
args.push(opts.status);
|
|
1103
|
+
}
|
|
1104
|
+
if (opts.source === "imported") where.push(`json_extract(o.metadata_json, '$.imported_from') IS NOT NULL`);
|
|
1105
|
+
if (opts.source === "store") where.push(`json_extract(o.metadata_json, '$.imported_from') IS NULL`);
|
|
1106
|
+
if (!opts.includeUnpaid) where.push(`(o.payment_status != 'unpaid' OR COALESCE(o.payment_provider, 'manual') = 'manual')`);
|
|
1107
|
+
const clause = where.length ? ` WHERE ${where.join(" AND ")}` : "";
|
|
1108
|
+
const limit = Math.min(Math.max(Math.trunc(opts.limit ?? 50), 1), 200);
|
|
1109
|
+
const offset = Math.max(Math.trunc(opts.offset ?? 0), 0);
|
|
1110
|
+
const counted = await this.ctx.db.prepare(`SELECT COUNT(*) AS n FROM orders o JOIN customers c ON c.id = o.customer_id${clause}`).bind(...args).first();
|
|
1111
|
+
const orders = await this.hydrate(`${ORDER_SELECT}${clause} ORDER BY o.created_at DESC, o.number DESC LIMIT ? OFFSET ?`, [...args, limit, offset]);
|
|
1112
|
+
return { orders, total: counted?.n ?? 0 };
|
|
1113
|
+
}
|
|
1081
1114
|
async count() {
|
|
1082
1115
|
const row = await this.ctx.db.prepare(`SELECT COUNT(*) AS n FROM orders`).first();
|
|
1083
1116
|
return row?.n ?? 0;
|