includio-cms 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +42 -2
- package/CHANGELOG.md +65 -0
- package/DOCS.md +1 -1
- package/ROADMAP.md +8 -0
- package/dist/admin/auth-client.d.ts +42 -42
- package/dist/admin/client/admin/admin-layout.svelte +12 -2
- package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
- package/dist/admin/client/collection/data-table.svelte +0 -39
- package/dist/admin/client/collection/data-table.svelte.d.ts +0 -2
- package/dist/admin/client/shop/coupon-schema.d.ts +1 -1
- package/dist/admin/client/shop/refund-dialog.svelte +37 -1
- package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +107 -0
- package/dist/admin/components/fields/field-renderer.svelte +6 -1
- package/dist/admin/components/fields/icon-field.svelte +86 -0
- package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
- package/dist/admin/components/fields/object-field.svelte +27 -7
- package/dist/admin/components/fields/shop-field.svelte +210 -20
- package/dist/admin/components/layout/layout-tabs.svelte +1 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
- package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
- package/dist/admin/helpers/build-icon-set-map.js +16 -0
- package/dist/admin/helpers/index.d.ts +2 -0
- package/dist/admin/helpers/index.js +2 -0
- package/dist/admin/remote/shop.remote.d.ts +58 -24
- package/dist/admin/remote/shop.remote.js +61 -6
- package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
- package/dist/admin/state/icon-sets.svelte.js +20 -0
- package/dist/cli/scaffold/admin.js +2 -2
- package/dist/components/ui/checkbox/checkbox.svelte +3 -3
- package/dist/core/cms.d.ts +11 -2
- package/dist/core/cms.js +29 -0
- package/dist/core/fields/fieldSchemaToTs.js +7 -0
- package/dist/core/server/generator/fields.d.ts +2 -0
- package/dist/core/server/generator/fields.js +34 -1
- package/dist/core/server/generator/generator.js +2 -1
- package/dist/db-postgres/schema/shop/order.d.ts +37 -1
- package/dist/db-postgres/schema/shop/order.js +3 -1
- package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
- package/dist/db-postgres/schema/shop/payment.js +4 -1
- package/dist/db-postgres/schema/shop/product.d.ts +20 -0
- package/dist/db-postgres/schema/shop/product.js +3 -1
- package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
- package/dist/db-postgres/schema/shop/productVariant.js +22 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/shop/cart/types.d.ts +1 -0
- package/dist/shop/client/index.d.ts +54 -0
- package/dist/shop/client/index.js +5 -1
- package/dist/shop/expiry.d.ts +35 -0
- package/dist/shop/expiry.js +68 -0
- package/dist/shop/http/balance-handler.d.ts +20 -0
- package/dist/shop/http/balance-handler.js +91 -0
- package/dist/shop/http/cart-handler.js +19 -0
- package/dist/shop/http/checkout-handler.js +19 -1
- package/dist/shop/http/index.d.ts +2 -0
- package/dist/shop/http/index.js +2 -0
- package/dist/shop/http/upcoming-handler.d.ts +16 -0
- package/dist/shop/http/upcoming-handler.js +65 -0
- package/dist/shop/http/webhook-handler.js +46 -9
- package/dist/shop/index.d.ts +4 -1
- package/dist/shop/index.js +7 -1
- package/dist/shop/server/balance-payment.d.ts +40 -0
- package/dist/shop/server/balance-payment.js +140 -0
- package/dist/shop/server/cart-hydrate.js +2 -0
- package/dist/shop/server/init.d.ts +14 -0
- package/dist/shop/server/init.js +35 -0
- package/dist/shop/server/orders.d.ts +34 -0
- package/dist/shop/server/orders.js +141 -2
- package/dist/shop/server/payment-policy.d.ts +35 -0
- package/dist/shop/server/payment-policy.js +55 -0
- package/dist/shop/server/payments.d.ts +29 -0
- package/dist/shop/server/payments.js +64 -0
- package/dist/shop/server/populate.d.ts +1 -1
- package/dist/shop/server/refund.d.ts +17 -12
- package/dist/shop/server/refund.js +96 -13
- package/dist/shop/server/shop-data.d.ts +4 -1
- package/dist/shop/server/shop-data.js +24 -2
- package/dist/shop/template.d.ts +13 -0
- package/dist/shop/template.js +98 -0
- package/dist/shop/types.d.ts +142 -1
- package/dist/shop/variant-attributes.d.ts +28 -0
- package/dist/shop/variant-attributes.js +69 -0
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +2 -0
- package/dist/types/cms.d.ts +4 -3
- package/dist/types/cms.schema.d.ts +1 -1
- package/dist/types/cms.schema.js +9 -0
- package/dist/types/fields.d.ts +21 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/plugins.d.ts +40 -0
- package/dist/types/plugins.js +4 -1
- package/dist/updates/0.26.1/index.d.ts +2 -0
- package/dist/updates/0.26.1/index.js +19 -0
- package/dist/updates/0.27.0/index.d.ts +2 -0
- package/dist/updates/0.27.0/index.js +50 -0
- package/dist/updates/index.js +5 -1
- package/package.json +1 -1
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export type ShopPaymentStatus = 'pending' | 'paid' | 'failed' | 'refunded' | 'cancelled';
|
|
2
|
+
/** `kind` distinguishes full / deposit / balance payment rows under paymentPolicy A-lite. */
|
|
3
|
+
export declare const shopPaymentKindEnum: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgEnum<["full", "deposit", "balance"]>;
|
|
4
|
+
export type ShopPaymentKind = 'full' | 'deposit' | 'balance';
|
|
2
5
|
export declare const shopPaymentsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
|
|
3
6
|
name: "shop_payments";
|
|
4
7
|
schema: undefined;
|
|
@@ -90,6 +93,23 @@ export declare const shopPaymentsTable: import("drizzle-orm/pg-core/table", { wi
|
|
|
90
93
|
}, {}, {
|
|
91
94
|
$type: ShopPaymentStatus;
|
|
92
95
|
}>;
|
|
96
|
+
kind: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
97
|
+
name: "kind";
|
|
98
|
+
tableName: "shop_payments";
|
|
99
|
+
dataType: "string";
|
|
100
|
+
columnType: "PgEnumColumn";
|
|
101
|
+
data: "full" | "deposit" | "balance";
|
|
102
|
+
driverParam: string;
|
|
103
|
+
notNull: true;
|
|
104
|
+
hasDefault: true;
|
|
105
|
+
isPrimaryKey: false;
|
|
106
|
+
isAutoincrement: false;
|
|
107
|
+
hasRuntimeDefault: false;
|
|
108
|
+
enumValues: ["full", "deposit", "balance"];
|
|
109
|
+
baseColumn: never;
|
|
110
|
+
identity: undefined;
|
|
111
|
+
generated: undefined;
|
|
112
|
+
}, {}, {}>;
|
|
93
113
|
amount: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
94
114
|
name: "amount";
|
|
95
115
|
tableName: "shop_payments";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { integer, jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
1
|
+
import { integer, jsonb, pgEnum, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
2
2
|
import { shopOrdersTable } from './order.js';
|
|
3
|
+
/** `kind` distinguishes full / deposit / balance payment rows under paymentPolicy A-lite. */
|
|
4
|
+
export const shopPaymentKindEnum = pgEnum('shop_payment_kind', ['full', 'deposit', 'balance']);
|
|
3
5
|
export const shopPaymentsTable = pgTable('shop_payments', {
|
|
4
6
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
5
7
|
orderId: uuid('order_id')
|
|
@@ -8,6 +10,7 @@ export const shopPaymentsTable = pgTable('shop_payments', {
|
|
|
8
10
|
provider: text('provider').notNull(),
|
|
9
11
|
providerRef: text('provider_ref'),
|
|
10
12
|
status: text('status').$type().notNull().default('pending'),
|
|
13
|
+
kind: shopPaymentKindEnum('kind').notNull().default('full'),
|
|
11
14
|
amount: integer('amount').notNull(),
|
|
12
15
|
currency: text('currency').notNull(),
|
|
13
16
|
raw: jsonb('raw'),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PaymentPolicy } from '../../../shop/types.js';
|
|
1
2
|
export declare const shopProductsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
|
|
2
3
|
name: "shop_products";
|
|
3
4
|
schema: undefined;
|
|
@@ -121,6 +122,25 @@ export declare const shopProductsTable: import("drizzle-orm/pg-core/table", { wi
|
|
|
121
122
|
identity: undefined;
|
|
122
123
|
generated: undefined;
|
|
123
124
|
}, {}, {}>;
|
|
125
|
+
paymentPolicy: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
126
|
+
name: "payment_policy";
|
|
127
|
+
tableName: "shop_products";
|
|
128
|
+
dataType: "json";
|
|
129
|
+
columnType: "PgJsonb";
|
|
130
|
+
data: PaymentPolicy | null;
|
|
131
|
+
driverParam: unknown;
|
|
132
|
+
notNull: false;
|
|
133
|
+
hasDefault: false;
|
|
134
|
+
isPrimaryKey: false;
|
|
135
|
+
isAutoincrement: false;
|
|
136
|
+
hasRuntimeDefault: false;
|
|
137
|
+
enumValues: undefined;
|
|
138
|
+
baseColumn: never;
|
|
139
|
+
identity: undefined;
|
|
140
|
+
generated: undefined;
|
|
141
|
+
}, {}, {
|
|
142
|
+
$type: PaymentPolicy | null;
|
|
143
|
+
}>;
|
|
124
144
|
createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
125
145
|
name: "created_at";
|
|
126
146
|
tableName: "shop_products";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { boolean, integer, numeric, pgTable, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
1
|
+
import { boolean, integer, jsonb, numeric, pgTable, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
2
2
|
import { entriesTable } from '../entry.js';
|
|
3
3
|
export const shopProductsTable = pgTable('shop_products', {
|
|
4
4
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
@@ -13,6 +13,8 @@ export const shopProductsTable = pgTable('shop_products', {
|
|
|
13
13
|
sortOrder: integer('sort_order'),
|
|
14
14
|
// Null = alert disabled. When stock decrement crosses threshold, fire-and-forget admin email.
|
|
15
15
|
lowStockThreshold: integer('low_stock_threshold'),
|
|
16
|
+
// Per-product payment policy. Null = full payment (legacy).
|
|
17
|
+
paymentPolicy: jsonb('payment_policy').$type(),
|
|
16
18
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
17
19
|
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
|
|
18
20
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VariantAttribute } from '../../../shop/types.js';
|
|
1
2
|
export declare const shopProductVariantsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
|
|
2
3
|
name: "shop_product_variants";
|
|
3
4
|
schema: undefined;
|
|
@@ -111,7 +112,7 @@ export declare const shopProductVariantsTable: import("drizzle-orm/pg-core/table
|
|
|
111
112
|
tableName: "shop_product_variants";
|
|
112
113
|
dataType: "json";
|
|
113
114
|
columnType: "PgJsonb";
|
|
114
|
-
data: Record<string,
|
|
115
|
+
data: Record<string, unknown>;
|
|
115
116
|
driverParam: unknown;
|
|
116
117
|
notNull: false;
|
|
117
118
|
hasDefault: false;
|
|
@@ -123,7 +124,7 @@ export declare const shopProductVariantsTable: import("drizzle-orm/pg-core/table
|
|
|
123
124
|
identity: undefined;
|
|
124
125
|
generated: undefined;
|
|
125
126
|
}, {}, {
|
|
126
|
-
$type: Record<string,
|
|
127
|
+
$type: Record<string, unknown>;
|
|
127
128
|
}>;
|
|
128
129
|
sortOrder: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
129
130
|
name: "sort_order";
|
|
@@ -162,3 +163,12 @@ export declare const shopProductVariantsTable: import("drizzle-orm/pg-core/table
|
|
|
162
163
|
};
|
|
163
164
|
dialect: "pg";
|
|
164
165
|
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Build `CREATE INDEX IF NOT EXISTS … USING gin ((attributes->'<key>'))` DDL
|
|
168
|
+
* statements — one per attribute marked `indexable: true`. Drizzle's static
|
|
169
|
+
* schema can't capture user-defined variantAttributes, so apply these at shop
|
|
170
|
+
* bootstrap (or via the migration in `src/lib/updates/0.27.0`).
|
|
171
|
+
* Index name pattern: `shop_variant_attr_<key>_gin_idx`.
|
|
172
|
+
* @internal
|
|
173
|
+
*/
|
|
174
|
+
export declare function createVariantAttributeIndexes(attrs: Record<string, VariantAttribute>): string[];
|
|
@@ -14,3 +14,25 @@ export const shopProductVariantsTable = pgTable('shop_product_variants', {
|
|
|
14
14
|
sortOrder: integer('sort_order'),
|
|
15
15
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
|
16
16
|
});
|
|
17
|
+
const SAFE_KEY = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
18
|
+
/**
|
|
19
|
+
* Build `CREATE INDEX IF NOT EXISTS … USING gin ((attributes->'<key>'))` DDL
|
|
20
|
+
* statements — one per attribute marked `indexable: true`. Drizzle's static
|
|
21
|
+
* schema can't capture user-defined variantAttributes, so apply these at shop
|
|
22
|
+
* bootstrap (or via the migration in `src/lib/updates/0.27.0`).
|
|
23
|
+
* Index name pattern: `shop_variant_attr_<key>_gin_idx`.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function createVariantAttributeIndexes(attrs) {
|
|
27
|
+
const stmts = [];
|
|
28
|
+
for (const [key, attr] of Object.entries(attrs)) {
|
|
29
|
+
if (!attr.indexable)
|
|
30
|
+
continue;
|
|
31
|
+
if (!SAFE_KEY.test(key)) {
|
|
32
|
+
throw new Error(`variantAttribute key "${key}" is not a safe SQL identifier (allowed: [A-Za-z][A-Za-z0-9_]*).`);
|
|
33
|
+
}
|
|
34
|
+
stmts.push(`CREATE INDEX IF NOT EXISTS shop_variant_attr_${key}_gin_idx ` +
|
|
35
|
+
`ON shop_product_variants USING gin ((attributes->'${key}'))`);
|
|
36
|
+
}
|
|
37
|
+
return stmts;
|
|
38
|
+
}
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export function hello_world(inputs: {
|
|
2
|
+
name: NonNullable<unknown>;
|
|
3
|
+
}, options?: {
|
|
4
|
+
locale?: "en" | "pl";
|
|
5
|
+
}): string;
|
|
6
|
+
/**
|
|
7
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
8
|
+
*
|
|
9
|
+
* - Changing this function will be over-written by the next build.
|
|
10
|
+
*
|
|
11
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
12
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
13
|
+
*
|
|
14
|
+
* @param {{}} inputs
|
|
15
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
declare function login_hello(inputs?: {}, options?: {
|
|
19
|
+
locale?: "en" | "pl";
|
|
20
|
+
}): string;
|
|
21
|
+
/**
|
|
22
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
23
|
+
*
|
|
24
|
+
* - Changing this function will be over-written by the next build.
|
|
25
|
+
*
|
|
26
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
27
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
28
|
+
*
|
|
29
|
+
* @param {{}} inputs
|
|
30
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
declare function login_please_login(inputs?: {}, options?: {
|
|
34
|
+
locale?: "en" | "pl";
|
|
35
|
+
}): string;
|
|
36
|
+
export { login_hello as login.hello, login_please_login as login.please_login };
|
|
@@ -1,4 +1,72 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"
|
|
3
|
+
import * as en from "./en.js"
|
|
4
|
+
import * as pl from "./pl.js"
|
|
5
|
+
/**
|
|
6
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
7
|
+
*
|
|
8
|
+
* - Changing this function will be over-written by the next build.
|
|
9
|
+
*
|
|
10
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
11
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
12
|
+
*
|
|
13
|
+
* @param {{ name: NonNullable<unknown> }} inputs
|
|
14
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
18
|
+
export const hello_world = (inputs, options = {}) => {
|
|
19
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
20
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
|
|
21
|
+
}
|
|
22
|
+
const locale = options.locale ?? getLocale()
|
|
23
|
+
trackMessageCall("hello_world", locale)
|
|
24
|
+
if (locale === "en") return en.hello_world(inputs)
|
|
25
|
+
return pl.hello_world(inputs)
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
29
|
+
*
|
|
30
|
+
* - Changing this function will be over-written by the next build.
|
|
31
|
+
*
|
|
32
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
33
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
34
|
+
*
|
|
35
|
+
* @param {{}} inputs
|
|
36
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
40
|
+
const login_hello = (inputs = {}, options = {}) => {
|
|
41
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
42
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
|
|
43
|
+
}
|
|
44
|
+
const locale = options.locale ?? getLocale()
|
|
45
|
+
trackMessageCall("login_hello", locale)
|
|
46
|
+
if (locale === "en") return en.login_hello(inputs)
|
|
47
|
+
return pl.login_hello(inputs)
|
|
48
|
+
};
|
|
49
|
+
export { login_hello as "login.hello" }
|
|
50
|
+
/**
|
|
51
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
52
|
+
*
|
|
53
|
+
* - Changing this function will be over-written by the next build.
|
|
54
|
+
*
|
|
55
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
56
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
57
|
+
*
|
|
58
|
+
* @param {{}} inputs
|
|
59
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
63
|
+
const login_please_login = (inputs = {}, options = {}) => {
|
|
64
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
65
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
|
|
66
|
+
}
|
|
67
|
+
const locale = options.locale ?? getLocale()
|
|
68
|
+
trackMessageCall("login_please_login", locale)
|
|
69
|
+
if (locale === "en") return en.login_please_login(inputs)
|
|
70
|
+
return pl.login_please_login(inputs)
|
|
71
|
+
};
|
|
72
|
+
export { login_please_login as "login.please_login" }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
|
|
5
|
+
return `Hello, ${i.name} from en!`
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const login_hello = /** @type {(inputs: {}) => string} */ () => {
|
|
9
|
+
return `Welcome back`
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
|
|
13
|
+
return `Login to your account`
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
|
|
5
|
+
return `Hello, ${i.name} from pl!`
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const login_hello = /** @type {(inputs: {}) => string} */ () => {
|
|
9
|
+
return `Witaj ponownie`
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
|
|
13
|
+
return `Zaloguj się na swoje konto`
|
|
14
|
+
};
|
|
@@ -114,6 +114,34 @@ export interface RetryPaymentResult {
|
|
|
114
114
|
requiresPaymentRedirect: boolean;
|
|
115
115
|
redirectUrl: string | null;
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Result of `orders.payBalance` — initiates a payment session for the
|
|
119
|
+
* outstanding balance on a deposit order.
|
|
120
|
+
* @public
|
|
121
|
+
*/
|
|
122
|
+
export interface PayBalanceResult {
|
|
123
|
+
status: 'redirect' | 'manual' | 'error';
|
|
124
|
+
requiresPaymentRedirect: boolean;
|
|
125
|
+
redirectUrl: string | null;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Public-facing variant row returned by `client.products.listUpcoming`.
|
|
129
|
+
* Mirrors the `shop_product_variants` schema with `priceDelta` decoded as
|
|
130
|
+
* the raw numeric(20,6) string and `attributes` as the typed map declared
|
|
131
|
+
* in `defineShop({ variantAttributes })`.
|
|
132
|
+
* @public
|
|
133
|
+
*/
|
|
134
|
+
export interface VariantPublic {
|
|
135
|
+
id: string;
|
|
136
|
+
productId: string;
|
|
137
|
+
sku: string | null;
|
|
138
|
+
name: Record<string, string> | null;
|
|
139
|
+
priceDelta: string;
|
|
140
|
+
stock: number | null;
|
|
141
|
+
attributes: Record<string, unknown> | null;
|
|
142
|
+
sortOrder: number | null;
|
|
143
|
+
createdAt: string;
|
|
144
|
+
}
|
|
117
145
|
/**
|
|
118
146
|
* Headless shop SDK surface returned by `createShopClient()`.
|
|
119
147
|
*
|
|
@@ -124,6 +152,21 @@ export interface RetryPaymentResult {
|
|
|
124
152
|
* @public
|
|
125
153
|
*/
|
|
126
154
|
export interface ShopClient {
|
|
155
|
+
/** Product / variant discovery. */
|
|
156
|
+
products: {
|
|
157
|
+
/**
|
|
158
|
+
* List upcoming (non-expired) variants for a product. When
|
|
159
|
+
* `defineShop({ variantExpiry })` is not configured this returns
|
|
160
|
+
* every variant; otherwise variants whose datetime attribute has
|
|
161
|
+
* already passed (per `offsetDays`) are filtered out and the
|
|
162
|
+
* remainder are sorted ascending by that attribute.
|
|
163
|
+
*
|
|
164
|
+
* @param productId - The shop product id (UUID).
|
|
165
|
+
*/
|
|
166
|
+
listUpcoming(productId: string): Promise<{
|
|
167
|
+
items: VariantPublic[];
|
|
168
|
+
}>;
|
|
169
|
+
};
|
|
127
170
|
/** Cart operations — read state and mutate items / coupon. */
|
|
128
171
|
cart: {
|
|
129
172
|
/**
|
|
@@ -201,6 +244,17 @@ export interface ShopClient {
|
|
|
201
244
|
* re-fill the checkout form.
|
|
202
245
|
*/
|
|
203
246
|
retryPayment(orderNumber: string, token?: string): Promise<RetryPaymentResult>;
|
|
247
|
+
/**
|
|
248
|
+
* Pay the outstanding balance on a deposit-policy order. `token` is
|
|
249
|
+
* the signed balance token from the admin-generated balance link;
|
|
250
|
+
* the server returns a redirect URL when the payment provider needs
|
|
251
|
+
* to take over, or `paymentStatus: 'manual'` for manual methods.
|
|
252
|
+
*
|
|
253
|
+
* @param orderNumber - The order number from the balance link.
|
|
254
|
+
* @param token - Signed balance token (query parameter on the link).
|
|
255
|
+
* @experimental
|
|
256
|
+
*/
|
|
257
|
+
payBalance(orderNumber: string, token: string): Promise<PayBalanceResult>;
|
|
204
258
|
};
|
|
205
259
|
}
|
|
206
260
|
/**
|
|
@@ -59,6 +59,9 @@ export function createShopClient(options = {}) {
|
|
|
59
59
|
return (await res.json());
|
|
60
60
|
}
|
|
61
61
|
return {
|
|
62
|
+
products: {
|
|
63
|
+
listUpcoming: (productId) => call('GET', `/api/shop/products/${encodeURIComponent(productId)}/variants/upcoming`)
|
|
64
|
+
},
|
|
62
65
|
cart: {
|
|
63
66
|
get: () => call('GET', '/api/shop/cart'),
|
|
64
67
|
add: (variantId, qty = 1) => call('POST', '/api/shop/cart', { variantId, qty }),
|
|
@@ -77,7 +80,8 @@ export function createShopClient(options = {}) {
|
|
|
77
80
|
orders: {
|
|
78
81
|
get: (orderNumber, token) => call('GET', `/api/shop/orders/${encodeURIComponent(orderNumber)}${tokenQuery(token)}`),
|
|
79
82
|
refreshPayment: (orderNumber, token) => call('POST', `/api/shop/orders/${encodeURIComponent(orderNumber)}/refresh-payment${tokenQuery(token)}`),
|
|
80
|
-
retryPayment: (orderNumber, token) => call('POST', `/api/shop/orders/${encodeURIComponent(orderNumber)}/retry-payment${tokenQuery(token)}`)
|
|
83
|
+
retryPayment: (orderNumber, token) => call('POST', `/api/shop/orders/${encodeURIComponent(orderNumber)}/retry-payment${tokenQuery(token)}`),
|
|
84
|
+
payBalance: (orderNumber, token) => call('POST', `/api/shop/orders/${encodeURIComponent(orderNumber)}/balance?token=${encodeURIComponent(token)}`, {})
|
|
81
85
|
}
|
|
82
86
|
};
|
|
83
87
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { VariantExpiryConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by `createOrderFromCart` / cart-add HTTP guard when a referenced
|
|
4
|
+
* variant has already expired under the configured `variantExpiry`. The
|
|
5
|
+
* `code` is `VARIANT_EXPIRED` and `variantId` identifies the offending row.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export declare class VariantExpiredError extends Error {
|
|
9
|
+
readonly code = "VARIANT_EXPIRED";
|
|
10
|
+
readonly variantId: string;
|
|
11
|
+
constructor(variantId: string);
|
|
12
|
+
}
|
|
13
|
+
interface VariantLike {
|
|
14
|
+
attributes: Record<string, unknown> | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check whether a variant has expired under the given config. Fail-open by
|
|
18
|
+
* design: `null` config, missing source attribute, non-string value, or
|
|
19
|
+
* malformed datetime all return `false` — only a parseable past datetime
|
|
20
|
+
* counts as expired. Malformed datetimes emit `console.warn` so authoring
|
|
21
|
+
* issues surface in dev without blocking checkout.
|
|
22
|
+
*
|
|
23
|
+
* @param variant - Object with an `attributes` map (e.g. `shop_product_variants.attributes`).
|
|
24
|
+
* @param config - Result of `defineShop({ variantExpiry })`, or `null` when not configured.
|
|
25
|
+
* @param now - Override the current time (test seam). Defaults to `Date.now()`.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export declare function isVariantExpired(variant: VariantLike, config: VariantExpiryConfig | null, now?: Date): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Return the subset of `variants` that have not yet expired. Order is
|
|
31
|
+
* preserved. `null` config = passthrough.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export declare function filterUpcoming<T extends VariantLike>(variants: T[], config: VariantExpiryConfig | null, now?: Date): T[];
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by `createOrderFromCart` / cart-add HTTP guard when a referenced
|
|
3
|
+
* variant has already expired under the configured `variantExpiry`. The
|
|
4
|
+
* `code` is `VARIANT_EXPIRED` and `variantId` identifies the offending row.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export class VariantExpiredError extends Error {
|
|
8
|
+
code = 'VARIANT_EXPIRED';
|
|
9
|
+
variantId;
|
|
10
|
+
constructor(variantId) {
|
|
11
|
+
super(`Variant ${variantId} has expired`);
|
|
12
|
+
this.name = 'VariantExpiredError';
|
|
13
|
+
this.variantId = variantId;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Add `offsetDays` to an ISO-8601 datetime and return the resulting `Date`.
|
|
18
|
+
* Returns `null` on malformed input (caller decides fail-open vs throw).
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
function addDaysIso(iso, offsetDays) {
|
|
22
|
+
const ts = Date.parse(iso);
|
|
23
|
+
if (Number.isNaN(ts))
|
|
24
|
+
return null;
|
|
25
|
+
return new Date(ts + offsetDays * 86_400_000);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check whether a variant has expired under the given config. Fail-open by
|
|
29
|
+
* design: `null` config, missing source attribute, non-string value, or
|
|
30
|
+
* malformed datetime all return `false` — only a parseable past datetime
|
|
31
|
+
* counts as expired. Malformed datetimes emit `console.warn` so authoring
|
|
32
|
+
* issues surface in dev without blocking checkout.
|
|
33
|
+
*
|
|
34
|
+
* @param variant - Object with an `attributes` map (e.g. `shop_product_variants.attributes`).
|
|
35
|
+
* @param config - Result of `defineShop({ variantExpiry })`, or `null` when not configured.
|
|
36
|
+
* @param now - Override the current time (test seam). Defaults to `Date.now()`.
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export function isVariantExpired(variant, config, now = new Date(Date.now())) {
|
|
40
|
+
if (!config)
|
|
41
|
+
return false;
|
|
42
|
+
const attrs = variant.attributes;
|
|
43
|
+
if (!attrs)
|
|
44
|
+
return false;
|
|
45
|
+
const raw = attrs[config.source];
|
|
46
|
+
if (raw === undefined || raw === null)
|
|
47
|
+
return false;
|
|
48
|
+
if (typeof raw !== 'string') {
|
|
49
|
+
console.warn(`[variantExpiry] attribute "${config.source}" is not a string (got ${typeof raw}); treating variant as upcoming`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const cutoff = addDaysIso(raw, config.offsetDays);
|
|
53
|
+
if (!cutoff) {
|
|
54
|
+
console.warn(`[variantExpiry] attribute "${config.source}" is not a parseable datetime ("${raw}"); treating variant as upcoming`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return cutoff.getTime() < now.getTime();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Return the subset of `variants` that have not yet expired. Order is
|
|
61
|
+
* preserved. `null` config = passthrough.
|
|
62
|
+
* @public
|
|
63
|
+
*/
|
|
64
|
+
export function filterUpcoming(variants, config, now) {
|
|
65
|
+
if (!config)
|
|
66
|
+
return variants;
|
|
67
|
+
return variants.filter((v) => !isVariantExpired(v, config, now));
|
|
68
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RequestHandler } from '@sveltejs/kit';
|
|
2
|
+
/**
|
|
3
|
+
* @experimental
|
|
4
|
+
* HTTP handlers for the balance-payment flow. Mount at
|
|
5
|
+
* `/api/shop/orders/[number]/balance` (or any URL containing `number` +
|
|
6
|
+
* `?token=...`).
|
|
7
|
+
*
|
|
8
|
+
* - `GET` returns the minimal public order view (amount due, currency)
|
|
9
|
+
* when the token + balanceOwed check passes.
|
|
10
|
+
* - `POST` initiates a payment session for the outstanding balance using
|
|
11
|
+
* the order's original payment adapter and returns `{ redirectUrl,
|
|
12
|
+
* status }`.
|
|
13
|
+
*
|
|
14
|
+
* Both verbs return 403 when the token is invalid OR `balanceOwed` is
|
|
15
|
+
* false (no oracle distinction — we don't tell the caller which gate failed).
|
|
16
|
+
*/
|
|
17
|
+
export declare function createBalanceHandler(): {
|
|
18
|
+
GET: RequestHandler;
|
|
19
|
+
POST: RequestHandler;
|
|
20
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { json } from '@sveltejs/kit';
|
|
2
|
+
import { getCMS } from '../../core/cms.js';
|
|
3
|
+
import { createBalanceSession, requireBalanceTokenSecret, verifyBalanceToken } from '../server/balance-payment.js';
|
|
4
|
+
import { getOrderByNumber } from '../server/orders.js';
|
|
5
|
+
function shopEnabled() {
|
|
6
|
+
try {
|
|
7
|
+
return getCMS().shopConfig !== null;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @experimental
|
|
15
|
+
* HTTP handlers for the balance-payment flow. Mount at
|
|
16
|
+
* `/api/shop/orders/[number]/balance` (or any URL containing `number` +
|
|
17
|
+
* `?token=...`).
|
|
18
|
+
*
|
|
19
|
+
* - `GET` returns the minimal public order view (amount due, currency)
|
|
20
|
+
* when the token + balanceOwed check passes.
|
|
21
|
+
* - `POST` initiates a payment session for the outstanding balance using
|
|
22
|
+
* the order's original payment adapter and returns `{ redirectUrl,
|
|
23
|
+
* status }`.
|
|
24
|
+
*
|
|
25
|
+
* Both verbs return 403 when the token is invalid OR `balanceOwed` is
|
|
26
|
+
* false (no oracle distinction — we don't tell the caller which gate failed).
|
|
27
|
+
*/
|
|
28
|
+
export function createBalanceHandler() {
|
|
29
|
+
return {
|
|
30
|
+
GET: async ({ params, url }) => {
|
|
31
|
+
if (!shopEnabled())
|
|
32
|
+
return json({ error: 'Shop not enabled' }, { status: 404 });
|
|
33
|
+
const orderNumber = params.number;
|
|
34
|
+
if (!orderNumber)
|
|
35
|
+
return json({ error: 'Order number required' }, { status: 400 });
|
|
36
|
+
const token = url.searchParams.get('token') ?? '';
|
|
37
|
+
const order = await getOrderByNumber(orderNumber);
|
|
38
|
+
if (!order)
|
|
39
|
+
return json({ error: 'Order not found' }, { status: 404 });
|
|
40
|
+
if (!order.balanceOwed || !order.partialPayment) {
|
|
41
|
+
return json({ error: 'No outstanding balance' }, { status: 403 });
|
|
42
|
+
}
|
|
43
|
+
const secret = requireBalanceTokenSecret();
|
|
44
|
+
if (!verifyBalanceToken(token, order.id, secret)) {
|
|
45
|
+
return json({ error: 'Invalid token' }, { status: 403 });
|
|
46
|
+
}
|
|
47
|
+
const pp = order.partialPayment;
|
|
48
|
+
return json({
|
|
49
|
+
orderNumber: order.number,
|
|
50
|
+
currency: order.currency,
|
|
51
|
+
totalGross: order.totalGross,
|
|
52
|
+
amountToPay: pp.balanceAmount,
|
|
53
|
+
paidAmount: pp.paidAmount,
|
|
54
|
+
paymentMethod: order.paymentMethod,
|
|
55
|
+
language: order.language
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
POST: async ({ params, url, request, getClientAddress }) => {
|
|
59
|
+
if (!shopEnabled())
|
|
60
|
+
return json({ error: 'Shop not enabled' }, { status: 404 });
|
|
61
|
+
const orderNumber = params.number;
|
|
62
|
+
if (!orderNumber)
|
|
63
|
+
return json({ error: 'Order number required' }, { status: 400 });
|
|
64
|
+
const token = url.searchParams.get('token') ?? '';
|
|
65
|
+
let customerIp;
|
|
66
|
+
try {
|
|
67
|
+
customerIp = getClientAddress();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
customerIp = undefined;
|
|
71
|
+
}
|
|
72
|
+
const body = (await request.json().catch(() => ({})));
|
|
73
|
+
const language = typeof body.language === 'string' ? body.language : undefined;
|
|
74
|
+
try {
|
|
75
|
+
const result = await createBalanceSession(orderNumber, token, { customerIp, language });
|
|
76
|
+
return json({
|
|
77
|
+
status: result.status,
|
|
78
|
+
redirectUrl: result.redirectUrl ?? null,
|
|
79
|
+
requiresPaymentRedirect: result.status === 'redirect'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const message = err instanceof Error ? err.message : 'Balance payment failed';
|
|
84
|
+
const code = message === 'Invalid balance token' || message === 'Order has no outstanding balance'
|
|
85
|
+
? 403
|
|
86
|
+
: 400;
|
|
87
|
+
return json({ error: message }, { status: code });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|