@develemit/commerce-server 0.4.0 → 0.7.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/CHANGELOG.md ADDED
@@ -0,0 +1,112 @@
1
+ ## 0.7.0 (2026-11-04)
2
+
3
+ ### Features
4
+
5
+ - `devCheckout` orders now complete with a buyer email (`dev-buyer@example.com`), a sample shipping address and the session's first shipping rate, so `onOrderPaid` hooks see real fields. New `devCheckout: { buyer }` option (object or `(sessionId) => buyer`) overrides them; `DevBuyer` and `DevBuyerSetting` types are exported.
6
+ - Drafts can be incomplete: migration `0008` makes `price_cents` and `category` nullable and gives `name` a `''` default. Publishing (`PATCH` to `status: 'active'`) with a missing name, price, category or photo answers 422 `PRODUCT_INCOMPLETE` with `missing`. New `admin.requirePhotoToPublish` option (default `true`).
7
+ - `GET /admin/products/counts` returns `{ all, live, draft, sold, archived, incompleteDrafts }`.
8
+ - Nameless drafts get a temporary `draft-<id>` slug, replaced once by the name slug the first time a name is set, then frozen.
9
+ - **Changed:** `GET /admin/products` pages newest first (`created_at` then `slug`, descending); cursors from before this change page in the wrong direction. New `excludeArchived=true` query param leaves archived products out, and `incomplete=true` keeps only drafts missing a name, price or category (the set `incompleteDrafts` counts).
10
+ - The public catalog skips active rows missing a price or category.
11
+
12
+ ### Features
13
+
14
+ - `PaymentGateway.preflight?()` is new and optional; `commercePlugin` awaits it during registration. `createStripeGateway` implements it, so a host with Stripe configured but the optional peer `stripe` missing fails at startup with `Run: pnpm add stripe` rather than at the first checkout.
15
+
16
+ ### Fixes
17
+
18
+ - `createDiskStorage` `put` and `delete` now accept a top-level key such as `x.png`; before, only nested keys worked. Every traversal guard is unchanged.
19
+ - **Changed:** `POST /admin/products` with `status: 'active'` now follows the photo rule too: while `admin.requirePhotoToPublish` is on (the default) it answers 422 `PRODUCT_INCOMPLETE` with `missing: ['images']`, because images can only upload after create. Create the product as a draft, add photos, then publish with `PATCH`. Hosts that create active products directly must do that or set `requirePhotoToPublish: false`.
20
+ - `checkoutUrls(...).success` no longer percent-encodes Stripe's `{CHECKOUT_SESSION_ID}` placeholder, so Stripe substitutes the real session id and buyers land on `/checkout/success?session_id=cs_…` with an id that matches their order. Hosts using `checkoutUrls` with Stripe on 0.5.0–0.6.x should upgrade. No API change.
21
+
22
+ ### Docs
23
+
24
+ - The package now ships `CHANGELOG.md` and declares `description`, `repository` and `homepage`. The README documents the lower-level exports (`catalogPlugin`, `ordersPlugin`, `ProductNotFoundError`, `WebhookSignatureError`, `SESSION_ID_PLACEHOLDER`, `isFakeGateway`, `createMemoryStorage`) and rate-limiting the public checkout routes.
25
+
26
+ ## 0.6.0 (2026-11-03)
27
+
28
+ ### Features
29
+
30
+ - `orderEmailLines(order, { locale, labels })` turns an `OrderDetail` into email-ready pieces: item, subtotal, discount (only when one applied), shipping and total lines, the ship-to address, and `text` / `html` renderings with every value HTML-escaped. Money is formatted like the storefront's `formatPrice`. New types `EmailLine`, `OrderEmailLabels`, `OrderEmailLines`, `OrderEmailLinesOptions`.
31
+
32
+ ## 0.5.0 (2026-11-02)
33
+
34
+ ### Features
35
+
36
+ - `resolvePayment(env = process.env)` picks the Stripe or fake gateway from `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `PAYMENT_GATEWAY` and `NODE_ENV`, and throws on half-configured Stripe or the fake gateway in production. `checkoutUrls(webUrl, { successPath, cancelPath })` builds the `urls` option. New types `CheckoutUrlPaths`, `PaymentEnv`.
37
+ - `commercePlugin({ devCheckout: true | { apiUrl } })` adds `GET /checkout/dev/:sessionId/complete` for the fake gateway: it completes the order through the webhook event path and redirects to the success URL (`?outcome=expired` releases it and redirects to the cancel URL), so a full purchase runs locally without Stripe. Registration throws when `NODE_ENV=production`; the route is skipped for any other gateway. New exports `isFakeGateway`, `DevCheckoutSetting`; `FakeGateway` gains `kind: 'fake'` and `useSessionUrl()`.
38
+ - `createDiskStorage({ dir, publicUrl })` is a `FileStorage` that writes under `dir` and rejects keys that escape it (`..`, absolute paths, encoded traversal, NUL bytes, symlinks pointing outside). `openStoredFile(dir, key)` opens a stored file for serving (stream, image content type, size) or returns `null`, with the same guard. New types `DiskStorageOptions`, `StoredFileStream`.
39
+
40
+ ### Fixes
41
+
42
+ - `createStripeGateway`'s one-off discount coupons now set `redeem_by` to the checkout session's expiry, so coupons from failed or abandoned sessions expire on their own.
43
+ - Admin order and discount list cursors keep Postgres's microsecond `created_at`, so paging never skips or repeats rows created within the same millisecond. Cursors issued by earlier versions (ISO milliseconds) are still accepted.
44
+ - Image upload (`POST /admin/products/:id/images`) applies the same trimmed, 500-character `alt` rule as the PATCH route (400 when longer).
45
+ - Editing a redeemed fixed discount with a case-only currency difference no longer answers 409 `DISCOUNT_IN_USE`.
46
+
47
+ ## 0.4.0 (2026-10-30)
48
+
49
+ ### Features
50
+
51
+ - Migration `0007` (`commerce_discounts`, `commerce_discount_redemptions`, and `discount_cents`/`discount_code` on `commerce_orders`; existing orders get `discount_cents = 0`). Apply it with `runCommerceMigrations` before serving traffic. Order totals are now subtotal − discount + shipping.
52
+ - `PATCH /admin/products/:id/images/:imageId` with `{ alt }` edits an existing image's alt text (404 `PRODUCT_NOT_FOUND` / `IMAGE_NOT_FOUND`, 400 on invalid input) and returns the updated product. Catalog ETags change with the new alt text.
53
+ - `GET|POST /admin/discounts` and `GET|PATCH /admin/discounts/:id` (only when `admin` and checkout are configured) list, create, edit and deactivate discount codes, with `redeemedCount` and `reservedCount`. Codes are unique (409 `DISCOUNT_CODE_TAKEN`); once redeemed, the code and value can't change (409 `DISCOUNT_IN_USE`) and `maxRedemptions` can't drop below usage (400). No hard delete. New contracts `adminDiscountListQuerySchema`, `adminDiscountPageSchema`, `discountStatusSchema`.
54
+ - `POST /checkout/discount/preview` (only when checkout is configured) prices a cart on the server and answers the discount a code would take off, or 422 `DISCOUNT_INVALID` with a `reason`. Inactive codes are reported as `not_found`. Rate-limit it at the host.
55
+ - `POST /checkout` accepts `discountCode`. The code is re-evaluated against server prices and a redemption is reserved in the same transaction as the stock holds, under a row lock on the discount, so `maxRedemptions` holds under concurrent checkouts. A rejected code answers 422 `DISCOUNT_INVALID` with a `reason` and creates no order. The order stores `discountCode`/`discountCents`. Payment commits the redemption; expiry, a failed async payment, the hold sweeper, or a failed gateway call releases it. A `needs_attention` order keeps it reserved until resolved (committed) or fully refunded (released). Refunds of paid orders leave it committed.
56
+ - `CreateCheckoutSessionInput` gains an optional `discount: { code, amountCents }` (new exported type `CheckoutDiscount`). It is an amount off the merchandise subtotal, never shipping. **Custom gateways should apply it**, or the buyer is charged the undiscounted price while the order records the discount. `createStripeGateway` creates a one-off coupon (`duration: 'once'`, `max_redemptions: 1`) and passes `discounts: [{ coupon }]`; `createFakeGateway` records it on `sessions`.
57
+
58
+ ## 0.3.0 (2026-10-30)
59
+
60
+ ### Features
61
+
62
+ - `GET /admin/products` accepts `?stock=in|sold` (stored quantity above 0, or 0; holds are ignored), combinable with `status` and `q`.
63
+ - Public catalog GETs send `Cache-Control` (default `public, max-age=30, stale-while-revalidate=300`) and a strong `ETag`, and answer `If-None-Match` with 304. New `catalogCache: { cacheControl?: string | false }` option on `commercePlugin`; catalog errors send `no-store`.
64
+
65
+ ### Breaking changes
66
+
67
+ - `GET /admin/products` now returns `{ items, nextCursor }` instead of an array. Page with `?limit=` (1-100, default 50) and `?cursor=`, filter with `?status=`, and search names with `?q=` (case-insensitive substring, `%`/`_` literal). Order is unchanged (`created_at`, then `slug`); a bad cursor gives 400 `INVALID_CURSOR`.
68
+ - `GET /catalog/products` now returns `{ items, nextCursor }` instead of an array. Items are cards (no description, materials or dimensions; only the first image). Page with `?limit=` (1-100, default 48) and `?cursor=`; a bad cursor gives 400 `INVALID_CURSOR`, a bad limit 400 `INVALID_QUERY`. Order is unchanged (`created_at`, then `slug`). `GET /catalog/products/:slug` is unchanged.
69
+
70
+ ### Fixes
71
+
72
+ - Published tarballs no longer include declaration maps (`*.d.ts.map`), which pointed at `src/` files that were not shipped. JS sourcemaps now embed `sourcesContent`.
73
+
74
+ ## 0.2.0 (2026-10-29)
75
+
76
+ First published release. 0.1.0 was tagged but never published to npm, so installing 0.2.0 gets everything in both entries below.
77
+
78
+ ### Breaking changes
79
+
80
+ - `PaymentGateway` has a new required method, `refund({ sessionId, amountCents, idempotencyKey })`, returning `{ gatewayRefundId }`. Custom gateways must implement it; `createStripeGateway` and `createFakeGateway` already do. The `PaymentEvent` union also gains a `payment.refunded` member (`GatewayRefundEvent`), which custom gateways may emit, so exhaustive switches over it need a new case.
81
+ - `POST /admin/orders/:id/refunds` requires an `Idempotency-Key` header (1-255 characters, else 400 `IDEMPOTENCY_KEY_REQUIRED`).
82
+
83
+ ### Features
84
+
85
+ - Migrations `0005` (`refunded` order status, fulfillment columns, `commerce_order_refunds`) and `0006` (refund `idempotency_key`, unique per order, and pending refund rows). Apply them with `runCommerceMigrations`.
86
+ - Admin order routes, behind `admin.authorize` and only when checkout is configured: `GET /admin/orders` (filter, search, cursor paging), `GET /admin/orders/:id`, `POST /admin/orders/:id/ship`, `POST /admin/orders/:id/resolve` and `POST /admin/orders/:id/refunds`.
87
+ - Refunds go through the gateway, can restock, and set the order `refunded` once the balance reaches zero. Errors: 400 `REFUND_EXCEEDS_BALANCE`, `RESTOCK_REQUIRES_FULL_REFUND` and `RESTOCK_NOT_APPLICABLE` (`needs_attention` orders never took stock), and 502 `REFUND_GATEWAY_ERROR`. Concurrent refunds count against the balance while in flight.
88
+ - `POST /admin/orders/:id/ship` records carrier and tracking and moves `paid` to `shipped`. The buyer's `GET /orders/session/:id` now returns `fulfillment`.
89
+ - `POST /admin/orders/:id/resolve` moves a `needs_attention` order to `paid` when stock is available, or answers 409 `INSUFFICIENT_STOCK` with `productIds`.
90
+ - Hooks `onOrderShipped` (first ship only) and `onOrderRefunded(order, refund)`. `onOrderPaid` also fires when an order is resolved.
91
+ - `charge.refunded` webhook: refunds made in the Stripe dashboard are recorded as `source: 'gateway'` refunds. Stripe refunds started by the kit carry `metadata.commerceRefundKey`, so the webhook echo matches the pending row instead of recording a second one. Enable `charge.refunded` on your Stripe webhook endpoint.
92
+ - An unknown refund outcome (gateway timeout, network error or 5xx) keeps the pending refund and answers 502 `REFUND_OUTCOME_UNKNOWN`, so a fresh `Idempotency-Key` can't refund twice; retry with the same key. Only a certain failure (Stripe 4xx) still deletes the row. Custom gateways signal certainty by throwing `RefundRejectedError`; the fake gateway gets `failNextRefundUncertain()`, and `failNextRefund()` now throws a `RefundRejectedError`.
93
+ - `GET /admin/orders?hasPendingRefund=true|false`, and pending refunds appear in `AdminOrderDetail.refunds` (`refundedCents` still counts confirmed ones only).
94
+ - Exported `RefundInput` and `GatewayRefund` types. The fake gateway records `refunds` and `refundCalls`, and has `failNextRefund()` and `refunded()`.
95
+
96
+ ## 0.1.0 (2026-10-29)
97
+
98
+ First release. Fastify plugins that own their Drizzle schema and migrations. Peers: `fastify`, `drizzle-orm`, `pg`. Optional peers: `stripe` (Stripe gateway) and `@fastify/multipart` (admin image upload).
99
+
100
+ ### Features
101
+
102
+ - Owned schema and migrations `0000`–`0004`, applied with `runCommerceMigrations`; `commerceSchema` is exported for host queries.
103
+ - `commercePlugin` (and the lower-level `catalogPlugin` / `ordersPlugin`): catalog routes, checkout and order lookup.
104
+ - Checkout re-reads prices server-side and reserves stock with no-oversell inventory holds. `startHoldSweeper` releases lapsed holds.
105
+ - Payment gateways: `createStripeGateway` and `createFakeGateway` (signed test webhooks). Implement `PaymentGateway` to add another.
106
+ - Webhook handling for completed, expired, async-succeeded and async-failed payments. A payment that arrives after its hold lapsed, or an unpaid async completion, no longer silently commits stock; the order moves to `needs_attention`.
107
+ - Shipping rates (`seedShippingRates`, `shippingCountries`) with the chosen rate and address stored on the order.
108
+ - Order hooks: `onOrderPaid`, `onOrderExpired`, `onOrderNeedsAttention`, `onOrderPaymentFailed` (async payment failed; stock already released). Hook failures are logged, not thrown.
109
+ - Catalog-only mode: omit `payment` and `urls` to mount the catalog without checkout.
110
+ - Admin product API (`AdminOptions`): host-supplied `authorize`, product CRUD, and image upload and ordering through the `FileStorage` interface (`createMemoryStorage` included).
111
+ - `seedProducts` for fixtures and demos.
112
+ - `categoryLabels` option (record or function) sets display labels for `GET /catalog/categories`; unmatched slugs fall back to the slug.
package/README.md CHANGED
@@ -4,11 +4,16 @@ Fastify plugins for a small shop: the public catalog, no-oversell inventory
4
4
  holds, Stripe hosted checkout, and orders. The package ships its own Drizzle
5
5
  schema and migrations, and the host owns the database pool and the secrets.
6
6
 
7
+ New here? Start with the
8
+ [getting-started guide](https://github.com/develemit/develemit-commerce/blob/main/docs/getting-started.md).
9
+ What changed in each version, including new migrations, is in
10
+ [CHANGELOG.md](CHANGELOG.md).
11
+
7
12
  ## Install
8
13
 
9
14
  ```sh
10
15
  pnpm add @develemit/commerce-server fastify drizzle-orm pg
11
- pnpm add stripe # only if you use createStripeGateway
16
+ pnpm add stripe # whenever the Stripe keys are set, including via resolvePayment
12
17
  pnpm add @fastify/multipart # only if you pass `admin`
13
18
  ```
14
19
 
@@ -51,6 +56,11 @@ const sweeper = startHoldSweeper(db, { intervalMs: 60_000 });
51
56
  app.addHook('onClose', async () => sweeper.stop());
52
57
  ```
53
58
 
59
+ `resolvePayment(process.env)` picks Stripe or the fake gateway from the
60
+ environment (it refuses the fake gateway when `NODE_ENV=production` and
61
+ half-configured Stripe keys), and `checkoutUrls(webUrl)` builds the `urls`
62
+ option; use them instead of hand-writing both.
63
+
54
64
  `payment` and `urls` go together. Omit both (`commercePlugin({ db, currency })`)
55
65
  for a catalog-only mount: the checkout, webhook, and order routes aren't
56
66
  registered, so `/checkout` returns 404.
@@ -105,6 +115,28 @@ matters.
105
115
  The package never reads `process.env`; pass everything in. Load products with
106
116
  `seedProducts(db, products)`, an idempotent upsert on `slug`.
107
117
 
118
+ ## Order emails
119
+
120
+ `orderEmailLines(order, { locale, labels })` builds the money and address
121
+ pieces of an order email from the `OrderDetail` a hook receives. You write the
122
+ layout and copy.
123
+
124
+ ```ts
125
+ onOrderPaid: async (order) => {
126
+ const lines = orderEmailLines(order);
127
+ await mailer.send({
128
+ to: order.email,
129
+ text: `${lines.text.items}\n${lines.text.totals}\n\n${lines.text.address}`,
130
+ html: `<table>${lines.html.items}${lines.html.totals}</table>${lines.html.address}`,
131
+ });
132
+ },
133
+ ```
134
+
135
+ The result also has structured `items`, `subtotal`, `discount` (absent when
136
+ no discount applied), `shipping`, `total` and `address` (absent for pickup).
137
+ HTML output is `<tr>` / `<p>` fragments with `& < > " '` escaped. Override
138
+ copy with `labels`.
139
+
108
140
  ## Running a shop
109
141
 
110
142
  The order lifecycle, from the host's side:
@@ -177,7 +209,33 @@ Relative to the host's prefix:
177
209
  | `POST /checkout` | `{ items: [{ productId, quantity }] }` → `{ url }`, or 409 |
178
210
  | `POST /checkout/discount/preview` | `{ code, items }` → discount preview, or 422 `DISCOUNT_INVALID` |
179
211
  | `POST /orders/webhooks/stripe` | Stripe webhook (raw body, `stripe-signature` header) |
180
- | `GET /orders/session/:sessionId` | `orderSummarySchema` for the success page |
212
+ | `GET /orders/session/:sessionId` | `orderSummarySchema` for the success page (no shipping address) |
213
+
214
+ With `devCheckout`, the fake gateway also gets
215
+ `GET /checkout/dev/:sessionId/complete` (see [Local checkout](docs/payments.md#local-checkout-without-stripe)).
216
+ The dev order gets a sample buyer email, address and the first shipping rate;
217
+ `devCheckout: { buyer }` overrides them.
218
+ Admin routes are listed in [docs/admin.md](docs/admin.md).
219
+
220
+ `POST /checkout` and `POST /checkout/discount/preview` are public and write or
221
+ price carts, so rate-limit both at the host (for example `@fastify/rate-limit`
222
+ with a route config).
223
+
224
+ ## Lower-level exports
225
+
226
+ - `catalogPlugin` and `ordersPlugin` are the two halves `commercePlugin`
227
+ registers. Use them only to mount the catalog without checkout, or to put
228
+ them under different prefixes.
229
+ - `ProductNotFoundError` is thrown by the catalog service. The route turns it
230
+ into a 404.
231
+ - `WebhookSignatureError` is what a `PaymentGateway`'s `verifyWebhook` should
232
+ throw for a bad signature. The webhook route answers 400 for it.
233
+ - `SESSION_ID_PLACEHOLDER` (`{CHECKOUT_SESSION_ID}`): checkout builds the
234
+ success URL as `urls.success(SESSION_ID_PLACEHOLDER)`. Stripe fills it in
235
+ itself, but a custom gateway must replace it with its session id.
236
+ - `isFakeGateway(gateway)` narrows a `PaymentGateway` to the fake one (for
237
+ dev-only wiring).
238
+ - `createMemoryStorage()` is an in-memory `FileStorage` for tests.
181
239
 
182
240
  ## Discounts
183
241
 
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ declare const commerceSchema: {
52
52
  data: string;
53
53
  driverParam: string;
54
54
  notNull: true;
55
- hasDefault: false;
55
+ hasDefault: true;
56
56
  isPrimaryKey: false;
57
57
  isAutoincrement: false;
58
58
  hasRuntimeDefault: false;
@@ -119,7 +119,7 @@ declare const commerceSchema: {
119
119
  columnType: "PgInteger";
120
120
  data: number;
121
121
  driverParam: string | number;
122
- notNull: true;
122
+ notNull: false;
123
123
  hasDefault: false;
124
124
  isPrimaryKey: false;
125
125
  isAutoincrement: false;
@@ -153,7 +153,7 @@ declare const commerceSchema: {
153
153
  columnType: "PgText";
154
154
  data: string;
155
155
  driverParam: string;
156
- notNull: true;
156
+ notNull: false;
157
157
  hasDefault: false;
158
158
  isPrimaryKey: false;
159
159
  isAutoincrement: false;
@@ -1643,6 +1643,7 @@ interface FileStorage {
1643
1643
  interface AdminOptions {
1644
1644
  authorize(req: FastifyRequest): Promise<boolean>;
1645
1645
  storage: FileStorage;
1646
+ requirePhotoToPublish?: boolean;
1646
1647
  }
1647
1648
 
1648
1649
  interface CatalogCacheOptions {
@@ -1745,6 +1746,7 @@ interface GatewayRefund {
1745
1746
  gatewayRefundId: string;
1746
1747
  }
1747
1748
  interface PaymentGateway {
1749
+ preflight?(): Promise<void>;
1748
1750
  createCheckoutSession(input: CreateCheckoutSessionInput): Promise<CheckoutSession>;
1749
1751
  verifyWebhook(rawBody: Buffer, signature: string | undefined): Promise<PaymentEvent>;
1750
1752
  refund(input: RefundInput): Promise<GatewayRefund>;
@@ -1775,17 +1777,30 @@ interface OrderHooks {
1775
1777
  onOrderRefunded?(order: OrderDetail, refund: OrderRefund): Promise<void>;
1776
1778
  }
1777
1779
 
1780
+ interface DevBuyer {
1781
+ email?: string;
1782
+ name?: string;
1783
+ address?: ShippingAddress;
1784
+ }
1785
+ type DevBuyerSetting = DevBuyer | ((sessionId: string) => DevBuyer);
1786
+
1778
1787
  interface CheckoutOptions {
1779
1788
  payment: PaymentGateway;
1780
1789
  urls: CheckoutUrls;
1781
1790
  shippingCountries?: string[];
1782
1791
  hooks?: OrderHooks;
1792
+ devCheckout?: boolean | DevCheckoutSetting;
1793
+ }
1794
+ interface DevCheckoutSetting {
1795
+ apiUrl?: string;
1796
+ buyer?: DevBuyerSetting;
1783
1797
  }
1784
1798
  interface CatalogOnlyOptions {
1785
1799
  payment?: undefined;
1786
1800
  urls?: undefined;
1787
1801
  shippingCountries?: undefined;
1788
1802
  hooks?: undefined;
1803
+ devCheckout?: undefined;
1789
1804
  }
1790
1805
  type CommercePluginOptions = {
1791
1806
  db: CommerceDb;
@@ -1820,6 +1835,8 @@ interface SignedWebhook {
1820
1835
  signature: string;
1821
1836
  }
1822
1837
  interface FakeGateway extends PaymentGateway {
1838
+ readonly kind: 'fake';
1839
+ useSessionUrl(build: (sessionId: string) => string): void;
1823
1840
  readonly sessions: (CreateCheckoutSessionInput & {
1824
1841
  id: string;
1825
1842
  })[];
@@ -1846,6 +1863,7 @@ interface FakeGatewayOptions {
1846
1863
  secret?: string;
1847
1864
  }
1848
1865
  declare function createFakeGateway({ secret, }?: FakeGatewayOptions): FakeGateway;
1866
+ declare function isFakeGateway(gateway: PaymentGateway): gateway is FakeGateway;
1849
1867
 
1850
1868
  interface HoldSweeperOptions {
1851
1869
  intervalMs?: number;
@@ -1856,6 +1874,42 @@ interface HoldSweeper {
1856
1874
  }
1857
1875
  declare function startHoldSweeper(db: CommerceDb, { intervalMs, onError }?: HoldSweeperOptions): HoldSweeper;
1858
1876
 
1877
+ interface EmailLine {
1878
+ label: string;
1879
+ amount: string;
1880
+ }
1881
+ interface OrderEmailLabels {
1882
+ subtotal: string;
1883
+ discount: (code: string | null) => string;
1884
+ shipping: (label: string | null) => string;
1885
+ total: string;
1886
+ }
1887
+ interface OrderEmailLinesOptions {
1888
+ locale?: string;
1889
+ labels?: Partial<OrderEmailLabels>;
1890
+ }
1891
+ interface OrderEmailLinesText {
1892
+ items: string;
1893
+ totals: string;
1894
+ address: string;
1895
+ }
1896
+ interface OrderEmailLinesHtml {
1897
+ items: string;
1898
+ totals: string;
1899
+ address: string;
1900
+ }
1901
+ interface OrderEmailLines {
1902
+ items: EmailLine[];
1903
+ subtotal: EmailLine;
1904
+ discount?: EmailLine;
1905
+ shipping: EmailLine;
1906
+ total: EmailLine;
1907
+ address?: string[];
1908
+ text: OrderEmailLinesText;
1909
+ html: OrderEmailLinesHtml;
1910
+ }
1911
+ declare function orderEmailLines(order: OrderDetail, options?: OrderEmailLinesOptions): OrderEmailLines;
1912
+
1859
1913
  declare function runCommerceMigrations(db: NodePgDatabase<any>): Promise<void>;
1860
1914
 
1861
1915
  interface SeedProduct {
@@ -1896,5 +1950,30 @@ interface MemoryStorage extends FileStorage {
1896
1950
  }
1897
1951
  declare function createMemoryStorage(baseUrl?: string): MemoryStorage;
1898
1952
 
1899
- export { ProductNotFoundError, SESSION_ID_PLACEHOLDER, WebhookSignatureError, catalogPlugin, commercePlugin, commerceSchema, createFakeGateway, createMemoryStorage, createStripeGateway, ordersPlugin, runCommerceMigrations, seedProducts, seedShippingRates, startHoldSweeper };
1900
- export type { AdminOptions, CatalogCacheOptions, CatalogPluginOptions, CategoryLabels, CheckoutDiscount, CheckoutLine, CheckoutSession, CheckoutUrls, ChosenShipping, CommerceDb, CommercePluginOptions, CommerceSchema, CreateCheckoutSessionInput, FakeGateway, FakeGatewayOptions, FileStorage, GatewayRefund, HoldSweeper, HoldSweeperOptions, MemoryStorage, OrderHooks, OrdersPluginOptions, PaymentEvent, PaymentGateway, RefundInput, SeedProduct, SeedShippingRate, ShippingAddress, ShippingRateOption, SignedWebhook, StoredFile, StripeGatewayOptions };
1953
+ interface DiskStorageOptions {
1954
+ dir: string;
1955
+ publicUrl: string;
1956
+ }
1957
+ interface StoredFileStream {
1958
+ stream: Readable;
1959
+ contentType: string | undefined;
1960
+ size: number;
1961
+ }
1962
+ declare function createDiskStorage(options: DiskStorageOptions): FileStorage;
1963
+ declare function openStoredFile(dir: string, key: string): Promise<StoredFileStream | null>;
1964
+
1965
+ interface PaymentEnv {
1966
+ NODE_ENV?: string;
1967
+ PAYMENT_GATEWAY?: string;
1968
+ STRIPE_SECRET_KEY?: string;
1969
+ STRIPE_WEBHOOK_SECRET?: string;
1970
+ }
1971
+ declare function resolvePayment(env?: PaymentEnv): PaymentGateway;
1972
+ interface CheckoutUrlPaths {
1973
+ successPath?: string;
1974
+ cancelPath?: string;
1975
+ }
1976
+ declare function checkoutUrls(webUrl: string, { successPath, cancelPath, }?: CheckoutUrlPaths): CheckoutUrls;
1977
+
1978
+ export { ProductNotFoundError, SESSION_ID_PLACEHOLDER, WebhookSignatureError, catalogPlugin, checkoutUrls, commercePlugin, commerceSchema, createDiskStorage, createFakeGateway, createMemoryStorage, createStripeGateway, isFakeGateway, openStoredFile, orderEmailLines, ordersPlugin, resolvePayment, runCommerceMigrations, seedProducts, seedShippingRates, startHoldSweeper };
1979
+ export type { AdminOptions, CatalogCacheOptions, CatalogPluginOptions, CategoryLabels, CheckoutDiscount, CheckoutLine, CheckoutSession, CheckoutUrlPaths, CheckoutUrls, ChosenShipping, CommerceDb, CommercePluginOptions, CommerceSchema, CreateCheckoutSessionInput, DevBuyer, DevBuyerSetting, DevCheckoutSetting, DiskStorageOptions, EmailLine, FakeGateway, FakeGatewayOptions, FileStorage, GatewayRefund, HoldSweeper, HoldSweeperOptions, MemoryStorage, OrderEmailLabels, OrderEmailLines, OrderEmailLinesOptions, OrderHooks, OrdersPluginOptions, PaymentEnv, PaymentEvent, PaymentGateway, RefundInput, SeedProduct, SeedShippingRate, ShippingAddress, ShippingRateOption, SignedWebhook, StoredFile, StoredFileStream, StripeGatewayOptions };