includio-cms 0.24.0 → 0.25.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.
Files changed (90) hide show
  1. package/API.md +29 -6
  2. package/CHANGELOG.md +95 -0
  3. package/DOCS.md +80 -5
  4. package/ROADMAP.md +1 -0
  5. package/dist/admin/client/index.d.ts +3 -0
  6. package/dist/admin/client/index.js +3 -0
  7. package/dist/admin/client/shop/coupon-edit-page.svelte +44 -0
  8. package/dist/admin/client/shop/coupon-edit-page.svelte.d.ts +3 -0
  9. package/dist/admin/client/shop/coupon-form.svelte +170 -0
  10. package/dist/admin/client/shop/coupon-form.svelte.d.ts +18 -0
  11. package/dist/admin/client/shop/coupon-new-page.svelte +25 -0
  12. package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +18 -0
  13. package/dist/admin/client/shop/coupons-list-page.svelte +135 -0
  14. package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +3 -0
  15. package/dist/admin/client/shop/refund-dialog.svelte +161 -0
  16. package/dist/admin/client/shop/refund-dialog.svelte.d.ts +11 -0
  17. package/dist/admin/client/shop/shipping-method-edit-page.svelte +3 -6
  18. package/dist/admin/client/shop/shipping-method-form.svelte +15 -21
  19. package/dist/admin/client/shop/shipping-method-new-page.svelte +3 -6
  20. package/dist/admin/client/shop/shipping-methods-list-page.svelte +6 -6
  21. package/dist/admin/client/shop/shop-order-detail-page.svelte +107 -27
  22. package/dist/admin/client/shop/shop-orders-list-page.svelte +49 -11
  23. package/dist/admin/client/shop/shop-products-list-page.svelte +12 -11
  24. package/dist/admin/components/layout/lang.d.ts +1 -0
  25. package/dist/admin/components/layout/lang.js +4 -2
  26. package/dist/admin/components/layout/layout-renderer.svelte +12 -11
  27. package/dist/admin/components/layout/nav-breadcrumbs.svelte +3 -5
  28. package/dist/admin/components/layout/nav-shop.svelte +3 -1
  29. package/dist/admin/components/layout/nav-user.svelte +6 -4
  30. package/dist/admin/components/layout/site-header.svelte +11 -5
  31. package/dist/admin/remote/shop.remote.d.ts +122 -3
  32. package/dist/admin/remote/shop.remote.js +161 -5
  33. package/dist/db-postgres/schema/shop/couponRedemptions.d.ts +97 -0
  34. package/dist/db-postgres/schema/shop/couponRedemptions.js +21 -0
  35. package/dist/db-postgres/schema/shop/coupons.d.ts +197 -0
  36. package/dist/db-postgres/schema/shop/coupons.js +18 -0
  37. package/dist/db-postgres/schema/shop/index.d.ts +4 -0
  38. package/dist/db-postgres/schema/shop/index.js +4 -0
  39. package/dist/db-postgres/schema/shop/product.d.ts +17 -0
  40. package/dist/db-postgres/schema/shop/product.js +2 -0
  41. package/dist/db-postgres/schema/shop/refunds.d.ts +214 -0
  42. package/dist/db-postgres/schema/shop/refunds.js +21 -0
  43. package/dist/db-postgres/schema/shop/webhookEvents.d.ts +183 -0
  44. package/dist/db-postgres/schema/shop/webhookEvents.js +22 -0
  45. package/dist/shop/adapters/payu/client.d.ts +9 -0
  46. package/dist/shop/adapters/payu/client.js +29 -0
  47. package/dist/shop/adapters/payu/index.js +17 -1
  48. package/dist/shop/adapters/stripe/index.d.ts +64 -0
  49. package/dist/shop/adapters/stripe/index.js +169 -0
  50. package/dist/shop/adapters/stripe/payload.d.ts +38 -0
  51. package/dist/shop/adapters/stripe/payload.js +90 -0
  52. package/dist/shop/adapters/stripe/status-map.d.ts +11 -0
  53. package/dist/shop/adapters/stripe/status-map.js +31 -0
  54. package/dist/shop/cart/coupon-cookie.d.ts +7 -0
  55. package/dist/shop/cart/coupon-cookie.js +32 -0
  56. package/dist/shop/cart/types.d.ts +12 -0
  57. package/dist/shop/client/index.d.ts +118 -0
  58. package/dist/shop/client/index.js +39 -1
  59. package/dist/shop/http/cart-handler.d.ts +8 -0
  60. package/dist/shop/http/cart-handler.js +60 -1
  61. package/dist/shop/http/checkout-handler.js +7 -3
  62. package/dist/shop/http/index.d.ts +1 -1
  63. package/dist/shop/http/index.js +1 -1
  64. package/dist/shop/http/retry-payment-handler.js +1 -1
  65. package/dist/shop/http/webhook-handler.js +19 -1
  66. package/dist/shop/http/webhook-idempotency.d.ts +16 -0
  67. package/dist/shop/http/webhook-idempotency.js +51 -0
  68. package/dist/shop/http/webhook-logic.js +2 -1
  69. package/dist/shop/index.d.ts +3 -1
  70. package/dist/shop/index.js +3 -1
  71. package/dist/shop/pricing.d.ts +15 -0
  72. package/dist/shop/pricing.js +22 -0
  73. package/dist/shop/server/cart-hydrate.d.ts +1 -0
  74. package/dist/shop/server/cart-hydrate.js +58 -10
  75. package/dist/shop/server/coupons.d.ts +53 -0
  76. package/dist/shop/server/coupons.js +117 -0
  77. package/dist/shop/server/email.d.ts +15 -0
  78. package/dist/shop/server/email.js +46 -3
  79. package/dist/shop/server/orders.d.ts +1 -0
  80. package/dist/shop/server/orders.js +120 -54
  81. package/dist/shop/server/refund.d.ts +32 -0
  82. package/dist/shop/server/refund.js +140 -0
  83. package/dist/shop/svelte/InpostPicker.svelte +4 -7
  84. package/dist/shop/svelte/OrderStatus.svelte +6 -10
  85. package/dist/shop/svelte/labels.js +4 -2
  86. package/dist/shop/types.d.ts +41 -1
  87. package/dist/updates/0.25.0/index.d.ts +2 -0
  88. package/dist/updates/0.25.0/index.js +89 -0
  89. package/dist/updates/index.js +64 -1
  90. package/package.json +6 -1
@@ -77,7 +77,7 @@ export declare const reorderShippingMethodsCmd: import("@sveltejs/kit").RemoteCo
77
77
  success: boolean;
78
78
  }>>;
79
79
  export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunction<{
80
- status?: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected" | undefined;
80
+ status?: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected" | "refunded" | undefined;
81
81
  email?: string | undefined;
82
82
  limit?: number | undefined;
83
83
  offset?: number | undefined;
@@ -173,7 +173,7 @@ export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFuncti
173
173
  } | null>;
174
174
  export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand<{
175
175
  orderId: string;
176
- status: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected";
176
+ status: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected" | "refunded";
177
177
  note?: string | undefined;
178
178
  }, Promise<{
179
179
  number: string;
@@ -211,7 +211,7 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
211
211
  }>>;
212
212
  export declare const resendOrderEmailCmd: import("@sveltejs/kit").RemoteCommand<{
213
213
  orderId: string;
214
- status: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected";
214
+ status: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected" | "refunded";
215
215
  }, Promise<{
216
216
  success: boolean;
217
217
  }>>;
@@ -246,3 +246,122 @@ export declare const listShopableCollections: import("@sveltejs/kit").RemoteQuer
246
246
  plural?: import("../../types/languages.js").Localized;
247
247
  } | undefined;
248
248
  }[]>;
249
+ export declare const getOrderRefundsAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
250
+ refunds: {
251
+ id: string;
252
+ status: import("../../db-postgres/schema/shop/index.js").ShopRefundStatus;
253
+ createdAt: Date;
254
+ updatedAt: Date;
255
+ currency: string;
256
+ orderId: string;
257
+ provider: string;
258
+ providerRef: string | null;
259
+ amount: number;
260
+ paymentId: string | null;
261
+ reason: string | null;
262
+ createdBy: string | null;
263
+ }[];
264
+ refundedAmount: number;
265
+ remainingRefundable: number;
266
+ refundSupported: boolean;
267
+ paymentMethod: string | null;
268
+ currency: string;
269
+ totalGross: number;
270
+ } | null>;
271
+ export declare const refundOrderCmd: import("@sveltejs/kit").RemoteCommand<{
272
+ orderId: string;
273
+ amount?: number | undefined;
274
+ reason?: string | undefined;
275
+ }, Promise<{
276
+ refund: import("../../shop/server/refund.js").ShopRefundRow;
277
+ remainingRefundable: number;
278
+ orderStatusChanged: boolean;
279
+ success: true;
280
+ code?: undefined;
281
+ error?: undefined;
282
+ } | {
283
+ success: false;
284
+ code: "order_not_found" | "order_not_paid" | "no_provider_ref" | "unknown_provider" | "refund_unsupported" | "invalid_amount" | "amount_exceeds_remaining" | "provider_error";
285
+ error: string;
286
+ }>>;
287
+ export declare const listCouponsAdmin: import("@sveltejs/kit").RemoteQueryFunction<void, {
288
+ id: string;
289
+ code: string;
290
+ type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
291
+ value: string;
292
+ minOrderAmount: number | null;
293
+ maxUses: number | null;
294
+ usedCount: number;
295
+ expiresAt: Date | null;
296
+ isActive: boolean;
297
+ createdAt: Date;
298
+ updatedAt: Date;
299
+ }[]>;
300
+ export declare const getCouponAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
301
+ id: string;
302
+ code: string;
303
+ type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
304
+ value: string;
305
+ minOrderAmount: number | null;
306
+ maxUses: number | null;
307
+ usedCount: number;
308
+ expiresAt: Date | null;
309
+ isActive: boolean;
310
+ createdAt: Date;
311
+ updatedAt: Date;
312
+ }>;
313
+ export declare const createCouponCmd: import("@sveltejs/kit").RemoteCommand<{
314
+ code: string;
315
+ type: "fixed" | "percent";
316
+ value: number;
317
+ minOrderAmount?: number | null | undefined;
318
+ maxUses?: number | null | undefined;
319
+ expiresAt?: string | null | undefined;
320
+ isActive?: boolean | undefined;
321
+ }, Promise<{
322
+ code: string;
323
+ id: string;
324
+ type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
325
+ createdAt: Date;
326
+ updatedAt: Date;
327
+ isActive: boolean;
328
+ expiresAt: Date | null;
329
+ value: string;
330
+ minOrderAmount: number | null;
331
+ maxUses: number | null;
332
+ usedCount: number;
333
+ }>>;
334
+ export declare const updateCouponCmd: import("@sveltejs/kit").RemoteCommand<{
335
+ id: string;
336
+ input: {
337
+ code?: string | undefined;
338
+ type?: "fixed" | "percent" | undefined;
339
+ value?: number | undefined;
340
+ minOrderAmount?: number | null | undefined;
341
+ maxUses?: number | null | undefined;
342
+ expiresAt?: string | null | undefined;
343
+ isActive?: boolean | undefined;
344
+ };
345
+ }, Promise<{
346
+ id: string;
347
+ code: string;
348
+ type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
349
+ value: string;
350
+ minOrderAmount: number | null;
351
+ maxUses: number | null;
352
+ usedCount: number;
353
+ expiresAt: Date | null;
354
+ isActive: boolean;
355
+ createdAt: Date;
356
+ updatedAt: Date;
357
+ }>>;
358
+ export declare const deleteCouponCmd: import("@sveltejs/kit").RemoteCommand<string, Promise<{
359
+ success: boolean;
360
+ }>>;
361
+ export declare const exportOrdersCsv: import("@sveltejs/kit").RemoteQueryFunction<{
362
+ status?: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected" | "refunded" | undefined;
363
+ email?: string | undefined;
364
+ } | undefined, {
365
+ csv: string;
366
+ count: number;
367
+ }>;
@@ -6,6 +6,10 @@ import { createShippingMethod, deleteShippingMethod, getShippingMethod, listShip
6
6
  import { getOrderById, getOrderItems, getOrderStatusHistory, listOrders, updateOrderStatus } from '../../shop/server/orders.js';
7
7
  import { cancelShipmentForOrder, createShipmentForOrder } from '../../shop/server/shipments.js';
8
8
  import { sendOrderStatusEmail } from '../../shop/server/email.js';
9
+ import { getRefundedAmount, listRefunds, refundOrder, RefundError } from '../../shop/server/refund.js';
10
+ import { getShopDb } from '../../shop/server/db.js';
11
+ import { shopCouponsTable } from '../../db-postgres/schema/shop/index.js';
12
+ import { eq } from 'drizzle-orm';
9
13
  import { requireAuth } from './middleware/auth.js';
10
14
  export const getShopEnabled = query(async () => {
11
15
  return getCMS().shopConfig !== null;
@@ -113,7 +117,8 @@ const orderStatusSchema = z.enum([
113
117
  'sent',
114
118
  'done',
115
119
  'cancelled',
116
- 'paymentRejected'
120
+ 'paymentRejected',
121
+ 'refunded'
117
122
  ]);
118
123
  export const listOrdersAdmin = query(z
119
124
  .object({
@@ -131,10 +136,7 @@ export const getOrderForAdmin = query(z.string(), async (id) => {
131
136
  const order = await getOrderById(id);
132
137
  if (!order)
133
138
  return null;
134
- const [items, history] = await Promise.all([
135
- getOrderItems(id),
136
- getOrderStatusHistory(id)
137
- ]);
139
+ const [items, history] = await Promise.all([getOrderItems(id), getOrderStatusHistory(id)]);
138
140
  return { order, items, history };
139
141
  });
140
142
  export const updateOrderStatusCmd = command(z.object({
@@ -188,3 +190,157 @@ export const listShopableCollections = query(async () => {
188
190
  labels: c.labels
189
191
  }));
190
192
  });
193
+ // Refunds ────────────────────────────────────────────────────────────────────
194
+ export const getOrderRefundsAdmin = query(z.string(), async (orderId) => {
195
+ requireAuth();
196
+ const [refunds, refundedAmount, order] = await Promise.all([
197
+ listRefunds(orderId),
198
+ getRefundedAmount(orderId),
199
+ getOrderById(orderId)
200
+ ]);
201
+ if (!order)
202
+ return null;
203
+ const shop = getCMS().shopConfig;
204
+ const adapter = shop?.payment.find((a) => a.id === order.paymentMethod);
205
+ const refundSupported = !!(adapter && typeof adapter.refund === 'function');
206
+ return {
207
+ refunds,
208
+ refundedAmount,
209
+ remainingRefundable: Math.max(order.totalGross - refundedAmount, 0),
210
+ refundSupported,
211
+ paymentMethod: order.paymentMethod,
212
+ currency: order.currency,
213
+ totalGross: order.totalGross
214
+ };
215
+ });
216
+ export const refundOrderCmd = command(z.object({
217
+ orderId: z.string(),
218
+ amount: z.number().int().positive().optional(),
219
+ reason: z.string().max(500).optional()
220
+ }), async ({ orderId, amount, reason }) => {
221
+ requireAuth();
222
+ try {
223
+ const result = await refundOrder({ orderId, amount, reason, createdBy: 'admin' });
224
+ return { success: true, ...result };
225
+ }
226
+ catch (err) {
227
+ if (err instanceof RefundError) {
228
+ return { success: false, code: err.code, error: err.message };
229
+ }
230
+ const message = err instanceof Error ? err.message : 'Refund failed';
231
+ return { success: false, code: 'provider_error', error: message };
232
+ }
233
+ });
234
+ // Coupons ────────────────────────────────────────────────────────────────────
235
+ const couponInputSchema = z.object({
236
+ code: z.string().min(1).max(64),
237
+ type: z.enum(['percent', 'fixed']),
238
+ value: z.number().nonnegative().max(1e9),
239
+ minOrderAmount: z.number().int().nonnegative().nullable().optional(),
240
+ maxUses: z.number().int().positive().nullable().optional(),
241
+ expiresAt: z.string().datetime().nullable().optional(),
242
+ isActive: z.boolean().optional()
243
+ });
244
+ export const listCouponsAdmin = query(async () => {
245
+ requireAuth();
246
+ const db = getShopDb();
247
+ return db.select().from(shopCouponsTable).orderBy(shopCouponsTable.createdAt);
248
+ });
249
+ export const getCouponAdmin = query(z.string(), async (id) => {
250
+ requireAuth();
251
+ const db = getShopDb();
252
+ const [row] = await db.select().from(shopCouponsTable).where(eq(shopCouponsTable.id, id));
253
+ return row ?? null;
254
+ });
255
+ export const createCouponCmd = command(couponInputSchema, async (input) => {
256
+ requireAuth();
257
+ const db = getShopDb();
258
+ const code = input.code.trim().toUpperCase();
259
+ const [row] = await db
260
+ .insert(shopCouponsTable)
261
+ .values({
262
+ code,
263
+ type: input.type,
264
+ value: String(input.value),
265
+ minOrderAmount: input.minOrderAmount ?? null,
266
+ maxUses: input.maxUses ?? null,
267
+ expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,
268
+ isActive: input.isActive ?? true
269
+ })
270
+ .returning();
271
+ return row;
272
+ });
273
+ export const updateCouponCmd = command(z.object({ id: z.string(), input: couponInputSchema.partial() }), async ({ id, input }) => {
274
+ requireAuth();
275
+ const db = getShopDb();
276
+ const patch = { updatedAt: new Date() };
277
+ if (input.code !== undefined)
278
+ patch.code = input.code.trim().toUpperCase();
279
+ if (input.type !== undefined)
280
+ patch.type = input.type;
281
+ if (input.value !== undefined)
282
+ patch.value = String(input.value);
283
+ if (input.minOrderAmount !== undefined)
284
+ patch.minOrderAmount = input.minOrderAmount;
285
+ if (input.maxUses !== undefined)
286
+ patch.maxUses = input.maxUses;
287
+ if (input.expiresAt !== undefined)
288
+ patch.expiresAt = input.expiresAt ? new Date(input.expiresAt) : null;
289
+ if (input.isActive !== undefined)
290
+ patch.isActive = input.isActive;
291
+ const [row] = await db
292
+ .update(shopCouponsTable)
293
+ .set(patch)
294
+ .where(eq(shopCouponsTable.id, id))
295
+ .returning();
296
+ return row;
297
+ });
298
+ export const deleteCouponCmd = command(z.string(), async (id) => {
299
+ requireAuth();
300
+ const db = getShopDb();
301
+ await db.delete(shopCouponsTable).where(eq(shopCouponsTable.id, id));
302
+ return { success: true };
303
+ });
304
+ // CSV export ─────────────────────────────────────────────────────────────────
305
+ function csvEscape(value) {
306
+ if (value == null)
307
+ return '';
308
+ const s = String(value);
309
+ if (/[",\n\r]/.test(s))
310
+ return `"${s.replace(/"/g, '""')}"`;
311
+ return s;
312
+ }
313
+ const exportFiltersSchema = z
314
+ .object({
315
+ status: orderStatusSchema.optional(),
316
+ email: z.string().optional()
317
+ })
318
+ .optional();
319
+ export const exportOrdersCsv = query(exportFiltersSchema, async (opts) => {
320
+ requireAuth();
321
+ const orders = await listOrders({ ...(opts ?? {}), limit: 10_000 });
322
+ const header = [
323
+ 'number',
324
+ 'status',
325
+ 'customerEmail',
326
+ 'customerName',
327
+ 'totalGross',
328
+ 'currency',
329
+ 'shippingGross',
330
+ 'paymentMethod',
331
+ 'createdAt'
332
+ ];
333
+ const rows = orders.map((o) => [
334
+ o.number,
335
+ o.status,
336
+ o.customerEmail,
337
+ o.customerName ?? '',
338
+ (o.totalGross / 100).toFixed(2),
339
+ o.currency,
340
+ (o.shippingGross / 100).toFixed(2),
341
+ o.paymentMethod ?? '',
342
+ o.createdAt instanceof Date ? o.createdAt.toISOString() : String(o.createdAt)
343
+ ]);
344
+ const csv = [header, ...rows].map((row) => row.map(csvEscape).join(',')).join('\r\n');
345
+ return { csv, count: orders.length };
346
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * One row per coupon redemption. UNIQUE(orderId) prevents double-charge
3
+ * accounting if a checkout retries — second redemption for the same order
4
+ * fails at INSERT, server treats it as already-applied.
5
+ */
6
+ export declare const shopCouponRedemptionsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
7
+ name: "shop_coupon_redemptions";
8
+ schema: undefined;
9
+ columns: {
10
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
11
+ name: "id";
12
+ tableName: "shop_coupon_redemptions";
13
+ dataType: "string";
14
+ columnType: "PgUUID";
15
+ data: string;
16
+ driverParam: string;
17
+ notNull: true;
18
+ hasDefault: true;
19
+ isPrimaryKey: true;
20
+ isAutoincrement: false;
21
+ hasRuntimeDefault: false;
22
+ enumValues: undefined;
23
+ baseColumn: never;
24
+ identity: undefined;
25
+ generated: undefined;
26
+ }, {}, {}>;
27
+ couponId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
28
+ name: "coupon_id";
29
+ tableName: "shop_coupon_redemptions";
30
+ dataType: "string";
31
+ columnType: "PgUUID";
32
+ data: string;
33
+ driverParam: string;
34
+ notNull: true;
35
+ hasDefault: false;
36
+ isPrimaryKey: false;
37
+ isAutoincrement: false;
38
+ hasRuntimeDefault: false;
39
+ enumValues: undefined;
40
+ baseColumn: never;
41
+ identity: undefined;
42
+ generated: undefined;
43
+ }, {}, {}>;
44
+ orderId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
45
+ name: "order_id";
46
+ tableName: "shop_coupon_redemptions";
47
+ dataType: "string";
48
+ columnType: "PgUUID";
49
+ data: string;
50
+ driverParam: string;
51
+ notNull: true;
52
+ hasDefault: false;
53
+ isPrimaryKey: false;
54
+ isAutoincrement: false;
55
+ hasRuntimeDefault: false;
56
+ enumValues: undefined;
57
+ baseColumn: never;
58
+ identity: undefined;
59
+ generated: undefined;
60
+ }, {}, {}>;
61
+ discountAmount: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
62
+ name: "discount_amount";
63
+ tableName: "shop_coupon_redemptions";
64
+ dataType: "number";
65
+ columnType: "PgInteger";
66
+ data: number;
67
+ driverParam: string | number;
68
+ notNull: true;
69
+ hasDefault: false;
70
+ isPrimaryKey: false;
71
+ isAutoincrement: false;
72
+ hasRuntimeDefault: false;
73
+ enumValues: undefined;
74
+ baseColumn: never;
75
+ identity: undefined;
76
+ generated: undefined;
77
+ }, {}, {}>;
78
+ redeemedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
79
+ name: "redeemed_at";
80
+ tableName: "shop_coupon_redemptions";
81
+ dataType: "date";
82
+ columnType: "PgTimestamp";
83
+ data: Date;
84
+ driverParam: string;
85
+ notNull: true;
86
+ hasDefault: true;
87
+ isPrimaryKey: false;
88
+ isAutoincrement: false;
89
+ hasRuntimeDefault: false;
90
+ enumValues: undefined;
91
+ baseColumn: never;
92
+ identity: undefined;
93
+ generated: undefined;
94
+ }, {}, {}>;
95
+ };
96
+ dialect: "pg";
97
+ }>;
@@ -0,0 +1,21 @@
1
+ import { integer, pgTable, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
2
+ import { shopCouponsTable } from './coupons.js';
3
+ import { shopOrdersTable } from './order.js';
4
+ /**
5
+ * One row per coupon redemption. UNIQUE(orderId) prevents double-charge
6
+ * accounting if a checkout retries — second redemption for the same order
7
+ * fails at INSERT, server treats it as already-applied.
8
+ */
9
+ export const shopCouponRedemptionsTable = pgTable('shop_coupon_redemptions', {
10
+ id: uuid('id').primaryKey().defaultRandom(),
11
+ couponId: uuid('coupon_id')
12
+ .notNull()
13
+ .references(() => shopCouponsTable.id, { onDelete: 'cascade' }),
14
+ orderId: uuid('order_id')
15
+ .notNull()
16
+ .references(() => shopOrdersTable.id, { onDelete: 'cascade' }),
17
+ discountAmount: integer('discount_amount').notNull(),
18
+ redeemedAt: timestamp('redeemed_at', { withTimezone: true }).defaultNow().notNull()
19
+ }, (t) => ({
20
+ orderUnique: uniqueIndex('shop_coupon_redemptions_order_unique').on(t.orderId)
21
+ }));
@@ -0,0 +1,197 @@
1
+ export type ShopCouponType = 'percent' | 'fixed';
2
+ export declare const shopCouponsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
3
+ name: "shop_coupons";
4
+ schema: undefined;
5
+ columns: {
6
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
7
+ name: "id";
8
+ tableName: "shop_coupons";
9
+ dataType: "string";
10
+ columnType: "PgUUID";
11
+ data: string;
12
+ driverParam: string;
13
+ notNull: true;
14
+ hasDefault: true;
15
+ isPrimaryKey: true;
16
+ isAutoincrement: false;
17
+ hasRuntimeDefault: false;
18
+ enumValues: undefined;
19
+ baseColumn: never;
20
+ identity: undefined;
21
+ generated: undefined;
22
+ }, {}, {}>;
23
+ code: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
24
+ name: "code";
25
+ tableName: "shop_coupons";
26
+ dataType: "string";
27
+ columnType: "PgText";
28
+ data: string;
29
+ driverParam: string;
30
+ notNull: true;
31
+ hasDefault: false;
32
+ isPrimaryKey: false;
33
+ isAutoincrement: false;
34
+ hasRuntimeDefault: false;
35
+ enumValues: [string, ...string[]];
36
+ baseColumn: never;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }, {}, {}>;
40
+ type: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
41
+ name: "type";
42
+ tableName: "shop_coupons";
43
+ dataType: "string";
44
+ columnType: "PgText";
45
+ data: ShopCouponType;
46
+ driverParam: string;
47
+ notNull: true;
48
+ hasDefault: false;
49
+ isPrimaryKey: false;
50
+ isAutoincrement: false;
51
+ hasRuntimeDefault: false;
52
+ enumValues: [string, ...string[]];
53
+ baseColumn: never;
54
+ identity: undefined;
55
+ generated: undefined;
56
+ }, {}, {
57
+ $type: ShopCouponType;
58
+ }>;
59
+ value: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
60
+ name: "value";
61
+ tableName: "shop_coupons";
62
+ dataType: "string";
63
+ columnType: "PgNumeric";
64
+ data: string;
65
+ driverParam: string;
66
+ notNull: true;
67
+ hasDefault: false;
68
+ isPrimaryKey: false;
69
+ isAutoincrement: false;
70
+ hasRuntimeDefault: false;
71
+ enumValues: undefined;
72
+ baseColumn: never;
73
+ identity: undefined;
74
+ generated: undefined;
75
+ }, {}, {}>;
76
+ minOrderAmount: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
77
+ name: "min_order_amount";
78
+ tableName: "shop_coupons";
79
+ dataType: "number";
80
+ columnType: "PgInteger";
81
+ data: number;
82
+ driverParam: string | number;
83
+ notNull: false;
84
+ hasDefault: false;
85
+ isPrimaryKey: false;
86
+ isAutoincrement: false;
87
+ hasRuntimeDefault: false;
88
+ enumValues: undefined;
89
+ baseColumn: never;
90
+ identity: undefined;
91
+ generated: undefined;
92
+ }, {}, {}>;
93
+ maxUses: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
94
+ name: "max_uses";
95
+ tableName: "shop_coupons";
96
+ dataType: "number";
97
+ columnType: "PgInteger";
98
+ data: number;
99
+ driverParam: string | number;
100
+ notNull: false;
101
+ hasDefault: false;
102
+ isPrimaryKey: false;
103
+ isAutoincrement: false;
104
+ hasRuntimeDefault: false;
105
+ enumValues: undefined;
106
+ baseColumn: never;
107
+ identity: undefined;
108
+ generated: undefined;
109
+ }, {}, {}>;
110
+ usedCount: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
111
+ name: "used_count";
112
+ tableName: "shop_coupons";
113
+ dataType: "number";
114
+ columnType: "PgInteger";
115
+ data: number;
116
+ driverParam: string | number;
117
+ notNull: true;
118
+ hasDefault: true;
119
+ isPrimaryKey: false;
120
+ isAutoincrement: false;
121
+ hasRuntimeDefault: false;
122
+ enumValues: undefined;
123
+ baseColumn: never;
124
+ identity: undefined;
125
+ generated: undefined;
126
+ }, {}, {}>;
127
+ expiresAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
128
+ name: "expires_at";
129
+ tableName: "shop_coupons";
130
+ dataType: "date";
131
+ columnType: "PgTimestamp";
132
+ data: Date;
133
+ driverParam: string;
134
+ notNull: false;
135
+ hasDefault: false;
136
+ isPrimaryKey: false;
137
+ isAutoincrement: false;
138
+ hasRuntimeDefault: false;
139
+ enumValues: undefined;
140
+ baseColumn: never;
141
+ identity: undefined;
142
+ generated: undefined;
143
+ }, {}, {}>;
144
+ isActive: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
145
+ name: "is_active";
146
+ tableName: "shop_coupons";
147
+ dataType: "boolean";
148
+ columnType: "PgBoolean";
149
+ data: boolean;
150
+ driverParam: boolean;
151
+ notNull: true;
152
+ hasDefault: true;
153
+ isPrimaryKey: false;
154
+ isAutoincrement: false;
155
+ hasRuntimeDefault: false;
156
+ enumValues: undefined;
157
+ baseColumn: never;
158
+ identity: undefined;
159
+ generated: undefined;
160
+ }, {}, {}>;
161
+ createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
162
+ name: "created_at";
163
+ tableName: "shop_coupons";
164
+ dataType: "date";
165
+ columnType: "PgTimestamp";
166
+ data: Date;
167
+ driverParam: string;
168
+ notNull: true;
169
+ hasDefault: true;
170
+ isPrimaryKey: false;
171
+ isAutoincrement: false;
172
+ hasRuntimeDefault: false;
173
+ enumValues: undefined;
174
+ baseColumn: never;
175
+ identity: undefined;
176
+ generated: undefined;
177
+ }, {}, {}>;
178
+ updatedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
179
+ name: "updated_at";
180
+ tableName: "shop_coupons";
181
+ dataType: "date";
182
+ columnType: "PgTimestamp";
183
+ data: Date;
184
+ driverParam: string;
185
+ notNull: true;
186
+ hasDefault: true;
187
+ isPrimaryKey: false;
188
+ isAutoincrement: false;
189
+ hasRuntimeDefault: false;
190
+ enumValues: undefined;
191
+ baseColumn: never;
192
+ identity: undefined;
193
+ generated: undefined;
194
+ }, {}, {}>;
195
+ };
196
+ dialect: "pg";
197
+ }>;
@@ -0,0 +1,18 @@
1
+ import { boolean, integer, numeric, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
2
+ export const shopCouponsTable = pgTable('shop_coupons', {
3
+ id: uuid('id').primaryKey().defaultRandom(),
4
+ // Stored uppercased — user input case-insensitive.
5
+ code: text('code').notNull(),
6
+ type: text('type').$type().notNull(),
7
+ // percent → integer 0-100 stored as numeric for symmetry; fixed → PLN value (precision 20,6 like product.basePrice)
8
+ value: numeric('value', { precision: 20, scale: 6 }).notNull(),
9
+ minOrderAmount: integer('min_order_amount'),
10
+ maxUses: integer('max_uses'),
11
+ usedCount: integer('used_count').notNull().default(0),
12
+ expiresAt: timestamp('expires_at', { withTimezone: true }),
13
+ isActive: boolean('is_active').notNull().default(true),
14
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
15
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
16
+ }, (t) => ({
17
+ codeUnique: uniqueIndex('shop_coupons_code_unique').on(t.code)
18
+ }));
@@ -6,3 +6,7 @@ export * from './orderItem.js';
6
6
  export * from './orderStatusHistory.js';
7
7
  export * from './payment.js';
8
8
  export * from './stockReservation.js';
9
+ export * from './refunds.js';
10
+ export * from './webhookEvents.js';
11
+ export * from './coupons.js';
12
+ export * from './couponRedemptions.js';