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
@@ -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';
@@ -104,6 +104,23 @@ export declare const shopProductsTable: import("drizzle-orm/pg-core/table", { wi
104
104
  identity: undefined;
105
105
  generated: undefined;
106
106
  }, {}, {}>;
107
+ lowStockThreshold: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
108
+ name: "low_stock_threshold";
109
+ tableName: "shop_products";
110
+ dataType: "number";
111
+ columnType: "PgInteger";
112
+ data: number;
113
+ driverParam: string | number;
114
+ notNull: false;
115
+ hasDefault: false;
116
+ isPrimaryKey: false;
117
+ isAutoincrement: false;
118
+ hasRuntimeDefault: false;
119
+ enumValues: undefined;
120
+ baseColumn: never;
121
+ identity: undefined;
122
+ generated: undefined;
123
+ }, {}, {}>;
107
124
  createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
108
125
  name: "created_at";
109
126
  tableName: "shop_products";
@@ -11,6 +11,8 @@ export const shopProductsTable = pgTable('shop_products', {
11
11
  vatRate: integer('vat_rate').notNull(),
12
12
  isActive: boolean('is_active').default(true).notNull(),
13
13
  sortOrder: integer('sort_order'),
14
+ // Null = alert disabled. When stock decrement crosses threshold, fire-and-forget admin email.
15
+ lowStockThreshold: integer('low_stock_threshold'),
14
16
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
15
17
  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
16
18
  });
@@ -0,0 +1,214 @@
1
+ export type ShopRefundStatus = 'pending' | 'succeeded' | 'failed';
2
+ export declare const shopRefundsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
3
+ name: "shop_refunds";
4
+ schema: undefined;
5
+ columns: {
6
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
7
+ name: "id";
8
+ tableName: "shop_refunds";
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
+ orderId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
24
+ name: "order_id";
25
+ tableName: "shop_refunds";
26
+ dataType: "string";
27
+ columnType: "PgUUID";
28
+ data: string;
29
+ driverParam: string;
30
+ notNull: true;
31
+ hasDefault: false;
32
+ isPrimaryKey: false;
33
+ isAutoincrement: false;
34
+ hasRuntimeDefault: false;
35
+ enumValues: undefined;
36
+ baseColumn: never;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }, {}, {}>;
40
+ paymentId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
41
+ name: "payment_id";
42
+ tableName: "shop_refunds";
43
+ dataType: "string";
44
+ columnType: "PgUUID";
45
+ data: string;
46
+ driverParam: string;
47
+ notNull: false;
48
+ hasDefault: false;
49
+ isPrimaryKey: false;
50
+ isAutoincrement: false;
51
+ hasRuntimeDefault: false;
52
+ enumValues: undefined;
53
+ baseColumn: never;
54
+ identity: undefined;
55
+ generated: undefined;
56
+ }, {}, {}>;
57
+ provider: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
58
+ name: "provider";
59
+ tableName: "shop_refunds";
60
+ dataType: "string";
61
+ columnType: "PgText";
62
+ data: string;
63
+ driverParam: string;
64
+ notNull: true;
65
+ hasDefault: false;
66
+ isPrimaryKey: false;
67
+ isAutoincrement: false;
68
+ hasRuntimeDefault: false;
69
+ enumValues: [string, ...string[]];
70
+ baseColumn: never;
71
+ identity: undefined;
72
+ generated: undefined;
73
+ }, {}, {}>;
74
+ providerRef: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
75
+ name: "provider_ref";
76
+ tableName: "shop_refunds";
77
+ dataType: "string";
78
+ columnType: "PgText";
79
+ data: string;
80
+ driverParam: string;
81
+ notNull: false;
82
+ hasDefault: false;
83
+ isPrimaryKey: false;
84
+ isAutoincrement: false;
85
+ hasRuntimeDefault: false;
86
+ enumValues: [string, ...string[]];
87
+ baseColumn: never;
88
+ identity: undefined;
89
+ generated: undefined;
90
+ }, {}, {}>;
91
+ amount: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
92
+ name: "amount";
93
+ tableName: "shop_refunds";
94
+ dataType: "number";
95
+ columnType: "PgInteger";
96
+ data: number;
97
+ driverParam: string | number;
98
+ notNull: true;
99
+ hasDefault: false;
100
+ isPrimaryKey: false;
101
+ isAutoincrement: false;
102
+ hasRuntimeDefault: false;
103
+ enumValues: undefined;
104
+ baseColumn: never;
105
+ identity: undefined;
106
+ generated: undefined;
107
+ }, {}, {}>;
108
+ currency: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
109
+ name: "currency";
110
+ tableName: "shop_refunds";
111
+ dataType: "string";
112
+ columnType: "PgText";
113
+ data: string;
114
+ driverParam: string;
115
+ notNull: true;
116
+ hasDefault: false;
117
+ isPrimaryKey: false;
118
+ isAutoincrement: false;
119
+ hasRuntimeDefault: false;
120
+ enumValues: [string, ...string[]];
121
+ baseColumn: never;
122
+ identity: undefined;
123
+ generated: undefined;
124
+ }, {}, {}>;
125
+ reason: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
126
+ name: "reason";
127
+ tableName: "shop_refunds";
128
+ dataType: "string";
129
+ columnType: "PgText";
130
+ data: string;
131
+ driverParam: string;
132
+ notNull: false;
133
+ hasDefault: false;
134
+ isPrimaryKey: false;
135
+ isAutoincrement: false;
136
+ hasRuntimeDefault: false;
137
+ enumValues: [string, ...string[]];
138
+ baseColumn: never;
139
+ identity: undefined;
140
+ generated: undefined;
141
+ }, {}, {}>;
142
+ status: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
143
+ name: "status";
144
+ tableName: "shop_refunds";
145
+ dataType: "string";
146
+ columnType: "PgText";
147
+ data: ShopRefundStatus;
148
+ driverParam: string;
149
+ notNull: true;
150
+ hasDefault: true;
151
+ isPrimaryKey: false;
152
+ isAutoincrement: false;
153
+ hasRuntimeDefault: false;
154
+ enumValues: [string, ...string[]];
155
+ baseColumn: never;
156
+ identity: undefined;
157
+ generated: undefined;
158
+ }, {}, {
159
+ $type: ShopRefundStatus;
160
+ }>;
161
+ createdBy: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
162
+ name: "created_by";
163
+ tableName: "shop_refunds";
164
+ dataType: "string";
165
+ columnType: "PgText";
166
+ data: string;
167
+ driverParam: string;
168
+ notNull: false;
169
+ hasDefault: false;
170
+ isPrimaryKey: false;
171
+ isAutoincrement: false;
172
+ hasRuntimeDefault: false;
173
+ enumValues: [string, ...string[]];
174
+ baseColumn: never;
175
+ identity: undefined;
176
+ generated: undefined;
177
+ }, {}, {}>;
178
+ createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
179
+ name: "created_at";
180
+ tableName: "shop_refunds";
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
+ updatedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
196
+ name: "updated_at";
197
+ tableName: "shop_refunds";
198
+ dataType: "date";
199
+ columnType: "PgTimestamp";
200
+ data: Date;
201
+ driverParam: string;
202
+ notNull: true;
203
+ hasDefault: true;
204
+ isPrimaryKey: false;
205
+ isAutoincrement: false;
206
+ hasRuntimeDefault: false;
207
+ enumValues: undefined;
208
+ baseColumn: never;
209
+ identity: undefined;
210
+ generated: undefined;
211
+ }, {}, {}>;
212
+ };
213
+ dialect: "pg";
214
+ }>;
@@ -0,0 +1,21 @@
1
+ import { integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
2
+ import { shopOrdersTable } from './order.js';
3
+ import { shopPaymentsTable } from './payment.js';
4
+ export const shopRefundsTable = pgTable('shop_refunds', {
5
+ id: uuid('id').primaryKey().defaultRandom(),
6
+ orderId: uuid('order_id')
7
+ .notNull()
8
+ .references(() => shopOrdersTable.id, { onDelete: 'cascade' }),
9
+ paymentId: uuid('payment_id').references(() => shopPaymentsTable.id, {
10
+ onDelete: 'set null'
11
+ }),
12
+ provider: text('provider').notNull(),
13
+ providerRef: text('provider_ref'),
14
+ amount: integer('amount').notNull(),
15
+ currency: text('currency').notNull(),
16
+ reason: text('reason'),
17
+ status: text('status').$type().notNull().default('pending'),
18
+ createdBy: text('created_by'),
19
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
20
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
21
+ });
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Persistent webhook idempotency log. One row per unique (provider, eventId).
3
+ * Inserted before processing — `processedAt` set after success. Race-safe via
4
+ * UNIQUE constraint: concurrent duplicate events fail at INSERT, second caller
5
+ * returns idempotent response without re-processing.
6
+ */
7
+ export declare const shopWebhookEventsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
8
+ name: "shop_webhook_events";
9
+ schema: undefined;
10
+ columns: {
11
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
12
+ name: "id";
13
+ tableName: "shop_webhook_events";
14
+ dataType: "string";
15
+ columnType: "PgUUID";
16
+ data: string;
17
+ driverParam: string;
18
+ notNull: true;
19
+ hasDefault: true;
20
+ isPrimaryKey: true;
21
+ isAutoincrement: false;
22
+ hasRuntimeDefault: false;
23
+ enumValues: undefined;
24
+ baseColumn: never;
25
+ identity: undefined;
26
+ generated: undefined;
27
+ }, {}, {}>;
28
+ provider: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
29
+ name: "provider";
30
+ tableName: "shop_webhook_events";
31
+ dataType: "string";
32
+ columnType: "PgText";
33
+ data: string;
34
+ driverParam: string;
35
+ notNull: true;
36
+ hasDefault: false;
37
+ isPrimaryKey: false;
38
+ isAutoincrement: false;
39
+ hasRuntimeDefault: false;
40
+ enumValues: [string, ...string[]];
41
+ baseColumn: never;
42
+ identity: undefined;
43
+ generated: undefined;
44
+ }, {}, {}>;
45
+ eventId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
46
+ name: "event_id";
47
+ tableName: "shop_webhook_events";
48
+ dataType: "string";
49
+ columnType: "PgText";
50
+ data: string;
51
+ driverParam: string;
52
+ notNull: true;
53
+ hasDefault: false;
54
+ isPrimaryKey: false;
55
+ isAutoincrement: false;
56
+ hasRuntimeDefault: false;
57
+ enumValues: [string, ...string[]];
58
+ baseColumn: never;
59
+ identity: undefined;
60
+ generated: undefined;
61
+ }, {}, {}>;
62
+ eventType: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
63
+ name: "event_type";
64
+ tableName: "shop_webhook_events";
65
+ dataType: "string";
66
+ columnType: "PgText";
67
+ data: string;
68
+ driverParam: string;
69
+ notNull: false;
70
+ hasDefault: false;
71
+ isPrimaryKey: false;
72
+ isAutoincrement: false;
73
+ hasRuntimeDefault: false;
74
+ enumValues: [string, ...string[]];
75
+ baseColumn: never;
76
+ identity: undefined;
77
+ generated: undefined;
78
+ }, {}, {}>;
79
+ orderId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
80
+ name: "order_id";
81
+ tableName: "shop_webhook_events";
82
+ dataType: "string";
83
+ columnType: "PgUUID";
84
+ data: string;
85
+ driverParam: string;
86
+ notNull: false;
87
+ hasDefault: false;
88
+ isPrimaryKey: false;
89
+ isAutoincrement: false;
90
+ hasRuntimeDefault: false;
91
+ enumValues: undefined;
92
+ baseColumn: never;
93
+ identity: undefined;
94
+ generated: undefined;
95
+ }, {}, {}>;
96
+ orderNumber: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
97
+ name: "order_number";
98
+ tableName: "shop_webhook_events";
99
+ dataType: "string";
100
+ columnType: "PgText";
101
+ data: string;
102
+ driverParam: string;
103
+ notNull: false;
104
+ hasDefault: false;
105
+ isPrimaryKey: false;
106
+ isAutoincrement: false;
107
+ hasRuntimeDefault: false;
108
+ enumValues: [string, ...string[]];
109
+ baseColumn: never;
110
+ identity: undefined;
111
+ generated: undefined;
112
+ }, {}, {}>;
113
+ payloadHash: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
114
+ name: "payload_hash";
115
+ tableName: "shop_webhook_events";
116
+ dataType: "string";
117
+ columnType: "PgText";
118
+ data: string;
119
+ driverParam: string;
120
+ notNull: false;
121
+ hasDefault: false;
122
+ isPrimaryKey: false;
123
+ isAutoincrement: false;
124
+ hasRuntimeDefault: false;
125
+ enumValues: [string, ...string[]];
126
+ baseColumn: never;
127
+ identity: undefined;
128
+ generated: undefined;
129
+ }, {}, {}>;
130
+ raw: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
131
+ name: "raw";
132
+ tableName: "shop_webhook_events";
133
+ dataType: "json";
134
+ columnType: "PgJsonb";
135
+ data: unknown;
136
+ driverParam: unknown;
137
+ notNull: false;
138
+ hasDefault: false;
139
+ isPrimaryKey: false;
140
+ isAutoincrement: false;
141
+ hasRuntimeDefault: false;
142
+ enumValues: undefined;
143
+ baseColumn: never;
144
+ identity: undefined;
145
+ generated: undefined;
146
+ }, {}, {}>;
147
+ receivedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
148
+ name: "received_at";
149
+ tableName: "shop_webhook_events";
150
+ dataType: "date";
151
+ columnType: "PgTimestamp";
152
+ data: Date;
153
+ driverParam: string;
154
+ notNull: true;
155
+ hasDefault: true;
156
+ isPrimaryKey: false;
157
+ isAutoincrement: false;
158
+ hasRuntimeDefault: false;
159
+ enumValues: undefined;
160
+ baseColumn: never;
161
+ identity: undefined;
162
+ generated: undefined;
163
+ }, {}, {}>;
164
+ processedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
165
+ name: "processed_at";
166
+ tableName: "shop_webhook_events";
167
+ dataType: "date";
168
+ columnType: "PgTimestamp";
169
+ data: Date;
170
+ driverParam: string;
171
+ notNull: false;
172
+ hasDefault: false;
173
+ isPrimaryKey: false;
174
+ isAutoincrement: false;
175
+ hasRuntimeDefault: false;
176
+ enumValues: undefined;
177
+ baseColumn: never;
178
+ identity: undefined;
179
+ generated: undefined;
180
+ }, {}, {}>;
181
+ };
182
+ dialect: "pg";
183
+ }>;
@@ -0,0 +1,22 @@
1
+ import { jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
2
+ import { shopOrdersTable } from './order.js';
3
+ /**
4
+ * Persistent webhook idempotency log. One row per unique (provider, eventId).
5
+ * Inserted before processing — `processedAt` set after success. Race-safe via
6
+ * UNIQUE constraint: concurrent duplicate events fail at INSERT, second caller
7
+ * returns idempotent response without re-processing.
8
+ */
9
+ export const shopWebhookEventsTable = pgTable('shop_webhook_events', {
10
+ id: uuid('id').primaryKey().defaultRandom(),
11
+ provider: text('provider').notNull(),
12
+ eventId: text('event_id').notNull(),
13
+ eventType: text('event_type'),
14
+ orderId: uuid('order_id').references(() => shopOrdersTable.id, { onDelete: 'set null' }),
15
+ orderNumber: text('order_number'),
16
+ payloadHash: text('payload_hash'),
17
+ raw: jsonb('raw'),
18
+ receivedAt: timestamp('received_at', { withTimezone: true }).defaultNow().notNull(),
19
+ processedAt: timestamp('processed_at', { withTimezone: true })
20
+ }, (t) => ({
21
+ providerEventUnique: uniqueIndex('shop_webhook_events_provider_event_unique').on(t.provider, t.eventId)
22
+ }));
@@ -19,4 +19,13 @@ export declare class PayuClient {
19
19
  orderId: string;
20
20
  }>;
21
21
  getOrder(payuOrderId: string): Promise<unknown>;
22
+ refundOrder(payuOrderId: string, input: {
23
+ description: string;
24
+ amount?: number;
25
+ currencyCode?: string;
26
+ }): Promise<{
27
+ refundId: string;
28
+ status: string;
29
+ raw: unknown;
30
+ }>;
22
31
  }
@@ -75,4 +75,33 @@ export class PayuClient {
75
75
  throw new Error(`PayU getOrder failed: ${res.status}`);
76
76
  return res.json();
77
77
  }
78
+ async refundOrder(payuOrderId, input) {
79
+ const token = await this.getAccessToken();
80
+ const refund = {
81
+ description: input.description.slice(0, 70)
82
+ };
83
+ if (input.amount !== undefined) {
84
+ refund.amount = input.amount;
85
+ if (input.currencyCode)
86
+ refund.currencyCode = input.currencyCode;
87
+ }
88
+ const res = await this.fetchImpl(`${this.base}/api/v2_1/orders/${encodeURIComponent(payuOrderId)}/refunds`, {
89
+ method: 'POST',
90
+ headers: {
91
+ Authorization: `Bearer ${token}`,
92
+ 'Content-Type': 'application/json',
93
+ Accept: 'application/json'
94
+ },
95
+ body: JSON.stringify({ refund })
96
+ });
97
+ if (!res.ok)
98
+ throw new Error(`PayU refundOrder failed: ${res.status}`);
99
+ const data = (await res.json());
100
+ if (data.status?.statusCode && data.status.statusCode !== 'SUCCESS') {
101
+ throw new Error(`PayU refund status ${data.status.statusCode}${data.status.statusDesc ? ` — ${data.status.statusDesc}` : ''}`);
102
+ }
103
+ const refundId = data.refund?.refundId ?? '';
104
+ const status = data.refund?.status ?? 'PENDING';
105
+ return { refundId, status, raw: data };
106
+ }
78
107
  }
@@ -77,11 +77,27 @@ export function payuAdapter(opts) {
77
77
  const status = parsed.order?.status ?? 'PENDING';
78
78
  if (!extOrderId)
79
79
  throw new Error('PayU webhook missing extOrderId');
80
+ // PayU sends a `notify_id` in the `properties` array — use it for idempotency.
81
+ const notifyId = parsed.properties?.find((p) => p.name === 'PAYMENT_ID' || p.name === 'TRANSACTION_ID' || p.name === 'NOTIFY_ID')?.value;
80
82
  return {
81
83
  orderNumber: extOrderId,
82
84
  status: mapPayuStatus(status),
83
85
  providerRef: payuOrderId,
84
- raw: parsed
86
+ raw: parsed,
87
+ eventId: notifyId ? `${payuOrderId}:${status}:${notifyId}` : `${payuOrderId}:${status}`,
88
+ eventType: status
89
+ };
90
+ },
91
+ async refund(input) {
92
+ const result = await client.refundOrder(input.providerRef, {
93
+ description: input.reason ?? 'Refund',
94
+ amount: input.amount,
95
+ currencyCode: input.currency
96
+ });
97
+ return {
98
+ providerRef: result.refundId,
99
+ amount: input.amount ?? 0,
100
+ raw: result.raw
85
101
  };
86
102
  }
87
103
  };
@@ -0,0 +1,64 @@
1
+ import type { I18nText, PaymentAdapter } from '../../types.js';
2
+ type StripeNs = typeof import('stripe');
3
+ export interface StripeAdapterOptions {
4
+ /**
5
+ * Stripe secret key (`sk_live_…` / `sk_test_…`). Required.
6
+ */
7
+ secretKey: string;
8
+ /**
9
+ * Webhook signing secret (`whsec_…`) issued by Stripe per endpoint.
10
+ * Used by `handleWebhook()` to verify the `Stripe-Signature` header.
11
+ */
12
+ webhookSecret: string;
13
+ /**
14
+ * Absolute URL template the customer is redirected to after a successful
15
+ * payment. Supports `{orderNumber}`, `{orderId}`, `{accessToken}`, `{language}`.
16
+ * If omitted, falls back to `shop.orderViewUrl` (must then be absolute).
17
+ */
18
+ successUrl?: string;
19
+ /**
20
+ * Absolute URL the customer is redirected to if they cancel. If omitted,
21
+ * falls back to `shop.orderViewUrl` (same as `successUrl`).
22
+ */
23
+ cancelUrl?: string;
24
+ /**
25
+ * Stripe API version. Defaults to a recent stable release. Override only
26
+ * if your account is pinned to a different one.
27
+ */
28
+ apiVersion?: string;
29
+ /**
30
+ * Display name for the synthesised line item shown on the Stripe receipt.
31
+ * Default: `Order {number}` per language fallback (PL primary).
32
+ */
33
+ productName?: I18nText;
34
+ id?: string;
35
+ label?: I18nText;
36
+ /**
37
+ * Override the lazy import — primarily for testing. When provided, replaces
38
+ * `await import('stripe')`. Production code should leave this unset.
39
+ */
40
+ stripeImport?: () => Promise<StripeNs>;
41
+ }
42
+ /**
43
+ * Stripe payment adapter (Checkout Session flow).
44
+ *
45
+ * `stripe` is an **optional peer dependency** — install it in your project
46
+ * (`pnpm add stripe`) when using this adapter. The SDK is lazy-loaded on first
47
+ * `createPayment()` / `getStatus()` / `refund()` / `handleWebhook()` call;
48
+ * a missing peer throws a clear error.
49
+ *
50
+ * @public
51
+ * @example
52
+ * ```ts
53
+ * import { stripeAdapter } from 'includio-cms/shop';
54
+ *
55
+ * const stripe = stripeAdapter({
56
+ * secretKey: process.env.STRIPE_SECRET_KEY!,
57
+ * webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
58
+ * successUrl: 'https://example.com/shop/order/{orderNumber}?token={accessToken}',
59
+ * cancelUrl: 'https://example.com/shop/cancelled'
60
+ * });
61
+ * ```
62
+ */
63
+ export declare function stripeAdapter(opts: StripeAdapterOptions): PaymentAdapter;
64
+ export {};