@develemit/commerce-server 0.6.0 → 0.8.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/docs/admin.md CHANGED
@@ -5,6 +5,9 @@ Part of [`@develemit/commerce-server`](../README.md).
5
5
  Pass `admin: { authorize, storage }` to `commercePlugin` to register product
6
6
  management under `/admin`. Without it, no admin routes exist (404).
7
7
 
8
+ - `requirePhotoToPublish` (default `true`) makes `PATCH` to `status: 'active'`
9
+ refuse a draft with no image. Set `false` for shops that go live without
10
+ photos.
8
11
  - `authorize(req)` returns `true` to allow the request; `false` gives a 401.
9
12
  The kit owns no identity: check a session, token or header yourself.
10
13
  - `storage` implements `FileStorage` (`put(key, stream, contentType)` and
@@ -15,15 +18,17 @@ management under `/admin`. Without it, no admin routes exist (404).
15
18
  files with `openStoredFile(dir, key)`, which returns `{ stream, contentType,
16
19
  size }` or `null` (answer 404).
17
20
 
18
- | Route | Purpose |
19
- | ---------------------------------------- | ------------------------------------------------------ |
20
- | `GET/POST /admin/products` | List all statuses / create (`adminProductInputSchema`) |
21
- | `GET/PATCH /admin/products/:id` | Read / edit; `{ sold: true }` sets quantity to 0 |
22
- | `POST /admin/products/:id/images` | Multipart `file` (+ optional `alt`), 8MB max |
23
- | `PATCH /admin/products/:id/images/order` | `{ imageIds }`, every image exactly once |
24
- | `PATCH /admin/products/:id/images/:id` | `{ alt }` (trimmed, max 500); 404 for unknown image |
25
- | `DELETE /admin/products/:id/images/:id` | Removes the row and the stored file |
26
- | `GET/PUT /admin/shipping-rates` | `PUT` replaces the full set of rates |
21
+ | Route | Purpose |
22
+ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
23
+ | `GET/POST /admin/products` | List all statuses / create (`adminProductInputSchema`) |
24
+ | `GET /admin/products/counts` | `{ all, live, draft, sold, archived, incompleteDrafts }` |
25
+ | `GET/PATCH /admin/products/:id` | Read / edit; `{ sold: true }` sets quantity to 0 |
26
+ | `DELETE /admin/products/:id` | Deletes a never-live draft with no order lines (204), its files and holds; else 409 `PRODUCT_NOT_DELETABLE` (`reason`) |
27
+ | `POST /admin/products/:id/images` | Multipart `file` (+ optional `alt`), 8MB max |
28
+ | `PATCH /admin/products/:id/images/order` | `{ imageIds }`, every image exactly once |
29
+ | `PATCH /admin/products/:id/images/:id` | `{ alt }` (trimmed, max 500); 404 for unknown image |
30
+ | `DELETE /admin/products/:id/images/:id` | Removes the row and the stored file |
31
+ | `GET/PUT /admin/shipping-rates` | `PUT` replaces the full set of rates |
27
32
 
28
33
  Stored image keys are `products/<productId>/<uuid>.<ext>` and are never
29
34
  overwritten, so their URLs are safe to cache forever. Serve `FileStorage` URLs
@@ -31,8 +36,31 @@ through a CDN with `Cache-Control: public, max-age=31536000, immutable`, and
31
36
  have it (or the storefront's image adapter) resize.
32
37
 
33
38
  Uploads are checked by magic bytes (jpeg, png, webp, heic), not by filename or
34
- the client's content type. Slugs come from the name at creation and stay
35
- stable on rename. Edits never touch inventory holds.
39
+ the client's content type. Edits never touch inventory holds.
40
+
41
+ ## Drafts and publishing
42
+
43
+ A draft may have no name, price or category (a photo-only draft). It never
44
+ appears in the public catalog. Publishing needs all three, plus a photo unless
45
+ `requirePhotoToPublish` is `false`:
46
+
47
+ - `PATCH` to `status: 'active'` with something missing answers `422`
48
+ `{ error: 'PRODUCT_INCOMPLETE', missing: ['name' | 'priceCents' | 'category' | 'images'] }`.
49
+ `AdminClient` throws `ProductIncompleteError` with `missing`.
50
+ - `POST` with `status: 'active'` and no name, price or category answers `400
51
+ INVALID_INPUT` with one issue per missing field. A photo is not checked on
52
+ create, since a new product has no images yet.
53
+ - The public catalog also skips any active row missing a price or category.
54
+
55
+ Slugs: a nameless draft gets `draft-<first 8 chars of id>`. The first time it
56
+ is given a name the slug becomes `slugify(name)` (made unique with `-2`, `-3`).
57
+ After that it never changes, so renaming a live product keeps shop links
58
+ stable. A product created with a name keeps that slug on rename.
59
+
60
+ Counts: each number equals the total of the matching list filter. `live` is
61
+ `status=active`, `draft` is `status=draft`, `archived` is `status=archived`,
62
+ `sold` is `stock=sold` (any status), and `all` is every non-archived product.
63
+ `incompleteDrafts` counts drafts missing a name, price or category.
36
64
 
37
65
  ## Admin orders API
38
66
 
package/docs/payments.md CHANGED
@@ -5,11 +5,16 @@ Part of [`@develemit/commerce-server`](../README.md).
5
5
  `payment` takes any `PaymentGateway` (`createCheckoutSession`,
6
6
  `verifyWebhook`, `refund`). `refund({ sessionId, amountCents, idempotencyKey })`
7
7
  must return the same `gatewayRefundId` when called again with the same key.
8
+ A gateway may also define `preflight(): Promise<void>`; `commercePlugin` awaits
9
+ it during registration, so a gateway that can't work fails startup.
8
10
  Two ship with the package:
9
11
 
10
12
  - `createStripeGateway({ secretKey, webhookSecret })` loads `stripe` lazily, so
11
13
  it's an optional peer. Refunds look up the session's PaymentIntent and call
12
14
  `refunds.create` with the idempotency key.
15
+ Registration awaits the optional `preflight()` hook, which loads `stripe`,
16
+ so a missing package fails startup with `pnpm add stripe` instead of the
17
+ first checkout. A passed-in `client` skips the import.
13
18
  - `createFakeGateway()` for tests and local development. By default its
14
19
  session URL is the success URL (see [Local checkout](#local-checkout-without-stripe)
15
20
  for a click-through flow), and
@@ -34,6 +39,14 @@ only knows sessions created since the process started (404 otherwise), and
34
39
  option on `NODE_ENV !== 'production'` so one config works in every
35
40
  environment.
36
41
 
42
+ The completed order looks like a real one: buyer email `dev-buyer@example.com`,
43
+ a sample US address, and the session's first offered shipping rate (so the
44
+ total includes shipping; no rate means no shipping line). Override the buyer
45
+ with `devCheckout: { buyer: { email, name, address } }`, or pass
46
+ `buyer: (sessionId) => ({ ... })` to compute it per session. Omitted fields keep
47
+ their defaults, so `onOrderPaid` hooks that send a confirmation email or buy a
48
+ label can run locally without a custom dev route.
49
+
37
50
  ## Discounts
38
51
 
39
52
  A discount code is an amount off the merchandise subtotal, never shipping. The
@@ -0,0 +1,3 @@
1
+ ALTER TABLE "commerce_products" ALTER COLUMN "name" SET DEFAULT '';--> statement-breakpoint
2
+ ALTER TABLE "commerce_products" ALTER COLUMN "price_cents" DROP NOT NULL;--> statement-breakpoint
3
+ ALTER TABLE "commerce_products" ALTER COLUMN "category" DROP NOT NULL;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "commerce_products" ADD COLUMN "published_at" timestamp with time zone;--> statement-breakpoint
2
+ UPDATE "commerce_products" SET "published_at" = "updated_at" WHERE "status" <> 'draft';