includio-cms 0.15.0 → 0.15.2

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/DOCS.md +231 -1
  3. package/ROADMAP.md +7 -2
  4. package/dist/admin/client/shop/shipping-method-edit-page.svelte +1 -0
  5. package/dist/admin/client/shop/shipping-method-form.svelte +89 -21
  6. package/dist/admin/client/shop/shipping-method-form.svelte.d.ts +8 -1
  7. package/dist/admin/client/shop/shipping-method-new-page.svelte +1 -0
  8. package/dist/admin/client/shop/shipping-methods-list-page.svelte +7 -4
  9. package/dist/admin/client/shop/shop-products-list-page.svelte +2 -2
  10. package/dist/admin/components/fields/shop-field.svelte +63 -22
  11. package/dist/admin/remote/shop.remote.d.ts +16 -56
  12. package/dist/admin/remote/shop.remote.js +6 -4
  13. package/dist/cli/scaffold/admin.js +32 -0
  14. package/dist/db-postgres/schema/shop/order.d.ts +34 -0
  15. package/dist/db-postgres/schema/shop/order.js +2 -0
  16. package/dist/db-postgres/schema/shop/product.d.ts +4 -4
  17. package/dist/db-postgres/schema/shop/product.js +3 -2
  18. package/dist/db-postgres/schema/shop/productVariant.d.ts +4 -4
  19. package/dist/db-postgres/schema/shop/productVariant.js +3 -2
  20. package/dist/db-postgres/schema/shop/shippingMethod.d.ts +23 -4
  21. package/dist/db-postgres/schema/shop/shippingMethod.js +4 -2
  22. package/dist/shop/adapters/payu/client.d.ts +22 -0
  23. package/dist/shop/adapters/payu/client.js +78 -0
  24. package/dist/shop/adapters/payu/index.d.ts +24 -0
  25. package/dist/shop/adapters/payu/index.js +88 -0
  26. package/dist/shop/adapters/payu/payload.d.ts +48 -0
  27. package/dist/shop/adapters/payu/payload.js +48 -0
  28. package/dist/shop/adapters/payu/signature.d.ts +12 -0
  29. package/dist/shop/adapters/payu/signature.js +50 -0
  30. package/dist/shop/adapters/payu/status-map.d.ts +3 -0
  31. package/dist/shop/adapters/payu/status-map.js +14 -0
  32. package/dist/shop/cart/order-token-cookie.d.ts +9 -0
  33. package/dist/shop/cart/order-token-cookie.js +40 -0
  34. package/dist/shop/client/index.d.ts +64 -1
  35. package/dist/shop/client/index.js +9 -0
  36. package/dist/shop/client/use-order.svelte.d.ts +32 -0
  37. package/dist/shop/client/use-order.svelte.js +105 -0
  38. package/dist/shop/http/checkout-handler.js +47 -4
  39. package/dist/shop/http/index.d.ts +4 -0
  40. package/dist/shop/http/index.js +4 -0
  41. package/dist/shop/http/order-handler.d.ts +4 -0
  42. package/dist/shop/http/order-handler.js +85 -0
  43. package/dist/shop/http/refresh-payment-handler.d.ts +4 -0
  44. package/dist/shop/http/refresh-payment-handler.js +73 -0
  45. package/dist/shop/http/retry-payment-handler.d.ts +4 -0
  46. package/dist/shop/http/retry-payment-handler.js +99 -0
  47. package/dist/shop/http/retry-payment-logic.d.ts +2 -0
  48. package/dist/shop/http/retry-payment-logic.js +4 -0
  49. package/dist/shop/http/shipping-handler.js +2 -1
  50. package/dist/shop/http/webhook-handler.d.ts +4 -0
  51. package/dist/shop/http/webhook-handler.js +73 -0
  52. package/dist/shop/http/webhook-logic.d.ts +4 -0
  53. package/dist/shop/http/webhook-logic.js +21 -0
  54. package/dist/shop/index.d.ts +3 -1
  55. package/dist/shop/index.js +3 -1
  56. package/dist/shop/pricing.d.ts +4 -0
  57. package/dist/shop/pricing.js +18 -0
  58. package/dist/shop/server/cart-hydrate.js +6 -3
  59. package/dist/shop/server/email.js +18 -2
  60. package/dist/shop/server/order-access-url.d.ts +7 -0
  61. package/dist/shop/server/order-access-url.js +6 -0
  62. package/dist/shop/server/orders.d.ts +1 -0
  63. package/dist/shop/server/orders.js +12 -0
  64. package/dist/shop/server/payment-compat.d.ts +5 -0
  65. package/dist/shop/server/payment-compat.js +9 -0
  66. package/dist/shop/server/populate.d.ts +2 -0
  67. package/dist/shop/server/shipping.d.ts +12 -4
  68. package/dist/shop/server/shipping.js +24 -14
  69. package/dist/shop/server/shop-data.d.ts +8 -2
  70. package/dist/shop/server/shop-data.js +18 -10
  71. package/dist/shop/svelte/OrderStatus.svelte +368 -0
  72. package/dist/shop/svelte/OrderStatus.svelte.d.ts +14 -0
  73. package/dist/shop/svelte/index.d.ts +3 -0
  74. package/dist/shop/svelte/index.js +2 -0
  75. package/dist/shop/svelte/labels.d.ts +25 -0
  76. package/dist/shop/svelte/labels.js +41 -0
  77. package/dist/shop/types.d.ts +19 -1
  78. package/dist/updates/0.15.1/index.d.ts +2 -0
  79. package/dist/updates/0.15.1/index.js +27 -0
  80. package/dist/updates/0.15.2/index.d.ts +2 -0
  81. package/dist/updates/0.15.2/index.js +18 -0
  82. package/dist/updates/index.js +3 -1
  83. package/package.json +5 -1
@@ -6,6 +6,7 @@ import { hydrateCart } from './cart-hydrate.js';
6
6
  import { resolveShippingPrice } from './shipping.js';
7
7
  import { generateOrderNumber } from './order-number.js';
8
8
  import { sendOrderStatusEmail } from './email.js';
9
+ import { isPaymentMethodAllowed } from './payment-compat.js';
9
10
  const STOCK_RESERVATION_TTL_MINUTES = 30;
10
11
  async function purgeExpiredReservations() {
11
12
  const db = getShopDb();
@@ -60,6 +61,10 @@ export async function createOrderFromCart(input) {
60
61
  if (!shippingMethod || !shippingMethod.isActive) {
61
62
  throw new Error('Shipping method not found or inactive.');
62
63
  }
64
+ // Shipping ↔ payment compatibility: empty/null list = all payment methods allowed.
65
+ if (!isPaymentMethodAllowed(input.paymentMethod, shippingMethod.allowedPaymentMethods)) {
66
+ throw new Error(`Payment method "${input.paymentMethod}" not available for the selected shipping method.`);
67
+ }
63
68
  // Hydrate cart server-side (authoritative pricing)
64
69
  const snapshot = await hydrateCart(input.cartItems, { language: input.language });
65
70
  if (snapshot.items.length === 0 || snapshot.itemCount === 0) {
@@ -244,6 +249,13 @@ export async function updateOrderStatus(orderId, status, opts = {}) {
244
249
  void sendOrderStatusEmail(orderId, status);
245
250
  return updated;
246
251
  }
252
+ export async function setPaymentProviderRef(orderId, ref) {
253
+ const db = getShopDb();
254
+ await db
255
+ .update(shopOrdersTable)
256
+ .set({ paymentProviderRef: ref, updatedAt: new Date() })
257
+ .where(eq(shopOrdersTable.id, orderId));
258
+ }
247
259
  export async function getOrderById(id) {
248
260
  const db = getShopDb();
249
261
  const [row] = await db.select().from(shopOrdersTable).where(eq(shopOrdersTable.id, id));
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Returns true when the payment method is allowed for the given shipping method.
3
+ * `allowedPaymentMethods` null/undefined/empty means "no restrictions".
4
+ */
5
+ export declare function isPaymentMethodAllowed(paymentMethod: string, allowedPaymentMethods: string[] | null | undefined): boolean;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Returns true when the payment method is allowed for the given shipping method.
3
+ * `allowedPaymentMethods` null/undefined/empty means "no restrictions".
4
+ */
5
+ export function isPaymentMethodAllowed(paymentMethod, allowedPaymentMethods) {
6
+ if (!allowedPaymentMethods || allowedPaymentMethods.length === 0)
7
+ return true;
8
+ return allowedPaymentMethods.includes(paymentMethod);
9
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Field } from '../../types/fields.js';
2
2
  export interface PopulatedShopField {
3
+ /** Netto w PLN (number, ≤6dp). Od 0.15.2. */
3
4
  basePrice: number;
4
5
  vatRate: number;
5
6
  isActive: boolean;
@@ -7,6 +8,7 @@ export interface PopulatedShopField {
7
8
  id: string;
8
9
  sku: string | null;
9
10
  name: Record<string, string> | null;
11
+ /** Netto delta w PLN (number, ≤6dp). Od 0.15.2. */
10
12
  priceDelta: number;
11
13
  stock: number | null;
12
14
  attributes: Record<string, string> | null;
@@ -1,6 +1,9 @@
1
1
  import { shopShippingMethodsTable } from '../../db-postgres/schema/shop/index.js';
2
2
  import type { ShopCarrierType } from '../../db-postgres/schema/shop/shippingMethod.js';
3
- export type ShippingMethodRow = typeof shopShippingMethodsTable.$inferSelect;
3
+ type RawShippingMethodRow = typeof shopShippingMethodsTable.$inferSelect;
4
+ export type ShippingMethodRow = Omit<RawShippingMethodRow, 'price'> & {
5
+ price: number;
6
+ };
4
7
  export interface ShippingConditions {
5
8
  freeAbove?: number;
6
9
  }
@@ -11,6 +14,7 @@ export interface ShippingMethodInput {
11
14
  vatRate: number;
12
15
  carrierType?: ShopCarrierType;
13
16
  conditions?: ShippingConditions | null;
17
+ allowedPaymentMethods?: string[] | null;
14
18
  isActive?: boolean;
15
19
  sortOrder?: number | null;
16
20
  }
@@ -25,10 +29,14 @@ export declare function deleteShippingMethod(id: string): Promise<void>;
25
29
  export declare function reorderShippingMethods(orderedIds: string[]): Promise<void>;
26
30
  /**
27
31
  * Resolve effective shipping cost for a given cart.
28
- * Applies `conditions.freeAbove` threshold against the cart gross total.
29
- * Returns integer (smallest currency unit).
32
+ * Applies `conditions.freeAbove` threshold against the cart gross total (grosze).
33
+ * method.price PLN (numeric 20,6). Zwraca kwoty w groszach (integer) do snapshotu zamówienia.
30
34
  */
31
- export declare function resolveShippingPrice(method: Pick<ShippingMethodRow, 'price' | 'vatRate' | 'conditions'>, cartTotalGross: number): {
35
+ export declare function resolveShippingPrice(method: {
36
+ price: number | string;
37
+ vatRate: number;
38
+ conditions: ShippingConditions | null | unknown;
39
+ }, cartTotalGross: number): {
32
40
  net: number;
33
41
  gross: number;
34
42
  vat: number;
@@ -1,13 +1,16 @@
1
1
  import { asc, eq } from 'drizzle-orm';
2
2
  import { shopShippingMethodsTable } from '../../db-postgres/schema/shop/index.js';
3
- import { grossFromNet } from '../pricing.js';
3
+ import { grossPlnFromNetPln, toCents } from '../pricing.js';
4
4
  import { getShopDb } from './db.js';
5
+ function mapShippingRow(r) {
6
+ return { ...r, price: Number(r.price) };
7
+ }
5
8
  function validate(input) {
6
9
  if (!input.name || Object.keys(input.name).length === 0) {
7
10
  throw new Error('Shipping method name (i18n) is required.');
8
11
  }
9
- if (!Number.isInteger(input.price) || input.price < 0) {
10
- throw new Error('price must be non-negative integer (smallest currency unit).');
12
+ if (!Number.isFinite(input.price) || input.price < 0 || input.price > 1e9) {
13
+ throw new Error('price must be non-negative finite number (PLN, 1e9).');
11
14
  }
12
15
  if (!Number.isInteger(input.vatRate) || input.vatRate < 0 || input.vatRate > 100) {
13
16
  throw new Error('vatRate must be integer 0..100.');
@@ -15,17 +18,18 @@ function validate(input) {
15
18
  if (input.conditions?.freeAbove != null) {
16
19
  const fa = input.conditions.freeAbove;
17
20
  if (!Number.isInteger(fa) || fa < 0) {
18
- throw new Error('conditions.freeAbove must be non-negative integer.');
21
+ throw new Error('conditions.freeAbove must be non-negative integer (grosze).');
19
22
  }
20
23
  }
21
24
  }
22
25
  export async function listShippingMethods(opts = {}) {
23
26
  const db = getShopDb();
24
- return db
27
+ const rows = await db
25
28
  .select()
26
29
  .from(shopShippingMethodsTable)
27
30
  .where(opts.activeOnly ? eq(shopShippingMethodsTable.isActive, true) : undefined)
28
31
  .orderBy(asc(shopShippingMethodsTable.sortOrder), asc(shopShippingMethodsTable.createdAt));
32
+ return rows.map(mapShippingRow);
29
33
  }
30
34
  export async function getShippingMethod(id) {
31
35
  const db = getShopDb();
@@ -33,7 +37,7 @@ export async function getShippingMethod(id) {
33
37
  .select()
34
38
  .from(shopShippingMethodsTable)
35
39
  .where(eq(shopShippingMethodsTable.id, id));
36
- return row ?? null;
40
+ return row ? mapShippingRow(row) : null;
37
41
  }
38
42
  export async function createShippingMethod(input) {
39
43
  validate(input);
@@ -43,15 +47,16 @@ export async function createShippingMethod(input) {
43
47
  .values({
44
48
  name: input.name,
45
49
  description: input.description ?? null,
46
- price: input.price,
50
+ price: String(input.price),
47
51
  vatRate: input.vatRate,
48
52
  carrierType: input.carrierType ?? 'none',
49
53
  conditions: input.conditions ?? null,
54
+ allowedPaymentMethods: input.allowedPaymentMethods ?? null,
50
55
  isActive: input.isActive ?? true,
51
56
  sortOrder: input.sortOrder ?? null
52
57
  })
53
58
  .returning();
54
- return row;
59
+ return mapShippingRow(row);
55
60
  }
56
61
  export async function updateShippingMethod(id, input) {
57
62
  const db = getShopDb();
@@ -61,13 +66,15 @@ export async function updateShippingMethod(id, input) {
61
66
  if (input.description !== undefined)
62
67
  patch.description = input.description;
63
68
  if (input.price !== undefined)
64
- patch.price = input.price;
69
+ patch.price = String(input.price);
65
70
  if (input.vatRate !== undefined)
66
71
  patch.vatRate = input.vatRate;
67
72
  if (input.carrierType !== undefined)
68
73
  patch.carrierType = input.carrierType;
69
74
  if (input.conditions !== undefined)
70
75
  patch.conditions = input.conditions;
76
+ if (input.allowedPaymentMethods !== undefined)
77
+ patch.allowedPaymentMethods = input.allowedPaymentMethods;
71
78
  if (input.isActive !== undefined)
72
79
  patch.isActive = input.isActive;
73
80
  if (input.sortOrder !== undefined)
@@ -79,7 +86,7 @@ export async function updateShippingMethod(id, input) {
79
86
  .returning();
80
87
  if (!row)
81
88
  throw new Error('Shipping method not found');
82
- return row;
89
+ return mapShippingRow(row);
83
90
  }
84
91
  export async function deleteShippingMethod(id) {
85
92
  const db = getShopDb();
@@ -97,15 +104,18 @@ export async function reorderShippingMethods(orderedIds) {
97
104
  }
98
105
  /**
99
106
  * Resolve effective shipping cost for a given cart.
100
- * Applies `conditions.freeAbove` threshold against the cart gross total.
101
- * Returns integer (smallest currency unit).
107
+ * Applies `conditions.freeAbove` threshold against the cart gross total (grosze).
108
+ * method.price PLN (numeric 20,6). Zwraca kwoty w groszach (integer) do snapshotu zamówienia.
102
109
  */
103
110
  export function resolveShippingPrice(method, cartTotalGross) {
104
111
  const cond = method.conditions ?? null;
105
112
  if (cond?.freeAbove != null && cartTotalGross >= cond.freeAbove) {
106
113
  return { net: 0, gross: 0, vat: 0, isFree: true };
107
114
  }
108
- const gross = grossFromNet(method.price, method.vatRate);
109
- return { net: method.price, gross, vat: gross - method.price, isFree: false };
115
+ const priceNetPln = Number(method.price);
116
+ const priceGrossPln = grossPlnFromNetPln(priceNetPln, method.vatRate);
117
+ const net = toCents(priceNetPln);
118
+ const gross = toCents(priceGrossPln);
119
+ return { net, gross, vat: gross - net, isFree: false };
110
120
  }
111
121
  export { validate as validateShippingMethodInput };
@@ -1,6 +1,12 @@
1
1
  import { shopProductsTable, shopProductVariantsTable } from '../../db-postgres/schema/shop/index.js';
2
- export type ShopDataRow = typeof shopProductsTable.$inferSelect;
3
- export type VariantRow = typeof shopProductVariantsTable.$inferSelect;
2
+ type RawShopDataRow = typeof shopProductsTable.$inferSelect;
3
+ type RawVariantRow = typeof shopProductVariantsTable.$inferSelect;
4
+ export type ShopDataRow = Omit<RawShopDataRow, 'basePrice'> & {
5
+ basePrice: number;
6
+ };
7
+ export type VariantRow = Omit<RawVariantRow, 'priceDelta'> & {
8
+ priceDelta: number;
9
+ };
4
10
  export interface ShopDataWithVariants extends ShopDataRow {
5
11
  variants: VariantRow[];
6
12
  }
@@ -3,14 +3,21 @@ import { shopProductsTable, shopProductVariantsTable } from '../../db-postgres/s
3
3
  import { entriesTable } from '../../db-postgres/schema/entry.js';
4
4
  import { entryVersionsTable } from '../../db-postgres/schema/entryVersion.js';
5
5
  import { getShopDb } from './db.js';
6
+ const MAX_PLN = 1e9;
6
7
  function validateShopData(input) {
7
- if (!Number.isInteger(input.basePrice) || input.basePrice < 0) {
8
- throw new Error('basePrice must be a non-negative integer (smallest currency unit).');
8
+ if (!Number.isFinite(input.basePrice) || input.basePrice < 0 || input.basePrice > MAX_PLN) {
9
+ throw new Error('basePrice must be a non-negative finite number (PLN, 1e9).');
9
10
  }
10
11
  if (!Number.isInteger(input.vatRate) || input.vatRate < 0 || input.vatRate > 100) {
11
12
  throw new Error('vatRate must be an integer between 0 and 100.');
12
13
  }
13
14
  }
15
+ function mapShopRow(r) {
16
+ return { ...r, basePrice: Number(r.basePrice) };
17
+ }
18
+ function mapVariantRow(v) {
19
+ return { ...v, priceDelta: Number(v.priceDelta) };
20
+ }
14
21
  export async function getShopDataByEntry(entryId) {
15
22
  const db = getShopDb();
16
23
  const [row] = await db
@@ -24,18 +31,19 @@ export async function getShopDataByEntry(entryId) {
24
31
  .from(shopProductVariantsTable)
25
32
  .where(eq(shopProductVariantsTable.productId, row.id))
26
33
  .orderBy(asc(shopProductVariantsTable.sortOrder), asc(shopProductVariantsTable.createdAt));
27
- return { ...row, variants };
34
+ return { ...mapShopRow(row), variants: variants.map(mapVariantRow) };
28
35
  }
29
36
  export async function upsertShopData(entryId, input, variants) {
30
37
  validateShopData(input);
31
38
  const db = getShopDb();
32
39
  const existing = await getShopDataByEntry(entryId);
33
40
  let productId;
41
+ const basePriceSql = String(input.basePrice);
34
42
  if (existing) {
35
43
  const [updated] = await db
36
44
  .update(shopProductsTable)
37
45
  .set({
38
- basePrice: input.basePrice,
46
+ basePrice: basePriceSql,
39
47
  vatRate: input.vatRate,
40
48
  isActive: input.isActive ?? existing.isActive,
41
49
  sortOrder: input.sortOrder ?? existing.sortOrder,
@@ -50,7 +58,7 @@ export async function upsertShopData(entryId, input, variants) {
50
58
  .insert(shopProductsTable)
51
59
  .values({
52
60
  entryId,
53
- basePrice: input.basePrice,
61
+ basePrice: basePriceSql,
54
62
  vatRate: input.vatRate,
55
63
  isActive: input.isActive ?? true,
56
64
  sortOrder: input.sortOrder
@@ -73,7 +81,7 @@ export async function upsertShopData(entryId, input, variants) {
73
81
  .set({
74
82
  sku: v.sku ?? null,
75
83
  name: v.name ?? null,
76
- priceDelta: v.priceDelta ?? 0,
84
+ priceDelta: String(v.priceDelta ?? 0),
77
85
  stock: v.stock ?? null,
78
86
  attributes: v.attributes ?? null
79
87
  })
@@ -84,7 +92,7 @@ export async function upsertShopData(entryId, input, variants) {
84
92
  productId,
85
93
  sku: v.sku ?? null,
86
94
  name: v.name ?? null,
87
- priceDelta: v.priceDelta ?? 0,
95
+ priceDelta: String(v.priceDelta ?? 0),
88
96
  stock: v.stock ?? null,
89
97
  attributes: v.attributes ?? null
90
98
  });
@@ -101,7 +109,7 @@ export async function upsertShopData(entryId, input, variants) {
101
109
  productId,
102
110
  sku: null,
103
111
  name: null,
104
- priceDelta: 0,
112
+ priceDelta: '0',
105
113
  stock: null,
106
114
  attributes: null
107
115
  });
@@ -123,7 +131,7 @@ export async function getDefaultVariant(productId) {
123
131
  .where(eq(shopProductVariantsTable.productId, productId))
124
132
  .orderBy(asc(shopProductVariantsTable.createdAt))
125
133
  .limit(1);
126
- return v ?? null;
134
+ return v ? mapVariantRow(v) : null;
127
135
  }
128
136
  export async function deleteShopData(entryId) {
129
137
  const db = getShopDb();
@@ -170,7 +178,7 @@ export async function listShopEntries(opts = {}) {
170
178
  entryId: r.entryId,
171
179
  collectionSlug: r.collectionSlug,
172
180
  shopId: r.shopId,
173
- basePrice: r.basePrice,
181
+ basePrice: Number(r.basePrice),
174
182
  vatRate: r.vatRate,
175
183
  isActive: r.isActive,
176
184
  variantCount: variants.length,