includio-cms 0.15.2 → 0.15.3
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 +25 -0
- package/DOCS.md +137 -2
- package/ROADMAP.md +7 -2
- package/dist/admin/client/shop/shipping-method-form.svelte +66 -1
- package/dist/admin/client/shop/shipping-method-form.svelte.d.ts +8 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +101 -0
- package/dist/admin/remote/shop.remote.d.ts +44 -0
- package/dist/admin/remote/shop.remote.js +35 -0
- package/dist/cli/index.js +49 -4
- package/dist/cli/scaffold/admin.d.ts +9 -2
- package/dist/cli/scaffold/admin.js +32 -3
- package/dist/db-postgres/schema/shop/order.d.ts +68 -0
- package/dist/db-postgres/schema/shop/order.js +4 -0
- package/dist/db-postgres/schema/shop/shippingMethod.d.ts +25 -0
- package/dist/db-postgres/schema/shop/shippingMethod.js +1 -0
- package/dist/shop/adapters/inpost/geowidget.d.ts +27 -0
- package/dist/shop/adapters/inpost/geowidget.js +31 -0
- package/dist/shop/adapters/inpost/index.d.ts +89 -0
- package/dist/shop/adapters/inpost/index.js +156 -0
- package/dist/shop/adapters/inpost/payload.d.ts +18 -0
- package/dist/shop/adapters/inpost/payload.js +85 -0
- package/dist/shop/adapters/inpost/points-api.d.ts +17 -0
- package/dist/shop/adapters/inpost/points-api.js +55 -0
- package/dist/shop/adapters/inpost/shipx-client.d.ts +56 -0
- package/dist/shop/adapters/inpost/shipx-client.js +95 -0
- package/dist/shop/adapters/inpost/status-map.d.ts +9 -0
- package/dist/shop/adapters/inpost/status-map.js +46 -0
- package/dist/shop/adapters/inpost/webhook.d.ts +16 -0
- package/dist/shop/adapters/inpost/webhook.js +55 -0
- package/dist/shop/client/index.d.ts +5 -0
- package/dist/shop/http/carrier-handler.d.ts +12 -0
- package/dist/shop/http/carrier-handler.js +45 -0
- package/dist/shop/http/carrier-webhook-handler.d.ts +13 -0
- package/dist/shop/http/carrier-webhook-handler.js +66 -0
- package/dist/shop/http/checkout-handler.js +23 -1
- package/dist/shop/http/index.d.ts +3 -0
- package/dist/shop/http/index.js +3 -0
- package/dist/shop/http/order-handler.js +14 -0
- package/dist/shop/http/shipment-label-handler.d.ts +10 -0
- package/dist/shop/http/shipment-label-handler.js +53 -0
- package/dist/shop/http/shipping-handler.js +3 -0
- package/dist/shop/index.d.ts +3 -1
- package/dist/shop/index.js +1 -0
- package/dist/shop/server/email.js +37 -0
- package/dist/shop/server/orders.d.ts +9 -0
- package/dist/shop/server/orders.js +48 -0
- package/dist/shop/server/shipments.d.ts +33 -0
- package/dist/shop/server/shipments.js +145 -0
- package/dist/shop/server/shipping.d.ts +2 -1
- package/dist/shop/server/shipping.js +9 -0
- package/dist/shop/svelte/InpostPicker.svelte +270 -0
- package/dist/shop/svelte/InpostPicker.svelte.d.ts +51 -0
- package/dist/shop/svelte/OrderStatus.svelte +53 -1
- package/dist/shop/svelte/index.d.ts +1 -0
- package/dist/shop/svelte/index.js +1 -0
- package/dist/shop/svelte/labels.d.ts +5 -0
- package/dist/shop/svelte/labels.js +6 -1
- package/dist/shop/types.d.ts +49 -1
- package/dist/updates/0.15.3/index.d.ts +2 -0
- package/dist/updates/0.15.3/index.js +19 -0
- package/dist/updates/index.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
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.3 — 2026-04-16
|
|
7
|
+
|
|
8
|
+
Shop: InPost carrier adapter — Geowidget v5 picker + ShipX shipment + webhook auto-status.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `inpostAdapter()` ships in `includio-cms/shop` — wires Geowidget v5 (paczkomat picker on the frontend), ShipX (admin "Utwórz przesyłkę InPost" button creates the shipment, auto-buys the offer, fetches the label PDF) and a webhook receiver (`POST /api/shop/carriers/inpost/webhook?secret=...`) that updates order status + tracking number when ShipX events arrive.
|
|
12
|
+
- `<InpostPicker>` Svelte component (`includio-cms/shop/svelte`) — drop-in widget with `bind:value={carrierRef}`, lazy-loads the geowidget script + CSS, hides itself for `inpost_courier_*` services. Raw API alternative: `GET /api/shop/carriers/inpost`.
|
|
13
|
+
- Per-shipping-method service config — `shop_shipping_methods.carrier_config` jsonb with `serviceType` (`inpost_locker_standard` / `inpost_locker_express` / `inpost_courier_standard` / `inpost_courier_express`) and `defaultSize` (A/B/C → small/medium/large). Admin shipping form ungates the InPost option and shows the new fields when selected.
|
|
14
|
+
- Customer order endpoint exposes `trackingNumber` + `trackingUrl` (resolved via `adapter.trackingUrl()`); `<OrderStatus>` renders a "Śledzenie przesyłki" section automatically. Status emails for `preparing` / `sent` / `done` include a tracking block with a clickable carrier link.
|
|
15
|
+
- Admin order detail page gains a "Przesyłka {carrierType}" panel: Utwórz przesyłkę / Pobierz etykietę PDF / Anuluj przesyłkę. PDF is streamed through an admin-auth proxy at `/api/shop/admin/orders/[id]/label`.
|
|
16
|
+
- Checkout validates `carrierRef` against the carrier (Operating-only paczkomats via InPost Points API, in-memory cached 5 min). Invalid selections reject with HTTP 400 before the order is created.
|
|
17
|
+
- CLI scaffold (`pnpm includio scaffold admin`) emits the new routes: `api/shop/carriers/[id]/+server.ts`, `api/shop/carriers/[id]/webhook/+server.ts`, `api/shop/admin/orders/[id]/label/+server.ts`.
|
|
18
|
+
- CLI scaffolder auto-detects shop usage by scanning `src/lib/cms/cms.config.ts` for an active `shop:` property — projects without the shop module get a clean route tree by default, no flag required. Override with `--shop` / `--no-shop` (or pass `scaffoldAdmin({ shop: false })` programmatically). Combined with the existing config-driven schema generator + admin sidebar guard, shop is now fully opt-in with zero footprint when unused.
|
|
19
|
+
|
|
20
|
+
### Migration
|
|
21
|
+
|
|
22
|
+
```sql
|
|
23
|
+
ALTER TABLE shop_shipping_methods ADD COLUMN carrier_config jsonb;
|
|
24
|
+
ALTER TABLE shop_orders ADD COLUMN shipment_id text, ADD COLUMN tracking_number text, ADD COLUMN label_url text, ADD COLUMN shipment_created_at timestamptz;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Notes
|
|
28
|
+
|
|
29
|
+
Run the SQL once, then `pnpm db:push` to sync the rest of the schema. InPost requires two tokens: a public Geowidget v5 token (browser-side, from `manager.paczkomaty.pl`) and a private ShipX organization token (server-side). On sandbox, ShipX `POST /shipments/:id/buy` is asynchronous and often stalls in `offer_selected` because test accounts have no wallet to pay the offer — the webhook reconciles whatever ends up. See /docs/shop/inpost for full setup.
|
|
30
|
+
|
|
6
31
|
## 0.15.2 — 2026-04-15
|
|
7
32
|
|
|
8
33
|
Shop: cena przechowywana jako numeric(20,6) — eliminacja driftu brutto/netto po reload; toggle netto/brutto per wariant.
|
package/DOCS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Includio CMS Documentation (v0.15.
|
|
1
|
+
# Includio CMS Documentation (v0.15.3)
|
|
2
2
|
|
|
3
3
|
> This file is auto-generated from the docs site. For the latest version, update the package.
|
|
4
4
|
|
|
@@ -4311,6 +4311,7 @@ Headless e-commerce module. Optional — activate by adding `shop: defineShop({.
|
|
|
4311
4311
|
- **Cart** — signed cookie, headless SDK (`createShopClient()`), `POST/PATCH/DELETE /api/shop/cart`.
|
|
4312
4312
|
- **Checkout + orders** — `POST /api/shop/checkout` creates an order with consent validation, stock reservation (30-min TTL), and status emails.
|
|
4313
4313
|
- **Payment adapters** — `manualAdapter()` (bank transfer / COD) and `payuAdapter()` ship built-in; plug your own by implementing `PaymentAdapter`.
|
|
4314
|
+
- **Carrier adapters** — `inpostAdapter()` ships built-in (Geowidget v5 picker + ShipX shipments + webhook). Plug other carriers by implementing `CarrierAdapter`.
|
|
4314
4315
|
- **Webhook infrastructure** — `createPaymentWebhookHandler()` dispatches provider callbacks, verifies signatures, is idempotent.
|
|
4315
4316
|
- **Secure order view** — per-order `accessToken` + cookie fallback + `GET /api/shop/orders/[number]` token-gated API.
|
|
4316
4317
|
- **Shipping ↔ payment compatibility** — restrict payment methods per shipping (e.g. no COD for paczkomat).
|
|
@@ -4344,7 +4345,18 @@ export const cmsConfig = defineCMS({
|
|
|
4344
4345
|
|
|
4345
4346
|
## Routes
|
|
4346
4347
|
|
|
4347
|
-
|
|
4348
|
+
Shop is **opt-in**: projects without `shop: defineShop(...)` in their CMS config don't get shop tables in the DB schema (generator gates on `config.shop`), the admin sidebar hides the Shop section, and shop API handlers always return 404.
|
|
4349
|
+
|
|
4350
|
+
The CLI scaffolder **auto-detects** shop usage by scanning your `src/lib/cms/cms.config.ts` for `shop: …`. Override with `--shop` or `--no-shop` if needed:
|
|
4351
|
+
|
|
4352
|
+
```sh
|
|
4353
|
+
pnpm includio scaffold admin # auto: detects shop in cms config
|
|
4354
|
+
pnpm includio scaffold admin --shop # force include shop routes
|
|
4355
|
+
pnpm includio scaffold admin --no-shop # force exclude shop routes
|
|
4356
|
+
pnpm includio scaffold admin --cms-config path/to/config.ts # custom location
|
|
4357
|
+
```
|
|
4358
|
+
|
|
4359
|
+
Scaffold provisions these endpoints (when shop is enabled):
|
|
4348
4360
|
|
|
4349
4361
|
| Path | Handler | Purpose |
|
|
4350
4362
|
|------|---------|---------|
|
|
@@ -4355,11 +4367,15 @@ Scaffold provisions these endpoints in your app:
|
|
|
4355
4367
|
| `POST /api/shop/orders/[number]/refresh-payment` | `createRefreshPaymentHandler()` | Pull status from provider |
|
|
4356
4368
|
| `POST /api/shop/orders/[number]/retry-payment` | `createRetryPaymentHandler()` | New payment attempt |
|
|
4357
4369
|
| `POST /api/shop/webhooks/[provider]` | `createPaymentWebhookHandler()` | Provider callbacks |
|
|
4370
|
+
| `GET /api/shop/carriers/[id]` | `createCarrierConfigHandler()` | Public carrier widget descriptor |
|
|
4371
|
+
| `POST /api/shop/carriers/[id]/webhook` | `createCarrierWebhookHandler()` | Carrier event receiver |
|
|
4372
|
+
| `GET /api/shop/admin/orders/[id]/label` | `createShipmentLabelHandler()` | Admin shipping-label PDF proxy |
|
|
4358
4373
|
|
|
4359
4374
|
## See also
|
|
4360
4375
|
|
|
4361
4376
|
- [Order view](/docs/shop/order-view) — generic `<OrderStatus>` component + headless helper
|
|
4362
4377
|
- [Retry payment](/docs/shop/retry-payment) — lifecycle + endpoint
|
|
4378
|
+
- [InPost carrier](/docs/shop/inpost) — Geowidget v5 + ShipX shipment + webhook
|
|
4363
4379
|
|
|
4364
4380
|
|
|
4365
4381
|
---
|
|
@@ -4527,6 +4543,125 @@ if (url) window.location.href = url;
|
|
|
4527
4543
|
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
4544
|
|
|
4529
4545
|
|
|
4546
|
+
---
|
|
4547
|
+
|
|
4548
|
+
# InPost carrier
|
|
4549
|
+
|
|
4550
|
+
`inpostAdapter()` ships in 0.15.3. Wires three things together:
|
|
4551
|
+
|
|
4552
|
+
1. **Geowidget v5** — customer picks a paczkomat on the checkout page (`<InpostPicker>` Svelte component or raw config endpoint).
|
|
4553
|
+
2. **ShipX shipment** — admin clicks "Utwórz przesyłkę InPost" on the order detail page → backend POSTs the shipment to ShipX, auto-confirms the offer, stores the carrier-side shipment id + tracking number.
|
|
4554
|
+
3. **Webhook** — ShipX events (`confirmed`/`taken_by_courier`/`delivered`) update the order status and tracking number automatically.
|
|
4555
|
+
|
|
4556
|
+
> InPost requires two separate tokens: a **public Geowidget v5 token** (delivered to the browser) and a **private ShipX organization token** (server-side only). Both come from `manager.paczkomaty.pl` (production) or `sandbox-manager.paczkomaty.pl` (sandbox).
|
|
4557
|
+
|
|
4558
|
+
## Config
|
|
4559
|
+
|
|
4560
|
+
```ts
|
|
4561
|
+
import { defineShop, inpostAdapter } from 'includio-cms/shop';
|
|
4562
|
+
|
|
4563
|
+
defineShop({
|
|
4564
|
+
// …currency, payment, etc.
|
|
4565
|
+
carriers: [
|
|
4566
|
+
inpostAdapter({
|
|
4567
|
+
geowidgetToken: process.env.INPOST_GEOWIDGET_TOKEN!,
|
|
4568
|
+
shipxToken: process.env.INPOST_SHIPX_TOKEN!,
|
|
4569
|
+
organizationId: process.env.INPOST_ORG_ID!,
|
|
4570
|
+
environment: 'production', // or 'sandbox'
|
|
4571
|
+
webhookSecret: process.env.INPOST_WEBHOOK_SECRET!,
|
|
4572
|
+
senderAddress: {
|
|
4573
|
+
name: 'Twoja firma',
|
|
4574
|
+
company: 'AriaCMS',
|
|
4575
|
+
street: 'Mokotowska',
|
|
4576
|
+
buildingNumber: '1',
|
|
4577
|
+
city: 'Warszawa',
|
|
4578
|
+
postCode: '00-001',
|
|
4579
|
+
email: 'shop@example.com',
|
|
4580
|
+
phone: '+48500600700'
|
|
4581
|
+
}
|
|
4582
|
+
})
|
|
4583
|
+
]
|
|
4584
|
+
});
|
|
4585
|
+
```
|
|
4586
|
+
|
|
4587
|
+
**Optional opts:**
|
|
4588
|
+
|
|
4589
|
+
- `additionalServices: ['email', 'sms']` — InPost SMS/email notifications. Requires the service to be enabled on your account; sandbox usually doesn't have it.
|
|
4590
|
+
- `autoConfirm: false` — disable the post-create `POST /shipments/:id/buy` step. Default `true`.
|
|
4591
|
+
- `autoConfirmDelayMs` (default `1500`), `autoConfirmPollTimeoutMs` (default `8000`) — tune the offer-prep wait and post-buy polling.
|
|
4592
|
+
- `trackingUrlTemplate` — override `https://inpost.pl/sledzenie-przesylek?number={trackingNumber}`.
|
|
4593
|
+
- `labelFormat: 'Pdf' | 'ZebraLP'`, `labelSize: 'A4' | 'A6'` — default label format.
|
|
4594
|
+
- `debug: true` — verbose logging of the create→buy→poll flow (payload dump, every poll tick). Off by default; warnings + errors always log.
|
|
4595
|
+
|
|
4596
|
+
## Routes scaffolded
|
|
4597
|
+
|
|
4598
|
+
`pnpm includio scaffold admin` provisions:
|
|
4599
|
+
|
|
4600
|
+
| Path | Handler | Purpose |
|
|
4601
|
+
|------|---------|---------|
|
|
4602
|
+
| `GET /api/shop/carriers/[id]` | `createCarrierConfigHandler()` | Public widget descriptor (script URL + token + preset) |
|
|
4603
|
+
| `POST /api/shop/carriers/[id]/webhook` | `createCarrierWebhookHandler()` | ShipX event receiver — secret-gated via `?secret=…` |
|
|
4604
|
+
| `GET /api/shop/admin/orders/[id]/label` | `createShipmentLabelHandler()` | Admin-only PDF proxy for the shipment label |
|
|
4605
|
+
|
|
4606
|
+
In `manager.paczkomaty.pl` configure the webhook URL as
|
|
4607
|
+
`https://your-domain.pl/api/shop/carriers/inpost/webhook?secret=<INPOST_WEBHOOK_SECRET>`.
|
|
4608
|
+
|
|
4609
|
+
## Per-shipping-method service
|
|
4610
|
+
|
|
4611
|
+
Each shipping method with `carrierType=inpost` carries a `carrierConfig`:
|
|
4612
|
+
|
|
4613
|
+
| Field | Values |
|
|
4614
|
+
|------|--------|
|
|
4615
|
+
| `serviceType` | `inpost_locker_standard` (paczkomat), `inpost_locker_express`, `inpost_courier_standard`, `inpost_courier_express` |
|
|
4616
|
+
| `defaultSize` | `A` (small, 8×38×64 cm) / `B` (medium, 19×38×64 cm) / `C` (large, 41×38×64 cm) |
|
|
4617
|
+
|
|
4618
|
+
Set both in admin → Shop → Shipping methods. For `inpost_courier_*` services the picker hides itself and the receiver address from the order is used instead.
|
|
4619
|
+
|
|
4620
|
+
## Frontend picker
|
|
4621
|
+
|
|
4622
|
+
```svelte
|
|
4623
|
+
<InpostPicker
|
|
4624
|
+
bind:value={carrierRef}
|
|
4625
|
+
serviceType="inpost_locker_standard"
|
|
4626
|
+
onSelect={(point) => console.log('picked', point.name, point.address)}
|
|
4627
|
+
/>
|
|
4628
|
+
```
|
|
4629
|
+
|
|
4630
|
+
The component lazy-loads the Geowidget v5 script + CSS, mounts the `<inpost-geowidget>` custom element, and writes the chosen paczkomat code into `value`. For `inpost_courier_*` services it renders a short notice instead.
|
|
4631
|
+
|
|
4632
|
+
**Custom UI** — skip the component and call `GET /api/shop/carriers/inpost` yourself; the response is `{ id, label, widget: { scriptUrl, stylesheetUrl, config: { token, language, config } } }`.
|
|
4633
|
+
|
|
4634
|
+
## Admin actions
|
|
4635
|
+
|
|
4636
|
+
On any order with an InPost shipping method, the order detail page shows:
|
|
4637
|
+
|
|
4638
|
+
- **"Utwórz przesyłkę InPost"** when status is `paid`/`preparing` and no shipment exists yet — POSTs to ShipX, auto-buys the offer, polls until status leaves `offer_selected`, writes `shipmentId` + `trackingNumber` on the order, bumps status to `preparing`.
|
|
4639
|
+
- **"Pobierz etykietę PDF"** — opens the admin label proxy in a new tab.
|
|
4640
|
+
- **"Anuluj przesyłkę"** — `DELETE /v1/shipments/:id` on ShipX, clears local data. Only works after the shipment reaches `confirmed`.
|
|
4641
|
+
|
|
4642
|
+
## Webhook → status mapping
|
|
4643
|
+
|
|
4644
|
+
| ShipX event status | Order status |
|
|
4645
|
+
|---|---|
|
|
4646
|
+
| `created`, `offers_prepared`, `offer_selected`, `confirmed`, `dispatched_by_sender` | `preparing` |
|
|
4647
|
+
| `taken_by_courier`, `adopted_at_*`, `out_for_delivery`, `ready_to_pickup` | `sent` |
|
|
4648
|
+
| `delivered` | `done` |
|
|
4649
|
+
| `canceled`, `returned_to_sender`, `rejected` | `cancelled` |
|
|
4650
|
+
| anything else | (ignored) |
|
|
4651
|
+
|
|
4652
|
+
Tracking number is updated whenever the webhook payload contains a non-null `tracking_number`. Status emails (built-in PL/EN templates) include a tracking block on `preparing`/`sent`/`done`.
|
|
4653
|
+
|
|
4654
|
+
## Sandbox quirks
|
|
4655
|
+
|
|
4656
|
+
- ShipX `POST /shipments/:id/buy` is **asynchronous**. Production confirms in ~1–3 s; sandbox often **stalls in `offer_selected`** because test accounts have no wallet to pay the offer. The adapter polls for ~8 s then gives up and lets the webhook reconcile.
|
|
4657
|
+
- Sandbox geowidget paczkomaty are **separate** from production codes. Use `KRA012` (Kraków, Os. Kombatantów 20) or any code from `GET https://sandbox-api-shipx-pl.easypack24.net/v1/points?per_page=10`.
|
|
4658
|
+
- Sandbox `additional_services: ['email', 'sms']` returns `unavailable` — drop the option for sandbox testing.
|
|
4659
|
+
|
|
4660
|
+
## Customer tracking display
|
|
4661
|
+
|
|
4662
|
+
`OrderStatus.svelte` (the built-in component) renders a "Śledzenie przesyłki" section automatically when the order has a `trackingNumber`. If you wrote a custom order view, read `order.trackingNumber` and `order.trackingUrl` from the order endpoint response and render them yourself.
|
|
4663
|
+
|
|
4664
|
+
|
|
4530
4665
|
---
|
|
4531
4666
|
|
|
4532
4667
|
# Migration Guide
|
package/ROADMAP.md
CHANGED
|
@@ -304,8 +304,8 @@
|
|
|
304
304
|
- [x] `[feature]` `[P0]` Random Crockford base32 order numbers (`XXXXX-XXXXX`), unguessable
|
|
305
305
|
- [x] `[feature]` `[P0]` CLI scaffold provisions all shop routes (admin + public API) in consumer app
|
|
306
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.
|
|
308
|
-
- [
|
|
307
|
+
- [ ] `[feature]` `[P1]` Stripe payment adapter + webhook — deferred to 0.15.4
|
|
308
|
+
- [x] `[feature]` `[P2]` InPost carrier adapter — shipped in 0.15.3 (Geowidget v5 picker + ShipX shipments + auto-confirm + webhook status updates)
|
|
309
309
|
|
|
310
310
|
## 0.15.2 — Price precision fix
|
|
311
311
|
|
|
@@ -313,6 +313,11 @@
|
|
|
313
313
|
- [x] `[feature]` `[P2]` Variant price toggle netto/brutto (spójne z toggle\'em ceny bazowej)
|
|
314
314
|
- [x] `[feature]` `[P2]` `nodemailerAdapter` — typowanie `transportOptions` jako `SMTPTransport.Options` (OAuth2, pool, itd.)
|
|
315
315
|
|
|
316
|
+
## 0.15.3 — InPost carrier
|
|
317
|
+
|
|
318
|
+
- [x] `[feature]` `[P2]` `inpostAdapter()` — Geowidget v5 (`<InpostPicker>` Svelte + raw config endpoint), ShipX shipment create + auto-buy + label PDF + cancel, webhook → status + tracking, per-shipping-method service config, customer tracking display in `<OrderStatus>` + email templates
|
|
319
|
+
- [x] `[chore]` `[P2]` Verbose logging on adapter auto-confirm flow (offer prep wait, buy POST, polling) for sandbox debugging
|
|
320
|
+
|
|
316
321
|
## 0.16.0 — SEO module
|
|
317
322
|
|
|
318
323
|
- [ ] `[feature]` `[P1]` SERP preview + character limits for title/description <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
import { Switch } from '../../../components/ui/switch/index.js';
|
|
4
4
|
|
|
5
5
|
type CarrierType = 'none' | 'inpost';
|
|
6
|
+
type InpostServiceType =
|
|
7
|
+
| 'inpost_locker_standard'
|
|
8
|
+
| 'inpost_locker_express'
|
|
9
|
+
| 'inpost_courier_standard'
|
|
10
|
+
| 'inpost_courier_express';
|
|
11
|
+
type InpostParcelSize = 'A' | 'B' | 'C';
|
|
12
|
+
interface CarrierConfig {
|
|
13
|
+
serviceType?: InpostServiceType | string;
|
|
14
|
+
defaultSize?: InpostParcelSize | string;
|
|
15
|
+
}
|
|
6
16
|
export interface PaymentMethodOption {
|
|
7
17
|
id: string;
|
|
8
18
|
label: Record<string, string>;
|
|
@@ -13,6 +23,7 @@
|
|
|
13
23
|
price: number; // PLN (number, netto)
|
|
14
24
|
vatRate: number;
|
|
15
25
|
carrierType: CarrierType;
|
|
26
|
+
carrierConfig: CarrierConfig | null;
|
|
16
27
|
conditions: { freeAbove?: number } | null; // freeAbove: grosze
|
|
17
28
|
allowedPaymentMethods: string[] | null;
|
|
18
29
|
isActive: boolean;
|
|
@@ -24,6 +35,7 @@
|
|
|
24
35
|
price?: number | string;
|
|
25
36
|
vatRate?: number;
|
|
26
37
|
carrierType?: string;
|
|
38
|
+
carrierConfig?: CarrierConfig | null;
|
|
27
39
|
conditions?: { freeAbove?: number } | null;
|
|
28
40
|
allowedPaymentMethods?: string[] | null;
|
|
29
41
|
isActive?: boolean;
|
|
@@ -97,6 +109,12 @@
|
|
|
97
109
|
inputPrice = formatPln(preserved);
|
|
98
110
|
}
|
|
99
111
|
let carrierType = $state<CarrierType>((initial?.carrierType as CarrierType) ?? 'none');
|
|
112
|
+
let inpostServiceType = $state<InpostServiceType>(
|
|
113
|
+
(initial?.carrierConfig?.serviceType as InpostServiceType) ?? 'inpost_locker_standard'
|
|
114
|
+
);
|
|
115
|
+
let inpostDefaultSize = $state<InpostParcelSize>(
|
|
116
|
+
(initial?.carrierConfig?.defaultSize as InpostParcelSize) ?? 'A'
|
|
117
|
+
);
|
|
100
118
|
let isActive = $state(initial?.isActive ?? true);
|
|
101
119
|
// null/undefined = no restriction (all allowed); otherwise a whitelist.
|
|
102
120
|
let restrictPayments = $state(
|
|
@@ -132,6 +150,10 @@
|
|
|
132
150
|
price: netPln,
|
|
133
151
|
vatRate: Number(vatRate),
|
|
134
152
|
carrierType,
|
|
153
|
+
carrierConfig:
|
|
154
|
+
carrierType === 'inpost'
|
|
155
|
+
? { serviceType: inpostServiceType, defaultSize: inpostDefaultSize }
|
|
156
|
+
: null,
|
|
135
157
|
conditions: freeAboveEnabled
|
|
136
158
|
? { freeAbove: Math.round(parseFloat(freeAbove || '0') * 100) }
|
|
137
159
|
: null,
|
|
@@ -300,9 +322,52 @@
|
|
|
300
322
|
<span class="mb-1 block text-sm font-semibold">Typ</span>
|
|
301
323
|
<select bind:value={carrierType} class="border-border w-full rounded-lg border px-3 py-2">
|
|
302
324
|
<option value="none">Bez integracji (adres)</option>
|
|
303
|
-
<option value="inpost"
|
|
325
|
+
<option value="inpost">InPost</option>
|
|
304
326
|
</select>
|
|
305
327
|
</label>
|
|
328
|
+
|
|
329
|
+
{#if carrierType === 'inpost'}
|
|
330
|
+
<div class="space-y-4 border-l-2 border-primary/30 pl-4">
|
|
331
|
+
<p class="text-muted-foreground text-sm">
|
|
332
|
+
Wymaga skonfigurowanego adaptera <code>inpostAdapter</code> w <code>cms.config.ts</code>.
|
|
333
|
+
</p>
|
|
334
|
+
|
|
335
|
+
<label class="block">
|
|
336
|
+
<span class="mb-1 block text-sm font-semibold">Usługa ShipX</span>
|
|
337
|
+
<select
|
|
338
|
+
bind:value={inpostServiceType}
|
|
339
|
+
class="border-border w-full rounded-lg border px-3 py-2"
|
|
340
|
+
>
|
|
341
|
+
<option value="inpost_locker_standard">Paczkomat — standard</option>
|
|
342
|
+
<option value="inpost_locker_express">Paczkomat — Express</option>
|
|
343
|
+
<option value="inpost_courier_standard">Kurier — standard</option>
|
|
344
|
+
<option value="inpost_courier_express">Kurier — Express</option>
|
|
345
|
+
</select>
|
|
346
|
+
</label>
|
|
347
|
+
|
|
348
|
+
<label class="block">
|
|
349
|
+
<span class="mb-1 block text-sm font-semibold">Domyślny rozmiar paczki</span>
|
|
350
|
+
<select
|
|
351
|
+
bind:value={inpostDefaultSize}
|
|
352
|
+
class="border-border w-full rounded-lg border px-3 py-2"
|
|
353
|
+
>
|
|
354
|
+
<option value="A">A — mała (8 × 38 × 64 cm)</option>
|
|
355
|
+
<option value="B">B — średnia (19 × 38 × 64 cm)</option>
|
|
356
|
+
<option value="C">C — duża (41 × 38 × 64 cm)</option>
|
|
357
|
+
</select>
|
|
358
|
+
</label>
|
|
359
|
+
|
|
360
|
+
{#if inpostServiceType.startsWith('inpost_courier_')}
|
|
361
|
+
<p class="text-muted-foreground text-xs">
|
|
362
|
+
Kurier dostarcza pod adres — klient nie wybiera paczkomatu.
|
|
363
|
+
</p>
|
|
364
|
+
{:else}
|
|
365
|
+
<p class="text-muted-foreground text-xs">
|
|
366
|
+
Klient wybiera paczkomat odbiorczy w geowidgetcie InPost.
|
|
367
|
+
</p>
|
|
368
|
+
{/if}
|
|
369
|
+
</div>
|
|
370
|
+
{/if}
|
|
306
371
|
</section>
|
|
307
372
|
|
|
308
373
|
<div class="flex gap-3">
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
type CarrierType = 'none' | 'inpost';
|
|
2
|
+
type InpostServiceType = 'inpost_locker_standard' | 'inpost_locker_express' | 'inpost_courier_standard' | 'inpost_courier_express';
|
|
3
|
+
type InpostParcelSize = 'A' | 'B' | 'C';
|
|
4
|
+
interface CarrierConfig {
|
|
5
|
+
serviceType?: InpostServiceType | string;
|
|
6
|
+
defaultSize?: InpostParcelSize | string;
|
|
7
|
+
}
|
|
2
8
|
export interface PaymentMethodOption {
|
|
3
9
|
id: string;
|
|
4
10
|
label: Record<string, string>;
|
|
@@ -9,6 +15,7 @@ export interface ShippingFormPayload {
|
|
|
9
15
|
price: number;
|
|
10
16
|
vatRate: number;
|
|
11
17
|
carrierType: CarrierType;
|
|
18
|
+
carrierConfig: CarrierConfig | null;
|
|
12
19
|
conditions: {
|
|
13
20
|
freeAbove?: number;
|
|
14
21
|
} | null;
|
|
@@ -22,6 +29,7 @@ export interface ShippingFormInitial {
|
|
|
22
29
|
price?: number | string;
|
|
23
30
|
vatRate?: number;
|
|
24
31
|
carrierType?: string;
|
|
32
|
+
carrierConfig?: CarrierConfig | null;
|
|
25
33
|
conditions?: {
|
|
26
34
|
freeAbove?: number;
|
|
27
35
|
} | null;
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
let note = $state('');
|
|
46
46
|
let saving = $state(false);
|
|
47
47
|
let resending = $state(false);
|
|
48
|
+
let shipping = $state(false);
|
|
48
49
|
let errorMessage = $state<string | null>(null);
|
|
49
50
|
let successMessage = $state<string | null>(null);
|
|
50
51
|
|
|
@@ -102,6 +103,49 @@
|
|
|
102
103
|
resending = false;
|
|
103
104
|
}
|
|
104
105
|
}
|
|
106
|
+
|
|
107
|
+
async function handleCreateShipment() {
|
|
108
|
+
shipping = true;
|
|
109
|
+
errorMessage = null;
|
|
110
|
+
successMessage = null;
|
|
111
|
+
try {
|
|
112
|
+
const result = await remotes.createShipmentForOrderCmd({ orderId });
|
|
113
|
+
if (!result.success) {
|
|
114
|
+
errorMessage = result.error;
|
|
115
|
+
} else {
|
|
116
|
+
successMessage = result.trackingNumber
|
|
117
|
+
? `Przesyłka utworzona — ${result.trackingNumber}`
|
|
118
|
+
: 'Przesyłka utworzona. Numer śledzenia pojawi się po potwierdzeniu w ShipX.';
|
|
119
|
+
await query.refresh();
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
errorMessage =
|
|
123
|
+
err instanceof Error ? err.message : 'Błąd przy tworzeniu przesyłki';
|
|
124
|
+
} finally {
|
|
125
|
+
shipping = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function handleCancelShipment() {
|
|
130
|
+
if (!confirm('Anulować przesyłkę? Etykieta zostanie unieważniona w ShipX.')) return;
|
|
131
|
+
shipping = true;
|
|
132
|
+
errorMessage = null;
|
|
133
|
+
successMessage = null;
|
|
134
|
+
try {
|
|
135
|
+
const result = await remotes.cancelShipmentForOrderCmd({ orderId });
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
errorMessage = result.error;
|
|
138
|
+
} else {
|
|
139
|
+
successMessage = 'Przesyłka anulowana.';
|
|
140
|
+
await query.refresh();
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
errorMessage =
|
|
144
|
+
err instanceof Error ? err.message : 'Błąd przy anulowaniu przesyłki';
|
|
145
|
+
} finally {
|
|
146
|
+
shipping = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
105
149
|
</script>
|
|
106
150
|
|
|
107
151
|
{#if !query.ready}
|
|
@@ -268,6 +312,63 @@
|
|
|
268
312
|
</Button>
|
|
269
313
|
</section>
|
|
270
314
|
|
|
315
|
+
{#if order.carrierType && order.carrierType !== 'none'}
|
|
316
|
+
<section class="border-border bg-card space-y-3 rounded-xl border p-5 text-sm">
|
|
317
|
+
<h2 class="text-base font-bold">
|
|
318
|
+
Przesyłka <span class="text-muted-foreground font-mono text-xs">{order.carrierType}</span>
|
|
319
|
+
</h2>
|
|
320
|
+
{#if order.shipmentId}
|
|
321
|
+
<div class="space-y-1">
|
|
322
|
+
<div class="text-muted-foreground text-xs font-semibold">Shipment ID</div>
|
|
323
|
+
<div class="font-mono text-xs break-all">{order.shipmentId}</div>
|
|
324
|
+
</div>
|
|
325
|
+
{#if order.trackingNumber}
|
|
326
|
+
<div class="space-y-1">
|
|
327
|
+
<div class="text-muted-foreground text-xs font-semibold">Numer śledzenia</div>
|
|
328
|
+
<div class="font-mono text-xs break-all">{order.trackingNumber}</div>
|
|
329
|
+
</div>
|
|
330
|
+
{:else}
|
|
331
|
+
<p class="text-xs text-amber-700">
|
|
332
|
+
Numer śledzenia pojawi się gdy ShipX potwierdzi przesyłkę (zwykle w kilka minut).
|
|
333
|
+
</p>
|
|
334
|
+
{/if}
|
|
335
|
+
<div class="flex flex-wrap gap-2 pt-2">
|
|
336
|
+
<Button
|
|
337
|
+
href="/api/shop/admin/orders/{orderId}/label"
|
|
338
|
+
variant="outline"
|
|
339
|
+
size="sm"
|
|
340
|
+
target="_blank"
|
|
341
|
+
>
|
|
342
|
+
Pobierz etykietę PDF
|
|
343
|
+
</Button>
|
|
344
|
+
<Button
|
|
345
|
+
onclick={handleCancelShipment}
|
|
346
|
+
disabled={shipping}
|
|
347
|
+
variant="outline"
|
|
348
|
+
size="sm"
|
|
349
|
+
>
|
|
350
|
+
Anuluj przesyłkę
|
|
351
|
+
</Button>
|
|
352
|
+
</div>
|
|
353
|
+
{:else if order.status === 'paid' || order.status === 'preparing'}
|
|
354
|
+
<p class="text-muted-foreground text-xs">
|
|
355
|
+
Wygeneruj etykietę i nadaj paczkę przez ShipX.
|
|
356
|
+
</p>
|
|
357
|
+
<Button
|
|
358
|
+
onclick={handleCreateShipment}
|
|
359
|
+
disabled={shipping}
|
|
360
|
+
class="w-full"
|
|
361
|
+
>
|
|
362
|
+
{shipping ? 'Tworzenie…' : 'Utwórz przesyłkę InPost'}
|
|
363
|
+
</Button>
|
|
364
|
+
{:else}
|
|
365
|
+
<p class="text-muted-foreground text-xs">
|
|
366
|
+
Przesyłkę można utworzyć po opłaceniu zamówienia.
|
|
367
|
+
</p>
|
|
368
|
+
{/if}
|
|
369
|
+
</section>
|
|
370
|
+
{/if}
|
|
371
|
+
|
|
271
372
|
<section class="border-border bg-card space-y-2 rounded-xl border p-5 text-sm">
|
|
272
373
|
<h2 class="text-base font-bold">Klient</h2>
|
|
273
374
|
<div>
|
|
@@ -39,6 +39,10 @@ export declare const createShippingMethodCmd: import("@sveltejs/kit").RemoteComm
|
|
|
39
39
|
vatRate: number;
|
|
40
40
|
description?: Record<string, string> | null | undefined;
|
|
41
41
|
carrierType?: string | undefined;
|
|
42
|
+
carrierConfig?: {
|
|
43
|
+
serviceType?: string | undefined;
|
|
44
|
+
defaultSize?: string | undefined;
|
|
45
|
+
} | null | undefined;
|
|
42
46
|
conditions?: {
|
|
43
47
|
freeAbove?: number | undefined;
|
|
44
48
|
} | null | undefined;
|
|
@@ -54,6 +58,10 @@ export declare const updateShippingMethodCmd: import("@sveltejs/kit").RemoteComm
|
|
|
54
58
|
price?: number | undefined;
|
|
55
59
|
vatRate?: number | undefined;
|
|
56
60
|
carrierType?: string | undefined;
|
|
61
|
+
carrierConfig?: {
|
|
62
|
+
serviceType?: string | undefined;
|
|
63
|
+
defaultSize?: string | undefined;
|
|
64
|
+
} | null | undefined;
|
|
57
65
|
conditions?: {
|
|
58
66
|
freeAbove?: number | undefined;
|
|
59
67
|
} | null | undefined;
|
|
@@ -99,6 +107,10 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
|
|
|
99
107
|
shippingGross: number;
|
|
100
108
|
shippingMethodId: string | null;
|
|
101
109
|
carrierRef: string | null;
|
|
110
|
+
shipmentId: string | null;
|
|
111
|
+
trackingNumber: string | null;
|
|
112
|
+
labelUrl: string | null;
|
|
113
|
+
shipmentCreatedAt: Date | null;
|
|
102
114
|
paymentMethod: string | null;
|
|
103
115
|
paymentProviderRef: string | null;
|
|
104
116
|
notes: string | null;
|
|
@@ -130,6 +142,10 @@ export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFuncti
|
|
|
130
142
|
shippingGross: number;
|
|
131
143
|
shippingMethodId: string | null;
|
|
132
144
|
carrierRef: string | null;
|
|
145
|
+
shipmentId: string | null;
|
|
146
|
+
trackingNumber: string | null;
|
|
147
|
+
labelUrl: string | null;
|
|
148
|
+
shipmentCreatedAt: Date | null;
|
|
133
149
|
paymentMethod: string | null;
|
|
134
150
|
paymentProviderRef: string | null;
|
|
135
151
|
notes: string | null;
|
|
@@ -185,6 +201,10 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
|
|
|
185
201
|
shippingGross: number;
|
|
186
202
|
shippingMethodId: string | null;
|
|
187
203
|
carrierRef: string | null;
|
|
204
|
+
shipmentId: string | null;
|
|
205
|
+
trackingNumber: string | null;
|
|
206
|
+
labelUrl: string | null;
|
|
207
|
+
shipmentCreatedAt: Date | null;
|
|
188
208
|
paymentMethod: string | null;
|
|
189
209
|
paymentProviderRef: string | null;
|
|
190
210
|
notes: string | null;
|
|
@@ -195,6 +215,30 @@ export declare const resendOrderEmailCmd: import("@sveltejs/kit").RemoteCommand<
|
|
|
195
215
|
}, Promise<{
|
|
196
216
|
success: boolean;
|
|
197
217
|
}>>;
|
|
218
|
+
export declare const createShipmentForOrderCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
219
|
+
orderId: string;
|
|
220
|
+
}, Promise<{
|
|
221
|
+
success: true;
|
|
222
|
+
shipmentId: string | null;
|
|
223
|
+
trackingNumber: string | null;
|
|
224
|
+
status: import("../../shop/types.js").OrderStatus;
|
|
225
|
+
error?: undefined;
|
|
226
|
+
} | {
|
|
227
|
+
success: false;
|
|
228
|
+
error: string;
|
|
229
|
+
shipmentId?: undefined;
|
|
230
|
+
trackingNumber?: undefined;
|
|
231
|
+
status?: undefined;
|
|
232
|
+
}>>;
|
|
233
|
+
export declare const cancelShipmentForOrderCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
234
|
+
orderId: string;
|
|
235
|
+
}, Promise<{
|
|
236
|
+
success: true;
|
|
237
|
+
error?: undefined;
|
|
238
|
+
} | {
|
|
239
|
+
success: false;
|
|
240
|
+
error: string;
|
|
241
|
+
}>>;
|
|
198
242
|
export declare const listShopableCollections: import("@sveltejs/kit").RemoteQueryFunction<void, {
|
|
199
243
|
slug: string;
|
|
200
244
|
labels: {
|
|
@@ -4,6 +4,7 @@ import { getCMS } from '../../core/cms.js';
|
|
|
4
4
|
import { deleteShopData, getShopDataByEntry, listShopEntries, upsertShopData } from '../../shop/server/shop-data.js';
|
|
5
5
|
import { createShippingMethod, deleteShippingMethod, getShippingMethod, listShippingMethods, reorderShippingMethods, updateShippingMethod } from '../../shop/server/shipping.js';
|
|
6
6
|
import { getOrderById, getOrderItems, getOrderStatusHistory, listOrders, updateOrderStatus } from '../../shop/server/orders.js';
|
|
7
|
+
import { cancelShipmentForOrder, createShipmentForOrder } from '../../shop/server/shipments.js';
|
|
7
8
|
import { sendOrderStatusEmail } from '../../shop/server/email.js';
|
|
8
9
|
import { requireAuth } from './middleware/auth.js';
|
|
9
10
|
export const getShopEnabled = query(async () => {
|
|
@@ -63,6 +64,13 @@ const shippingMethodInputSchema = z.object({
|
|
|
63
64
|
price: z.number().nonnegative().max(1e9), // PLN netto (≤6dp)
|
|
64
65
|
vatRate: z.number().int().min(0).max(100),
|
|
65
66
|
carrierType: z.string().optional(),
|
|
67
|
+
carrierConfig: z
|
|
68
|
+
.object({
|
|
69
|
+
serviceType: z.string().optional(),
|
|
70
|
+
defaultSize: z.string().optional()
|
|
71
|
+
})
|
|
72
|
+
.nullable()
|
|
73
|
+
.optional(),
|
|
66
74
|
conditions: z
|
|
67
75
|
.object({ freeAbove: z.number().int().nonnegative().optional() })
|
|
68
76
|
.nullable()
|
|
@@ -143,6 +151,33 @@ export const resendOrderEmailCmd = command(z.object({ orderId: z.string(), statu
|
|
|
143
151
|
await sendOrderStatusEmail(orderId, status);
|
|
144
152
|
return { success: true };
|
|
145
153
|
});
|
|
154
|
+
export const createShipmentForOrderCmd = command(z.object({ orderId: z.string() }), async ({ orderId }) => {
|
|
155
|
+
requireAuth();
|
|
156
|
+
try {
|
|
157
|
+
const order = await createShipmentForOrder(orderId);
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
shipmentId: order.shipmentId,
|
|
161
|
+
trackingNumber: order.trackingNumber,
|
|
162
|
+
status: order.status
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
const message = err instanceof Error ? err.message : 'Shipment create failed';
|
|
167
|
+
return { success: false, error: message };
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
export const cancelShipmentForOrderCmd = command(z.object({ orderId: z.string() }), async ({ orderId }) => {
|
|
171
|
+
requireAuth();
|
|
172
|
+
try {
|
|
173
|
+
await cancelShipmentForOrder(orderId);
|
|
174
|
+
return { success: true };
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const message = err instanceof Error ? err.message : 'Shipment cancel failed';
|
|
178
|
+
return { success: false, error: message };
|
|
179
|
+
}
|
|
180
|
+
});
|
|
146
181
|
export const listShopableCollections = query(async () => {
|
|
147
182
|
requireAuth();
|
|
148
183
|
const cms = getCMS();
|