includio-cms 0.14.6 → 0.15.1
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 +59 -0
- package/DOCS.md +275 -1
- package/ROADMAP.md +23 -2
- package/dist/admin/auth-client.d.ts +42 -42
- package/dist/admin/client/entry/entry.svelte +1 -0
- package/dist/admin/client/index.d.ts +6 -0
- package/dist/admin/client/index.js +6 -0
- package/dist/admin/client/shop/shipping-method-edit-page.svelte +114 -0
- package/dist/admin/client/shop/shipping-method-edit-page.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shipping-method-form.svelte +309 -0
- package/dist/admin/client/shop/shipping-method-form.svelte.d.ts +44 -0
- package/dist/admin/client/shop/shipping-method-new-page.svelte +48 -0
- package/dist/admin/client/shop/shipping-method-new-page.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shipping-methods-list-page.svelte +172 -0
- package/dist/admin/client/shop/shipping-methods-list-page.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +332 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shop-orders-list-page.svelte +150 -0
- package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shop-products-list-page.svelte +157 -0
- package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +3 -0
- package/dist/admin/components/fields/field-renderer.svelte +4 -2
- package/dist/admin/components/fields/shop-field.svelte +298 -0
- package/dist/admin/components/fields/shop-field.svelte.d.ts +7 -0
- package/dist/admin/components/layout/app-sidebar.svelte +2 -0
- package/dist/admin/components/layout/lang.d.ts +6 -0
- package/dist/admin/components/layout/lang.js +12 -0
- package/dist/admin/components/layout/nav-shop.svelte +55 -0
- package/dist/admin/components/layout/nav-shop.svelte.d.ts +3 -0
- package/dist/admin/remote/index.d.ts +1 -0
- package/dist/admin/remote/index.js +1 -0
- package/dist/admin/remote/shop.remote.d.ts +260 -0
- package/dist/admin/remote/shop.remote.js +155 -0
- package/dist/cli/scaffold/admin.js +116 -0
- package/dist/core/cms.d.ts +2 -0
- package/dist/core/cms.js +2 -0
- package/dist/core/fields/fieldSchemaToTs.js +5 -0
- package/dist/core/server/entries/operations/get.js +3 -3
- package/dist/core/server/fields/populateEntry.d.ts +1 -1
- package/dist/core/server/fields/populateEntry.js +3 -1
- package/dist/core/server/generator/fields.js +14 -0
- package/dist/core/server/generator/generator.js +13 -0
- package/dist/db-postgres/schema/index.d.ts +1 -0
- package/dist/db-postgres/schema/index.js +1 -0
- package/dist/db-postgres/schema/shop/index.d.ts +8 -0
- package/dist/db-postgres/schema/shop/index.js +8 -0
- package/dist/db-postgres/schema/shop/order.d.ts +430 -0
- package/dist/db-postgres/schema/shop/order.js +30 -0
- package/dist/db-postgres/schema/shop/orderItem.d.ts +179 -0
- package/dist/db-postgres/schema/shop/orderItem.js +20 -0
- package/dist/db-postgres/schema/shop/orderStatusHistory.d.ts +112 -0
- package/dist/db-postgres/schema/shop/orderStatusHistory.js +12 -0
- package/dist/db-postgres/schema/shop/payment.d.ts +180 -0
- package/dist/db-postgres/schema/shop/payment.js +16 -0
- package/dist/db-postgres/schema/shop/product.d.ts +143 -0
- package/dist/db-postgres/schema/shop/product.js +15 -0
- package/dist/db-postgres/schema/shop/productVariant.d.ts +164 -0
- package/dist/db-postgres/schema/shop/productVariant.js +15 -0
- package/dist/db-postgres/schema/shop/shippingMethod.d.ts +209 -0
- package/dist/db-postgres/schema/shop/shippingMethod.js +14 -0
- package/dist/db-postgres/schema/shop/stockReservation.d.ts +109 -0
- package/dist/db-postgres/schema/shop/stockReservation.js +13 -0
- package/dist/db-postgres/schema-core.d.ts +9 -0
- package/dist/db-postgres/schema-core.js +9 -0
- package/dist/db-postgres/schema-shop.d.ts +1 -0
- package/dist/db-postgres/schema-shop.js +1 -0
- package/dist/email-nodemailer/index.d.ts +2 -9
- package/dist/paraglide/messages/_index.d.ts +3 -36
- package/dist/paraglide/messages/_index.js +3 -71
- package/dist/paraglide/messages/hello_world.d.ts +5 -0
- package/dist/paraglide/messages/hello_world.js +33 -0
- package/dist/paraglide/messages/login_hello.d.ts +16 -0
- package/dist/paraglide/messages/login_hello.js +34 -0
- package/dist/paraglide/messages/login_please_login.d.ts +16 -0
- package/dist/paraglide/messages/login_please_login.js +34 -0
- package/dist/shop/adapters/manual/index.d.ts +10 -0
- package/dist/shop/adapters/manual/index.js +16 -0
- package/dist/shop/adapters/payu/client.d.ts +22 -0
- package/dist/shop/adapters/payu/client.js +78 -0
- package/dist/shop/adapters/payu/index.d.ts +24 -0
- package/dist/shop/adapters/payu/index.js +88 -0
- package/dist/shop/adapters/payu/payload.d.ts +48 -0
- package/dist/shop/adapters/payu/payload.js +48 -0
- package/dist/shop/adapters/payu/signature.d.ts +12 -0
- package/dist/shop/adapters/payu/signature.js +50 -0
- package/dist/shop/adapters/payu/status-map.d.ts +3 -0
- package/dist/shop/adapters/payu/status-map.js +14 -0
- package/dist/shop/cart/cookie.d.ts +8 -0
- package/dist/shop/cart/cookie.js +84 -0
- package/dist/shop/cart/order-token-cookie.d.ts +9 -0
- package/dist/shop/cart/order-token-cookie.js +40 -0
- package/dist/shop/cart/types.d.ts +42 -0
- package/dist/shop/cart/types.js +1 -0
- package/dist/shop/client/index.d.ts +121 -0
- package/dist/shop/client/index.js +49 -0
- package/dist/shop/client/use-order.svelte.d.ts +32 -0
- package/dist/shop/client/use-order.svelte.js +105 -0
- package/dist/shop/http/cart-handler.d.ts +7 -0
- package/dist/shop/http/cart-handler.js +88 -0
- package/dist/shop/http/checkout-handler.d.ts +4 -0
- package/dist/shop/http/checkout-handler.js +143 -0
- package/dist/shop/http/index.d.ts +7 -0
- package/dist/shop/http/index.js +7 -0
- package/dist/shop/http/order-handler.d.ts +4 -0
- package/dist/shop/http/order-handler.js +85 -0
- package/dist/shop/http/refresh-payment-handler.d.ts +4 -0
- package/dist/shop/http/refresh-payment-handler.js +73 -0
- package/dist/shop/http/retry-payment-handler.d.ts +4 -0
- package/dist/shop/http/retry-payment-handler.js +99 -0
- package/dist/shop/http/retry-payment-logic.d.ts +2 -0
- package/dist/shop/http/retry-payment-logic.js +4 -0
- package/dist/shop/http/shipping-handler.d.ts +4 -0
- package/dist/shop/http/shipping-handler.js +32 -0
- package/dist/shop/http/webhook-handler.d.ts +4 -0
- package/dist/shop/http/webhook-handler.js +73 -0
- package/dist/shop/http/webhook-logic.d.ts +4 -0
- package/dist/shop/http/webhook-logic.js +21 -0
- package/dist/shop/index.d.ts +6 -0
- package/dist/shop/index.js +19 -0
- package/dist/shop/pricing.d.ts +15 -0
- package/dist/shop/pricing.js +31 -0
- package/dist/shop/rate-limit.d.ts +9 -0
- package/dist/shop/rate-limit.js +28 -0
- package/dist/shop/server/cart-hydrate.d.ts +4 -0
- package/dist/shop/server/cart-hydrate.js +172 -0
- package/dist/shop/server/db.d.ts +4 -0
- package/dist/shop/server/db.js +16 -0
- package/dist/shop/server/email.d.ts +2 -0
- package/dist/shop/server/email.js +154 -0
- package/dist/shop/server/order-access-url.d.ts +7 -0
- package/dist/shop/server/order-access-url.js +6 -0
- package/dist/shop/server/order-number.d.ts +5 -0
- package/dist/shop/server/order-number.js +15 -0
- package/dist/shop/server/orders.d.ts +46 -0
- package/dist/shop/server/orders.js +305 -0
- package/dist/shop/server/payment-compat.d.ts +5 -0
- package/dist/shop/server/payment-compat.js +9 -0
- package/dist/shop/server/populate.d.ts +15 -0
- package/dist/shop/server/populate.js +39 -0
- package/dist/shop/server/shipping.d.ts +38 -0
- package/dist/shop/server/shipping.js +114 -0
- package/dist/shop/server/shop-data.d.ts +51 -0
- package/dist/shop/server/shop-data.js +186 -0
- package/dist/shop/services/cart.service.d.ts +38 -0
- package/dist/shop/services/cart.service.js +1 -0
- package/dist/shop/services/email.service.d.ts +6 -0
- package/dist/shop/services/email.service.js +1 -0
- package/dist/shop/services/index.d.ts +6 -0
- package/dist/shop/services/index.js +1 -0
- package/dist/shop/services/orders.service.d.ts +34 -0
- package/dist/shop/services/orders.service.js +1 -0
- package/dist/shop/services/payment.service.d.ts +7 -0
- package/dist/shop/services/payment.service.js +1 -0
- package/dist/shop/services/products.service.d.ts +31 -0
- package/dist/shop/services/products.service.js +1 -0
- package/dist/shop/services/shipping.service.d.ts +23 -0
- package/dist/shop/services/shipping.service.js +1 -0
- package/dist/shop/svelte/OrderStatus.svelte +368 -0
- package/dist/shop/svelte/OrderStatus.svelte.d.ts +14 -0
- package/dist/shop/svelte/index.d.ts +3 -0
- package/dist/shop/svelte/index.js +2 -0
- package/dist/shop/svelte/labels.d.ts +25 -0
- package/dist/shop/svelte/labels.js +41 -0
- package/dist/shop/types.d.ts +90 -0
- package/dist/shop/types.js +1 -0
- package/dist/types/cms.d.ts +3 -0
- package/dist/types/fields.d.ts +18 -2
- package/dist/updates/0.15.0/index.d.ts +2 -0
- package/dist/updates/0.15.0/index.js +25 -0
- package/dist/updates/0.15.1/index.d.ts +2 -0
- package/dist/updates/0.15.1/index.js +27 -0
- package/dist/updates/index.js +3 -1
- package/package.json +31 -1
- package/dist/paraglide/messages/en.d.ts +0 -5
- package/dist/paraglide/messages/en.js +0 -14
- package/dist/paraglide/messages/pl.d.ts +0 -5
- package/dist/paraglide/messages/pl.js +0 -14
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,65 @@
|
|
|
3
3
|
All notable changes to includio-cms are documented here.
|
|
4
4
|
Generated from `src/lib/updates/` — do not edit manually.
|
|
5
5
|
|
|
6
|
+
## 0.15.1 — 2026-04-15
|
|
7
|
+
|
|
8
|
+
Shop: PayU payment adapter + secure order access (token-gated view API, email link).
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Orders now carry an `accessToken` — public order-view API is gated by this token (no enumeration by order number).
|
|
12
|
+
- New `createOrderHandler()` in `includio-cms/shop/http` — `GET /api/shop/orders/[number]?token=...` returns order + items + status history; accepts cookie fallback `aria_shop_order` written on checkout (30-min httpOnly).
|
|
13
|
+
- `ShopConfig.orderViewUrl` template — placeholders `{orderNumber}`, `{orderId}`, `{accessToken}`, `{language}`. Default: `/shop/order/{orderNumber}?token={accessToken}`. When set to an absolute URL, status emails include a "View order" button.
|
|
14
|
+
- New `createPaymentWebhookHandler()` — mount at `/api/shop/webhooks/[provider]/+server.ts`. Dispatches to the matching `PaymentAdapter.handleWebhook`, maps payment event to order status, and is idempotent (terminal orders ack 200 no-op — safe against provider retries). 400 on bad signature, 200 on unknown order.
|
|
15
|
+
- Checkout now calls `PaymentAdapter.createPayment()` after the order is created. The response exposes `paymentStatus`, `requiresPaymentRedirect`, `redirectUrl`, and `accessToken` so the frontend can redirect to the gateway and deep-link back to the order view.
|
|
16
|
+
- Orders store `paymentProviderRef` — the external id returned by the payment adapter (e.g. PayU orderId). Used to correlate webhooks and future refunds/status polls.
|
|
17
|
+
- New `payuAdapter()` — OAuth token caching, create order (REST v2.1), MD5 `OpenPayU-Signature` verification on webhooks, full PayU status mapping (including REJECTED and WAITING_FOR_CONFIRMATION). Import from `includio-cms/shop`.
|
|
18
|
+
- `PaymentAdapter.createPayment()` receives an optional `{ customerIp, language }` context, threaded from the SvelteKit request in the built-in checkout handler.
|
|
19
|
+
- Shipping ↔ payment compatibility: `shop_shipping_methods.allowed_payment_methods` (jsonb string[] | null). Null/empty = any payment method allowed; otherwise checkout rejects the order when the chosen payment is not in the list (e.g. disable COD for paczkomat). The shipping-methods API exposes the list so the frontend can filter payment options per shipping choice.
|
|
20
|
+
- Admin: shipping method form now exposes an "allowed payment methods" section with a restrict toggle + multi-select of configured payment adapters. `getShopConfig` remote returns the payment adapter list (id + i18n label).
|
|
21
|
+
- New optional `PaymentAdapter.getStatus(providerRef)` for provider-side polling, plus `createRefreshPaymentHandler()` mounted at `/api/shop/orders/[number]/refresh-payment`. Token-gated (same as order view). Used as a safety net when a webhook is lost — PayU adapter implements it out of the box.
|
|
22
|
+
- Retry payment — `createRetryPaymentHandler()` at `POST /api/shop/orders/[number]/retry-payment`. Reuses the same order, creates a fresh payment session via the adapter, stores the new `paymentProviderRef`. Status `paymentRejected` is rolled back to `awaitingPayment`. Terminal states return 409.
|
|
23
|
+
- Shop SDK: new `orders` namespace on `createShopClient()` — `get(number, token)`, `refreshPayment(number, token)`, `retryPayment(number, token)`. All response types exported from `includio-cms/shop/client`.
|
|
24
|
+
- Headless Svelte 5 helper `createOrderState({ number, token, initialData? })` — reactive state class with `data/loading/error/phase`, `load()`, `refreshPayment()`, `retry()`, `startPolling()/stopPolling()`. Supports SSR via `initialData`.
|
|
25
|
+
- Generic `<OrderStatus />` Svelte component — drop-in order view with awaiting/success/rejected states, auto-polling, retry button, status timeline. Full theming via CSS custom properties and `::part()`. Import from `includio-cms/shop/svelte`.
|
|
26
|
+
- Docs: new section covering the shop module, order view patterns (generic vs headless), and retry lifecycle.
|
|
27
|
+
|
|
28
|
+
### Migration
|
|
29
|
+
|
|
30
|
+
```sql
|
|
31
|
+
ALTER TABLE shop_orders ADD COLUMN access_token uuid NOT NULL DEFAULT gen_random_uuid();
|
|
32
|
+
ALTER TABLE shop_orders ADD COLUMN payment_provider_ref text;
|
|
33
|
+
ALTER TABLE shop_shipping_methods ADD COLUMN allowed_payment_methods jsonb;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Notes
|
|
37
|
+
|
|
38
|
+
Requires `gen_random_uuid()` (pgcrypto or Postgres 13+). Existing orders get a fresh random token on migration — pre-existing customer links keep working because order view links were not shipped before this version.
|
|
39
|
+
|
|
40
|
+
## 0.15.0 — 2026-04-14
|
|
41
|
+
|
|
42
|
+
Shop module MVP — headless e-commerce: products, cart, orders, payments, emails
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- New optional `shop` module — activate via `defineShop()` in `cms.config.ts`
|
|
46
|
+
- Product = CMS entry + built-in `shop` field (like `seo`) — add `{ type: "shop", slug: "shop" }` to any collection to make its entries purchasable. Shop data lives in `shop_products` (keyed by entry_id FK cascade), never in entry JSON
|
|
47
|
+
- Runtime `schema.ts` generator — auto-emits `src/lib/cms/runtime/schema.ts` combining core + auth + shop tables; point your `drizzle.config.ts` schema field at it, then `pnpm drizzle-kit push`
|
|
48
|
+
- Entry edit panel shows Shop section with net/gross toggle + live VAT preview; optional variants (auto default variant when disabled) and per-variant stock
|
|
49
|
+
- Shop admin: list of all shoppable entries across collections, list+drag-n-drop reorder for shipping methods, full orders list + detail with status history, change status, resend status email
|
|
50
|
+
- Headless cart (signed cookie) with server-hydrated totals — `POST/PATCH/DELETE /api/shop/cart` + typed `createShopClient()` SDK
|
|
51
|
+
- Checkout endpoint `POST /api/shop/checkout` — creates order from cart with consent validation, stock reservation (30-min TTL, released on cancel/reject, consumed on paid), and status emails
|
|
52
|
+
- Payment adapter interface + `manualAdapter()` for bank transfer / COD (order stays in `awaitingPayment` until admin marks paid)
|
|
53
|
+
- Shipping methods — admin CRUD with free-above threshold, carrier-agnostic (InPost/Stripe etc. slotted as adapters in later 0.15.x patches)
|
|
54
|
+
- Feature flags `variants`, `stock`, `accounts` — off by default; enable per project
|
|
55
|
+
- Rate-limit middleware on checkout + webhook endpoints
|
|
56
|
+
- Status-change emails rendered inline (PL/EN), sent via existing `EmailAdapter` — no new dependency
|
|
57
|
+
- Order numbers: random Crockford base32 (`XXXXX-XXXXX`) — unguessable
|
|
58
|
+
- CLI scaffold includes all shop routes (admin pages + public API stubs) — run `scaffoldAdmin()` to provision them in your consumer app
|
|
59
|
+
- `nodemailerAdapter` — `transportOptions` now typed as full `SMTPTransport.Options`, unlocking OAuth2 (Gmail etc.), pooled connections and all native Nodemailer options
|
|
60
|
+
|
|
61
|
+
### Notes
|
|
62
|
+
|
|
63
|
+
Headless e-commerce MVP. Stripe (0.15.1), PayU (0.15.2) and InPost geowidget (0.15.3) ship as optional adapters in follow-up patches. Consumer UI (cart drawer, checkout forms, success screens) is built in the app — shop only exposes the API and admin.
|
|
64
|
+
|
|
6
65
|
## 0.14.6 — 2026-04-13
|
|
7
66
|
|
|
8
67
|
Admin page titles — meaningful <title> in every admin route
|
package/DOCS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Includio CMS Documentation (v0.
|
|
1
|
+
# Includio CMS Documentation (v0.15.1)
|
|
2
2
|
|
|
3
3
|
> This file is auto-generated from the docs site. For the latest version, update the package.
|
|
4
4
|
|
|
@@ -3240,6 +3240,10 @@ interface SendMailOptions {
|
|
|
3240
3240
|
|
|
3241
3241
|
## Built-in: Nodemailer
|
|
3242
3242
|
|
|
3243
|
+
`transportOptions` accepts the full Nodemailer `SMTPTransport.Options` type, so anything Nodemailer supports works — classic SMTP, OAuth2, pooled connections, TLS options, etc.
|
|
3244
|
+
|
|
3245
|
+
### SMTP with user/password
|
|
3246
|
+
|
|
3243
3247
|
```typescript
|
|
3244
3248
|
import { nodemailerAdapter } from 'includio-cms/email-nodemailer';
|
|
3245
3249
|
|
|
@@ -3257,6 +3261,46 @@ email: nodemailerAdapter({
|
|
|
3257
3261
|
})
|
|
3258
3262
|
```
|
|
3259
3263
|
|
|
3264
|
+
### OAuth2 (generic)
|
|
3265
|
+
|
|
3266
|
+
```typescript
|
|
3267
|
+
email: nodemailerAdapter({
|
|
3268
|
+
defaultFromAddress: 'noreply@example.com',
|
|
3269
|
+
defaultFromName: 'My Site',
|
|
3270
|
+
transportOptions: {
|
|
3271
|
+
host: 'smtp.example.com',
|
|
3272
|
+
port: 465,
|
|
3273
|
+
secure: true,
|
|
3274
|
+
auth: {
|
|
3275
|
+
type: 'OAuth2',
|
|
3276
|
+
user: 'noreply@example.com',
|
|
3277
|
+
clientId: process.env.OAUTH_CLIENT_ID,
|
|
3278
|
+
clientSecret: process.env.OAUTH_CLIENT_SECRET,
|
|
3279
|
+
refreshToken: process.env.OAUTH_REFRESH_TOKEN
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
})
|
|
3283
|
+
```
|
|
3284
|
+
|
|
3285
|
+
### OAuth2 (Gmail)
|
|
3286
|
+
|
|
3287
|
+
```typescript
|
|
3288
|
+
email: nodemailerAdapter({
|
|
3289
|
+
defaultFromAddress: 'noreply@gmail.com',
|
|
3290
|
+
defaultFromName: 'My Site',
|
|
3291
|
+
transportOptions: {
|
|
3292
|
+
service: 'gmail',
|
|
3293
|
+
auth: {
|
|
3294
|
+
type: 'OAuth2',
|
|
3295
|
+
user: 'noreply@gmail.com',
|
|
3296
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
3297
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
3298
|
+
refreshToken: process.env.GOOGLE_REFRESH_TOKEN
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
})
|
|
3302
|
+
```
|
|
3303
|
+
|
|
3260
3304
|
> **Transactional Email:** For production, use a transactional email service (SendGrid, Postmark, Resend, etc). Implement the `EmailAdapter` interface with their SDK.
|
|
3261
3305
|
|
|
3262
3306
|
## Custom Adapter Example
|
|
@@ -4253,6 +4297,236 @@ Options for `create`/`createAndPublish`: `{ skipValidation?, sortOrder?, lang? }
|
|
|
4253
4297
|
> **Delete Restrictions:** Only archived entries can be permanently deleted. Call `archive` first, then `delete`.
|
|
4254
4298
|
|
|
4255
4299
|
|
|
4300
|
+
---
|
|
4301
|
+
|
|
4302
|
+
# Shop
|
|
4303
|
+
|
|
4304
|
+
Headless e-commerce module. Optional — activate by adding `shop: defineShop({...})` to your CMS config.
|
|
4305
|
+
|
|
4306
|
+
> Shop is in beta. API may change between 0.15.x patches.
|
|
4307
|
+
|
|
4308
|
+
## What you get
|
|
4309
|
+
|
|
4310
|
+
- **Products as entries** — add `{ type: 'shop', slug: 'shop' }` to any collection; its entries become purchasable.
|
|
4311
|
+
- **Cart** — signed cookie, headless SDK (`createShopClient()`), `POST/PATCH/DELETE /api/shop/cart`.
|
|
4312
|
+
- **Checkout + orders** — `POST /api/shop/checkout` creates an order with consent validation, stock reservation (30-min TTL), and status emails.
|
|
4313
|
+
- **Payment adapters** — `manualAdapter()` (bank transfer / COD) and `payuAdapter()` ship built-in; plug your own by implementing `PaymentAdapter`.
|
|
4314
|
+
- **Webhook infrastructure** — `createPaymentWebhookHandler()` dispatches provider callbacks, verifies signatures, is idempotent.
|
|
4315
|
+
- **Secure order view** — per-order `accessToken` + cookie fallback + `GET /api/shop/orders/[number]` token-gated API.
|
|
4316
|
+
- **Shipping ↔ payment compatibility** — restrict payment methods per shipping (e.g. no COD for paczkomat).
|
|
4317
|
+
|
|
4318
|
+
## Config
|
|
4319
|
+
|
|
4320
|
+
```ts
|
|
4321
|
+
import { defineShop, manualAdapter, payuAdapter } from 'includio-cms/shop';
|
|
4322
|
+
|
|
4323
|
+
export const cmsConfig = defineCMS({
|
|
4324
|
+
shop: defineShop({
|
|
4325
|
+
currency: 'PLN',
|
|
4326
|
+
vatRates: [23, 8, 0],
|
|
4327
|
+
orderViewUrl: `${process.env.PUBLIC_URL}/shop/order/{orderNumber}?token={accessToken}`,
|
|
4328
|
+
payment: [
|
|
4329
|
+
manualAdapter({ id: 'transfer', label: { pl: 'Przelew', en: 'Bank transfer' } }),
|
|
4330
|
+
payuAdapter({
|
|
4331
|
+
posId: process.env.PAYU_POS_ID!,
|
|
4332
|
+
clientId: process.env.PAYU_CLIENT_ID!,
|
|
4333
|
+
clientSecret: process.env.PAYU_CLIENT_SECRET!,
|
|
4334
|
+
secondKey: process.env.PAYU_SECOND_KEY!,
|
|
4335
|
+
environment: 'sandbox',
|
|
4336
|
+
notifyUrl: `${process.env.PUBLIC_URL}/api/shop/webhooks/payu`
|
|
4337
|
+
})
|
|
4338
|
+
],
|
|
4339
|
+
features: { variants: true, stock: true },
|
|
4340
|
+
consents: [{ id: 'terms', required: true, labelI18n: { pl: 'Akceptuję regulamin', en: 'I accept terms' } }]
|
|
4341
|
+
})
|
|
4342
|
+
})
|
|
4343
|
+
```
|
|
4344
|
+
|
|
4345
|
+
## Routes
|
|
4346
|
+
|
|
4347
|
+
Scaffold provisions these endpoints in your app:
|
|
4348
|
+
|
|
4349
|
+
| Path | Handler | Purpose |
|
|
4350
|
+
|------|---------|---------|
|
|
4351
|
+
| `POST /api/shop/cart` | `createCartHandler()` | Cart CRUD |
|
|
4352
|
+
| `GET /api/shop/shipping-methods` | `createShippingMethodsHandler()` | List active shipping |
|
|
4353
|
+
| `POST /api/shop/checkout` | `createCheckoutHandler()` | Create order + initiate payment |
|
|
4354
|
+
| `GET /api/shop/orders/[number]` | `createOrderHandler()` | Token-gated order view |
|
|
4355
|
+
| `POST /api/shop/orders/[number]/refresh-payment` | `createRefreshPaymentHandler()` | Pull status from provider |
|
|
4356
|
+
| `POST /api/shop/orders/[number]/retry-payment` | `createRetryPaymentHandler()` | New payment attempt |
|
|
4357
|
+
| `POST /api/shop/webhooks/[provider]` | `createPaymentWebhookHandler()` | Provider callbacks |
|
|
4358
|
+
|
|
4359
|
+
## See also
|
|
4360
|
+
|
|
4361
|
+
- [Order view](/docs/shop/order-view) — generic `<OrderStatus>` component + headless helper
|
|
4362
|
+
- [Retry payment](/docs/shop/retry-payment) — lifecycle + endpoint
|
|
4363
|
+
|
|
4364
|
+
|
|
4365
|
+
---
|
|
4366
|
+
|
|
4367
|
+
# Order view
|
|
4368
|
+
|
|
4369
|
+
After checkout the customer needs a page that shows their order status, payment outcome, and retry options. Shop offers two paths — a batteries-included component for quick projects, and a headless helper for custom designs.
|
|
4370
|
+
|
|
4371
|
+
## 1. Generic component (`<OrderStatus />`)
|
|
4372
|
+
|
|
4373
|
+
Drop-in view with three visual states (awaiting payment / success / rejected), auto-polling, retry button, status timeline. Theme through CSS custom properties.
|
|
4374
|
+
|
|
4375
|
+
```svelte
|
|
4376
|
+
<!-- src/routes/shop/order/[number]/+page.svelte -->
|
|
4377
|
+
<OrderStatus {number} {token} />
|
|
4378
|
+
```
|
|
4379
|
+
|
|
4380
|
+
### Theming
|
|
4381
|
+
|
|
4382
|
+
All styling sits on CSS variables — override in your page or app.css:
|
|
4383
|
+
|
|
4384
|
+
```css
|
|
4385
|
+
.aria-order-status {
|
|
4386
|
+
--shop-bg: #171717;
|
|
4387
|
+
--shop-fg: #f2e9e1;
|
|
4388
|
+
--shop-muted: rgba(242, 233, 225, 0.6);
|
|
4389
|
+
--shop-accent: #f2e9e1;
|
|
4390
|
+
--shop-success: #8fb58c;
|
|
4391
|
+
--shop-error: #c44b4b;
|
|
4392
|
+
--shop-border: rgba(242, 233, 225, 0.15);
|
|
4393
|
+
--shop-radius: 2px;
|
|
4394
|
+
--shop-font-display: 'Syncopate', sans-serif;
|
|
4395
|
+
--shop-font-body: 'Figtree', sans-serif;
|
|
4396
|
+
}
|
|
4397
|
+
```
|
|
4398
|
+
|
|
4399
|
+
Fine-grained overrides via `part="..."` selectors:
|
|
4400
|
+
|
|
4401
|
+
```css
|
|
4402
|
+
.aria-order-status::part(title) { text-transform: uppercase; }
|
|
4403
|
+
.aria-order-status::part(btn-retry) { letter-spacing: 0.08em; }
|
|
4404
|
+
```
|
|
4405
|
+
|
|
4406
|
+
### Props
|
|
4407
|
+
|
|
4408
|
+
| Prop | Type | Notes |
|
|
4409
|
+
|------|------|-------|
|
|
4410
|
+
| `number` | `string` | Order number (from URL) |
|
|
4411
|
+
| `token` | `string?` | Access token from `?token=`; falls back to cookie |
|
|
4412
|
+
| `initialData` | `OrderDetailResponse?` | SSR snapshot from `+page.server.ts` |
|
|
4413
|
+
| `labels` | `Partial<OrderStatusLabels>` | Override copy (Polish by default) |
|
|
4414
|
+
| `autoPoll` | `boolean` | Default `true` — polls every 5s when status is `awaitingPayment` |
|
|
4415
|
+
| `onPaid` | `(data) => void` | Fires the first time status becomes `paid` |
|
|
4416
|
+
|
|
4417
|
+
### SSR
|
|
4418
|
+
|
|
4419
|
+
Fetch on the server for instant render and email-from-SPA support:
|
|
4420
|
+
|
|
4421
|
+
```ts
|
|
4422
|
+
// +page.server.ts
|
|
4423
|
+
import { createShopClient } from 'includio-cms/shop/client';
|
|
4424
|
+
import { readOrderTokenCookie } from 'includio-cms/shop/client'; // exposed from shop module
|
|
4425
|
+
|
|
4426
|
+
export const load = async ({ params, url, cookies, fetch }) => {
|
|
4427
|
+
const client = createShopClient({ fetch });
|
|
4428
|
+
const token = url.searchParams.get('token') ?? undefined;
|
|
4429
|
+
try {
|
|
4430
|
+
const initialData = await client.orders.get(params.number, token);
|
|
4431
|
+
return { initialData, token };
|
|
4432
|
+
} catch {
|
|
4433
|
+
return { initialData: null, token };
|
|
4434
|
+
}
|
|
4435
|
+
};
|
|
4436
|
+
```
|
|
4437
|
+
|
|
4438
|
+
## 2. Custom UI (headless)
|
|
4439
|
+
|
|
4440
|
+
For fully custom designs, use `createOrderState` — a reactive Svelte 5 state object. Build whatever markup you want.
|
|
4441
|
+
|
|
4442
|
+
```svelte
|
|
4443
|
+
{#if order.loading && !order.data}
|
|
4444
|
+
<p>Ładowanie…</p>
|
|
4445
|
+
{:else if order.data}
|
|
4446
|
+
{@const o = order.data.order}
|
|
4447
|
+
<h1>Zamówienie {o.number}</h1>
|
|
4448
|
+
<p>Status: {o.status}</p>
|
|
4449
|
+
|
|
4450
|
+
{#if o.status === 'awaitingPayment' || o.status === 'paymentRejected'}
|
|
4451
|
+
<button onclick={handleRetry} disabled={order.phase === 'retrying'}>
|
|
4452
|
+
Zapłać ponownie
|
|
4453
|
+
</button>
|
|
4454
|
+
{/if}
|
|
4455
|
+
{/if}
|
|
4456
|
+
```
|
|
4457
|
+
|
|
4458
|
+
### State API
|
|
4459
|
+
|
|
4460
|
+
- `order.data` — `OrderDetailResponse | null`
|
|
4461
|
+
- `order.loading` — initial fetch flag
|
|
4462
|
+
- `order.error` — last error (`Error | null`)
|
|
4463
|
+
- `order.phase` — `'idle' | 'polling' | 'retrying' | 'refreshing'`
|
|
4464
|
+
- `order.status` — shorthand for `data.order.status`
|
|
4465
|
+
- `order.load()` — fetch fresh data
|
|
4466
|
+
- `order.refreshPayment()` — ask provider for latest (useful if webhook is lost)
|
|
4467
|
+
- `order.retry()` — create new payment attempt, returns `redirectUrl | null`
|
|
4468
|
+
- `order.startPolling() / stopPolling()` — auto-refresh every 5s (max 2min), stops on terminal status
|
|
4469
|
+
- `order.dispose()` — clear timers (call in `onDestroy`)
|
|
4470
|
+
|
|
4471
|
+
## Security
|
|
4472
|
+
|
|
4473
|
+
- Every order has an `accessToken` (uuid). The view API (`/api/shop/orders/[number]`) requires it in `?token=` or in the `aria_shop_order` cookie (same-browser fallback, 30-min httpOnly).
|
|
4474
|
+
- Status emails include the full URL with token — the customer can return from any device.
|
|
4475
|
+
- Order numbers are Crockford base32 but token-gating prevents enumeration regardless.
|
|
4476
|
+
|
|
4477
|
+
|
|
4478
|
+
---
|
|
4479
|
+
|
|
4480
|
+
# Retry payment
|
|
4481
|
+
|
|
4482
|
+
When a payment attempt is rejected or left hanging, the customer must be able to try again without going through checkout a second time.
|
|
4483
|
+
|
|
4484
|
+
## Lifecycle
|
|
4485
|
+
|
|
4486
|
+
1. Customer completes checkout → order created with status `awaitingPayment`, adapter creates a payment session, `paymentProviderRef` stored.
|
|
4487
|
+
2. Something goes wrong — customer cancels on gateway, card declined, session expires → webhook flips status to `paymentRejected` (or stays `awaitingPayment` if the gateway never called back).
|
|
4488
|
+
3. Customer hits "Zapłać ponownie" on the order view.
|
|
4489
|
+
4. `POST /api/shop/orders/[number]/retry-payment?token=...` →
|
|
4490
|
+
- token-gated, status must be `awaitingPayment` or `paymentRejected` (409 otherwise)
|
|
4491
|
+
- adapter's `createPayment()` is called again with the same `OrderRef`
|
|
4492
|
+
- new `paymentProviderRef` replaces the old one
|
|
4493
|
+
- if coming from `paymentRejected`, status rolls back to `awaitingPayment` (logged in status history as `changedBy: 'retry-payment'`)
|
|
4494
|
+
- response contains `redirectUrl` for the new gateway session
|
|
4495
|
+
|
|
4496
|
+
## SDK
|
|
4497
|
+
|
|
4498
|
+
```ts
|
|
4499
|
+
import { createShopClient } from 'includio-cms/shop/client';
|
|
4500
|
+
|
|
4501
|
+
const client = createShopClient();
|
|
4502
|
+
const result = await client.orders.retryPayment(orderNumber, token);
|
|
4503
|
+
|
|
4504
|
+
if (result.requiresPaymentRedirect && result.redirectUrl) {
|
|
4505
|
+
window.location.href = result.redirectUrl;
|
|
4506
|
+
}
|
|
4507
|
+
```
|
|
4508
|
+
|
|
4509
|
+
## With `createOrderState`
|
|
4510
|
+
|
|
4511
|
+
The headless helper handles the redirect for you — it returns the URL, you navigate:
|
|
4512
|
+
|
|
4513
|
+
```ts
|
|
4514
|
+
const url = await order.retry();
|
|
4515
|
+
if (url) window.location.href = url;
|
|
4516
|
+
```
|
|
4517
|
+
|
|
4518
|
+
## Constraints
|
|
4519
|
+
|
|
4520
|
+
- The same order is reused — numbers, tokens, items, totals stay the same.
|
|
4521
|
+
- Terminal statuses (`paid`, `done`, `cancelled`) block retry with **409**.
|
|
4522
|
+
- Orders without a payment method or whose adapter was removed from config return **400**.
|
|
4523
|
+
- Adapter errors bubble up as **502**.
|
|
4524
|
+
|
|
4525
|
+
## Admin override
|
|
4526
|
+
|
|
4527
|
+
If the customer is completely stuck, the admin can manually move the order to `paid` / `cancelled` from the orders detail page — same as before, status-change emails trigger automatically.
|
|
4528
|
+
|
|
4529
|
+
|
|
4256
4530
|
---
|
|
4257
4531
|
|
|
4258
4532
|
# Migration Guide
|
package/ROADMAP.md
CHANGED
|
@@ -287,14 +287,35 @@
|
|
|
287
287
|
- [x] `[feature]` `[P2]` Manual poster regeneration in media library file details <!-- files: src/lib/core/server/media/operations/regenerateVideoPoster.ts, src/lib/admin/components/media/file/file-details.svelte -->
|
|
288
288
|
- [x] `[feature]` `[P2]` Config flag `sidebarHelp` to hide admin sidebar Help link (default true) <!-- files: src/lib/types/cms.ts, src/lib/core/cms.ts, src/routes/admin/(afterLogin)/+layout.server.ts, src/lib/admin/components/layout/nav-footer.svelte -->
|
|
289
289
|
|
|
290
|
-
## 0.15.0 —
|
|
290
|
+
## 0.15.0 — Shop MVP (headless e-commerce)
|
|
291
|
+
|
|
292
|
+
- [x] `[feature]` `[P0]` Scaffold: `defineShop`, types, CMSConfig slot, package export `./shop` <!-- files: src/lib/shop/index.ts, src/lib/shop/types.ts -->
|
|
293
|
+
- [x] `[feature]` `[P0]` Drizzle tables: products (entry-backed), variants, orders, order items, status history, payments, shipping methods, stock reservations <!-- files: src/lib/db-postgres/schema/shop/ -->
|
|
294
|
+
- [x] `[feature]` `[P0]` Runtime `schema.ts` generator for drizzle-kit (core + auth + optional shop) <!-- files: src/lib/core/server/generator/generator.ts, src/lib/db-postgres/schema-core.ts, src/lib/db-postgres/schema-shop.ts -->
|
|
295
|
+
- [x] `[feature]` `[P0]` Rate-limit middleware (checkout/webhook) <!-- files: src/lib/shop/rate-limit.ts -->
|
|
296
|
+
- [x] `[feature]` `[P0]` Built-in `shop` field type — makes entries purchasable, auto-hydrated via populateEntryData <!-- files: src/lib/admin/components/fields/shop-field.svelte, src/lib/shop/server/populate.ts -->
|
|
297
|
+
- [x] `[feature]` `[P0]` Net/gross price toggle + live VAT preview in shop field and shipping form
|
|
298
|
+
- [x] `[feature]` `[P0]` Auto default variant on upsert — cart/order always reference variantId
|
|
299
|
+
- [x] `[feature]` `[P0]` Shop admin: products list (JOIN entries × shop_products), shipping methods CRUD with drag-n-drop reorder + free-above threshold
|
|
300
|
+
- [x] `[feature]` `[P0]` Cart (signed cookie) service + `/api/shop/cart` REST + headless SDK `createShopClient()` <!-- files: src/lib/shop/cart/, src/lib/shop/client/ -->
|
|
301
|
+
- [x] `[feature]` `[P0]` Orders + checkout + `manualAdapter` + status emails (inline HTML, PL/EN) <!-- files: src/lib/shop/server/orders.ts, src/lib/shop/http/checkout-handler.ts -->
|
|
302
|
+
- [x] `[feature]` `[P0]` Stock reservation with 30-min TTL — released on cancel/reject, consumed on paid
|
|
303
|
+
- [x] `[feature]` `[P0]` Admin orders view — list (status + email filter), detail (items, history, customer/address/consents, change status, resend email)
|
|
304
|
+
- [x] `[feature]` `[P0]` Random Crockford base32 order numbers (`XXXXX-XXXXX`), unguessable
|
|
305
|
+
- [x] `[feature]` `[P0]` CLI scaffold provisions all shop routes (admin + public API) in consumer app
|
|
306
|
+
- [x] `[feature]` `[P1]` PayU payment adapter + webhook — shipped in 0.15.1 (access-token-gated order view, idempotent webhooks, MD5 signature, shipping↔payment compat, poll fallback)
|
|
307
|
+
- [ ] `[feature]` `[P1]` Stripe payment adapter + webhook — deferred to 0.15.2
|
|
308
|
+
- [ ] `[feature]` `[P2]` InPost carrier adapter (headless geowidget config) — deferred to 0.15.3
|
|
309
|
+
- [x] `[feature]` `[P2]` `nodemailerAdapter` — typowanie `transportOptions` jako `SMTPTransport.Options` (OAuth2, pool, itd.)
|
|
310
|
+
|
|
311
|
+
## 0.16.0 — SEO module
|
|
291
312
|
|
|
292
313
|
- [ ] `[feature]` `[P1]` SERP preview + character limits for title/description <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
|
|
293
314
|
- [ ] `[feature]` `[P1]` Global SEO settings
|
|
294
315
|
- [ ] `[feature]` `[P1]` Dedicated frontend SEO components <!-- files: src/lib/sveltekit/components/seo.svelte -->
|
|
295
316
|
- [ ] `[feature]` `[P2]` Sitemap generation
|
|
296
317
|
|
|
297
|
-
## 0.
|
|
318
|
+
## 0.17.0 — WCAG/ATAG compliance
|
|
298
319
|
|
|
299
320
|
- [ ] `[chore]` `[P0]` Full WCAG/ATAG audit
|
|
300
321
|
- [ ] `[feature]` `[P0]` Accessibility rework based on audit findings
|