@stacksjs/commerce 0.70.53 → 0.70.55

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 (165) hide show
  1. package/package.json +3 -2
  2. package/src/carts/destroy.ts +54 -0
  3. package/src/carts/fetch.ts +20 -0
  4. package/src/carts/index.ts +13 -0
  5. package/src/carts/store.ts +75 -0
  6. package/src/carts/update.ts +78 -0
  7. package/src/coupons/destroy.ts +49 -0
  8. package/src/coupons/fetch.ts +510 -0
  9. package/src/coupons/index.ts +30 -0
  10. package/src/coupons/store.ts +46 -0
  11. package/src/coupons/update.ts +147 -0
  12. package/src/customers/destroy.ts +62 -0
  13. package/src/customers/fetch.ts +20 -0
  14. package/src/customers/index.ts +17 -0
  15. package/src/customers/store.ts +45 -0
  16. package/src/customers/update.ts +44 -0
  17. package/src/devices/destroy.ts +54 -0
  18. package/src/devices/export.ts +98 -0
  19. package/src/devices/fetch.ts +125 -0
  20. package/src/devices/index.ts +31 -0
  21. package/src/devices/store.ts +82 -0
  22. package/src/devices/update.ts +125 -0
  23. package/src/errors/destroy.ts +54 -0
  24. package/src/errors/fetch.ts +197 -0
  25. package/src/errors/index.ts +28 -0
  26. package/src/errors/update.ts +116 -0
  27. package/src/gift-cards/destroy.ts +87 -0
  28. package/src/gift-cards/fetch.ts +431 -0
  29. package/src/gift-cards/index.ts +26 -0
  30. package/src/gift-cards/store.ts +56 -0
  31. package/src/gift-cards/update.ts +102 -0
  32. package/src/index.ts +78 -0
  33. package/src/orders/destroy.ts +111 -0
  34. package/src/orders/events.ts +121 -0
  35. package/src/orders/export.ts +158 -0
  36. package/src/orders/fetch.ts +379 -0
  37. package/src/orders/guards.ts +261 -0
  38. package/src/orders/index.ts +91 -0
  39. package/src/orders/place-order.ts +274 -0
  40. package/src/orders/store.ts +51 -0
  41. package/src/orders/totals.ts +190 -0
  42. package/src/orders/update.ts +164 -0
  43. package/src/orders/webhook.ts +312 -0
  44. package/src/payments/destroy.ts +56 -0
  45. package/src/payments/fetch.ts +288 -0
  46. package/src/payments/index.ts +16 -0
  47. package/src/payments/store.ts +63 -0
  48. package/src/payments/update.ts +52 -0
  49. package/src/product.ts +0 -0
  50. package/src/products/categories/destroy.ts +103 -0
  51. package/src/products/categories/fetch.ts +285 -0
  52. package/src/products/categories/index.ts +36 -0
  53. package/src/products/categories/store.ts +87 -0
  54. package/src/products/categories/update.ts +251 -0
  55. package/src/products/index.ts +35 -0
  56. package/src/products/items/destroy.ts +54 -0
  57. package/src/products/items/fetch.ts +282 -0
  58. package/src/products/items/index.ts +26 -0
  59. package/src/products/items/store.ts +84 -0
  60. package/src/products/items/update.ts +324 -0
  61. package/src/products/manufacturers/destroy.ts +56 -0
  62. package/src/products/manufacturers/fetch.ts +117 -0
  63. package/src/products/manufacturers/index.ts +24 -0
  64. package/src/products/manufacturers/store.ts +82 -0
  65. package/src/products/manufacturers/update.ts +113 -0
  66. package/src/products/reviews/destroy.ts +56 -0
  67. package/src/products/reviews/fetch.ts +63 -0
  68. package/src/products/reviews/index.ts +25 -0
  69. package/src/products/reviews/store.ts +46 -0
  70. package/src/products/reviews/update.ts +82 -0
  71. package/src/products/units/destroy.ts +56 -0
  72. package/src/products/units/fetch.ts +20 -0
  73. package/src/products/units/index.ts +13 -0
  74. package/src/products/units/store.ts +162 -0
  75. package/src/products/units/update.ts +159 -0
  76. package/src/products/variants/destroy.ts +56 -0
  77. package/src/products/variants/fetch.ts +20 -0
  78. package/src/products/variants/index.ts +21 -0
  79. package/src/products/variants/store.ts +136 -0
  80. package/src/products/variants/update.ts +104 -0
  81. package/src/receipts/destroy.ts +54 -0
  82. package/src/receipts/export.ts +0 -0
  83. package/src/receipts/fetch.ts +222 -0
  84. package/src/receipts/index.ts +24 -0
  85. package/src/receipts/printer.ts +69 -0
  86. package/src/receipts/store.ts +69 -0
  87. package/src/receipts/update.ts +124 -0
  88. package/src/shippings/delivery-routes/destroy.ts +54 -0
  89. package/src/shippings/delivery-routes/fetch.ts +42 -0
  90. package/src/shippings/delivery-routes/index.ts +26 -0
  91. package/src/shippings/delivery-routes/store.ts +69 -0
  92. package/src/shippings/delivery-routes/update.ts +120 -0
  93. package/src/shippings/digital-deliveries/destroy.ts +111 -0
  94. package/src/shippings/digital-deliveries/fetch.ts +20 -0
  95. package/src/shippings/digital-deliveries/index.ts +26 -0
  96. package/src/shippings/digital-deliveries/store.ts +76 -0
  97. package/src/shippings/digital-deliveries/update.ts +129 -0
  98. package/src/shippings/drivers/destroy.ts +78 -0
  99. package/src/shippings/drivers/fetch.ts +20 -0
  100. package/src/shippings/drivers/index.ts +24 -0
  101. package/src/shippings/drivers/store.ts +69 -0
  102. package/src/shippings/drivers/update.ts +117 -0
  103. package/src/shippings/index.ts +39 -0
  104. package/src/shippings/license-keys/destroy.ts +111 -0
  105. package/src/shippings/license-keys/fetch.ts +20 -0
  106. package/src/shippings/license-keys/index.ts +26 -0
  107. package/src/shippings/license-keys/store.ts +77 -0
  108. package/src/shippings/license-keys/update.ts +117 -0
  109. package/src/shippings/shipping-methods/destroy.ts +111 -0
  110. package/src/shippings/shipping-methods/fetch.ts +62 -0
  111. package/src/shippings/shipping-methods/index.ts +28 -0
  112. package/src/shippings/shipping-methods/store.ts +123 -0
  113. package/src/shippings/shipping-methods/update.ts +120 -0
  114. package/src/shippings/shipping-rates/destroy.ts +102 -0
  115. package/src/shippings/shipping-rates/fetch.ts +320 -0
  116. package/src/shippings/shipping-rates/index.ts +36 -0
  117. package/src/shippings/shipping-rates/store.ts +102 -0
  118. package/src/shippings/shipping-rates/update.ts +139 -0
  119. package/src/shippings/shipping-zones/destroy.ts +111 -0
  120. package/src/shippings/shipping-zones/fetch.ts +96 -0
  121. package/src/shippings/shipping-zones/index.ts +26 -0
  122. package/src/shippings/shipping-zones/store.ts +74 -0
  123. package/src/shippings/shipping-zones/update.ts +156 -0
  124. package/src/tax/destroy.ts +54 -0
  125. package/src/tax/fetch.ts +20 -0
  126. package/src/tax/index.ts +24 -0
  127. package/src/tax/store.ts +69 -0
  128. package/src/tax/update.ts +119 -0
  129. package/src/tests/coupons.test.ts +90 -0
  130. package/src/tests/customers.test.ts +39 -0
  131. package/src/tests/device.test.ts +111 -0
  132. package/src/tests/digitals.test.ts +31 -0
  133. package/src/tests/drivers.test.ts +25 -0
  134. package/src/tests/gift-cards.test.ts +36 -0
  135. package/src/tests/license.test.ts +31 -0
  136. package/src/tests/manufacturers.test.ts +80 -0
  137. package/src/tests/orders.test.ts +25 -0
  138. package/src/tests/payments.test.ts +75 -0
  139. package/src/tests/rates.test.ts +367 -0
  140. package/src/tests/receipts.test.ts +287 -0
  141. package/src/tests/restaurant.test.ts +522 -0
  142. package/src/tests/reviews.test.ts +165 -0
  143. package/src/tests/routes.test.ts +51 -0
  144. package/src/tests/setup.ts +149 -0
  145. package/src/tests/shipping.test.ts +88 -0
  146. package/src/tests/tax.test.ts +25 -0
  147. package/src/tests/units.test.ts +110 -0
  148. package/src/tests/variants.test.ts +133 -0
  149. package/src/tests/waitlist.test.ts +303 -0
  150. package/src/tests/zones.test.ts +86 -0
  151. package/src/types.ts +312 -0
  152. package/src/utils/typed-stats.ts +67 -0
  153. package/src/waitlists/index.ts +19 -0
  154. package/src/waitlists/products/destroy.ts +65 -0
  155. package/src/waitlists/products/export.ts +113 -0
  156. package/src/waitlists/products/fetch.ts +362 -0
  157. package/src/waitlists/products/index.ts +33 -0
  158. package/src/waitlists/products/store.ts +112 -0
  159. package/src/waitlists/products/update.ts +125 -0
  160. package/src/waitlists/restaurant/destroy.ts +65 -0
  161. package/src/waitlists/restaurant/export.ts +107 -0
  162. package/src/waitlists/restaurant/fetch.ts +538 -0
  163. package/src/waitlists/restaurant/index.ts +43 -0
  164. package/src/waitlists/restaurant/store.ts +109 -0
  165. package/src/waitlists/restaurant/update.ts +198 -0
@@ -0,0 +1,288 @@
1
+ import type { StacksExpressionBuilder } from '@stacksjs/database'
2
+ import { db } from '@stacksjs/database'
3
+ import { formatDate } from '@stacksjs/orm'
4
+ type PaymentJsonResponse = ModelRow<typeof Payment>
5
+
6
+ /**
7
+ * Payment statistics response interface
8
+ */
9
+ export interface PaymentStats {
10
+ total_transactions: number
11
+ total_revenue: number
12
+ average_transaction: number
13
+ successful_rate: number
14
+ comparison: {
15
+ transactions: {
16
+ difference: number
17
+ percentage: number
18
+ is_increase: boolean
19
+ }
20
+ revenue: {
21
+ difference: number
22
+ percentage: number
23
+ is_increase: boolean
24
+ }
25
+ average: {
26
+ difference: number
27
+ percentage: number
28
+ is_increase: boolean
29
+ }
30
+ }
31
+ }
32
+
33
+ export async function fetchAll(): Promise<PaymentJsonResponse[]> {
34
+ return await db
35
+ .selectFrom('payments')
36
+ .selectAll()
37
+ .execute() as PaymentJsonResponse[]
38
+ }
39
+
40
+ export async function fetchById(id: number): Promise<PaymentJsonResponse | undefined> {
41
+ return await db
42
+ .selectFrom('payments')
43
+ .selectAll()
44
+ .where('id', '=', id)
45
+ .executeTakeFirst() as PaymentJsonResponse | undefined
46
+ }
47
+
48
+ /**
49
+ * Fetch payment statistics for a specific time period
50
+ *
51
+ * @param daysRange Number of days to look back (7, 30, 60, etc.)
52
+ */
53
+ export async function fetchPaymentStats(daysRange: number = 30): Promise<PaymentStats> {
54
+ const today = new Date()
55
+
56
+ // Current period (last N days)
57
+ const currentPeriodStart = new Date(today)
58
+ currentPeriodStart.setDate(today.getDate() - daysRange)
59
+
60
+ // Previous period (N days before the current period)
61
+ const previousPeriodEnd = new Date(currentPeriodStart)
62
+ previousPeriodEnd.setDate(previousPeriodEnd.getDate() - 1)
63
+
64
+ const previousPeriodStart = new Date(previousPeriodEnd)
65
+ previousPeriodStart.setDate(previousPeriodEnd.getDate() - daysRange)
66
+
67
+ // Get current period stats for completed payments
68
+ const currentStats = await db
69
+ .selectFrom('payments')
70
+ .select(((eb: StacksExpressionBuilder) => [
71
+ eb.fn.count('id').as('transaction_count'),
72
+ eb.fn.sum('amount').as('total_revenue'),
73
+ ]) as any)
74
+ .where('created_at', '>=', formatDate(currentPeriodStart))
75
+ .where('created_at', '<=', formatDate(today))
76
+ .where('status', '=', 'completed')
77
+ .executeTakeFirst() as { transaction_count: number, total_revenue: number } | undefined
78
+
79
+ // Get previous period stats for completed payments
80
+ const previousStats = await db
81
+ .selectFrom('payments')
82
+ .select(((eb: StacksExpressionBuilder) => [
83
+ eb.fn.count('id').as('transaction_count'),
84
+ eb.fn.sum('amount').as('total_revenue'),
85
+ ]) as any)
86
+ .where('created_at', '>=', formatDate(previousPeriodStart))
87
+ .where('created_at', '<=', formatDate(previousPeriodEnd))
88
+ .where('status', '=', 'completed')
89
+ .executeTakeFirst() as { transaction_count: number, total_revenue: number } | undefined
90
+
91
+ // Get total transactions count (including non-completed ones) for calculating success rate
92
+ const totalTransactions = await db
93
+ .selectFrom('payments')
94
+ .select(((eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')) as any)
95
+ .where('created_at', '>=', formatDate(currentPeriodStart))
96
+ .where('created_at', '<=', formatDate(today))
97
+ .executeTakeFirst() as { count: number } | undefined
98
+
99
+ // Calculate current period stats
100
+ const currentTransactions = Number(currentStats?.transaction_count || 0)
101
+ const currentRevenue = Number(currentStats?.total_revenue || 0)
102
+ const currentAverage = currentTransactions > 0 ? currentRevenue / currentTransactions : 0
103
+
104
+ // Calculate previous period stats
105
+ const previousTransactions = Number(previousStats?.transaction_count || 0)
106
+ const previousRevenue = Number(previousStats?.total_revenue || 0)
107
+ const previousAverage = previousTransactions > 0 ? previousRevenue / previousTransactions : 0
108
+
109
+ // Calculate differences
110
+ const transactionDifference = currentTransactions - previousTransactions
111
+ const revenueDifference = currentRevenue - previousRevenue
112
+ const averageDifference = currentAverage - previousAverage
113
+
114
+ // Calculate percentage changes
115
+ const transactionPercentage = previousTransactions > 0
116
+ ? (transactionDifference / previousTransactions) * 100
117
+ : (currentTransactions > 0 ? 100 : 0)
118
+
119
+ const revenuePercentage = previousRevenue > 0
120
+ ? (revenueDifference / previousRevenue) * 100
121
+ : (currentRevenue > 0 ? 100 : 0)
122
+
123
+ const averagePercentage = previousAverage > 0
124
+ ? (averageDifference / previousAverage) * 100
125
+ : (currentAverage > 0 ? 100 : 0)
126
+
127
+ // Calculate success rate
128
+ const allTransactions = Number(totalTransactions?.count || 0)
129
+ const successRate = allTransactions > 0
130
+ ? (currentTransactions / allTransactions) * 100
131
+ : 0
132
+
133
+ return {
134
+ total_transactions: currentTransactions,
135
+ total_revenue: currentRevenue,
136
+ average_transaction: currentAverage,
137
+ successful_rate: successRate,
138
+ comparison: {
139
+ transactions: {
140
+ difference: transactionDifference,
141
+ percentage: Math.abs(transactionPercentage),
142
+ is_increase: transactionDifference >= 0,
143
+ },
144
+ revenue: {
145
+ difference: revenueDifference,
146
+ percentage: Math.abs(revenuePercentage),
147
+ is_increase: revenueDifference >= 0,
148
+ },
149
+ average: {
150
+ difference: averageDifference,
151
+ percentage: Math.abs(averagePercentage),
152
+ is_increase: averageDifference >= 0,
153
+ },
154
+ },
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Fetch payment statistics by payment method
160
+ *
161
+ * @param daysRange Number of days to look back
162
+ */
163
+ export async function fetchPaymentStatsByMethod(_daysRange: number = 30): Promise<Record<string, {
164
+ count: number
165
+ revenue: number
166
+ percentage_of_total: number
167
+ }>> {
168
+ const today = new Date()
169
+
170
+ const startDate = new Date(today)
171
+ startDate.setDate(today.getDate() - _daysRange)
172
+
173
+ // Get total stats for the period
174
+ const totalStats = await db
175
+ .selectFrom('payments')
176
+ .select(((eb: StacksExpressionBuilder) => [
177
+ eb.fn.count('id').as('total_count'),
178
+ eb.fn.sum('amount').as('total_revenue'),
179
+ ]) as any)
180
+ .where('created_at', '>=', formatDate(startDate))
181
+ .where('created_at', '<=', formatDate(today))
182
+ .where('status', '=', 'completed')
183
+ .executeTakeFirst() as { total_count: number, total_revenue: number } | undefined
184
+
185
+ const totalCount = Number(totalStats?.total_count || 0)
186
+
187
+ // Get stats grouped by payment method
188
+ const methodStats = await db
189
+ .selectFrom('payments')
190
+ .select([
191
+ 'method',
192
+ (eb: StacksExpressionBuilder) => eb.fn.count('id').as('count'),
193
+ (eb: StacksExpressionBuilder) => eb.fn.sum('amount').as('revenue'),
194
+ ] as any)
195
+ .where('created_at', '>=', formatDate(startDate))
196
+ .where('created_at', '<=', formatDate(today))
197
+ .where('status', '=', 'completed')
198
+ .groupBy('method')
199
+ .execute() as { method: string, count: number, revenue: number }[]
200
+
201
+ // Format the results
202
+ const result: Record<string, {
203
+ count: number
204
+ revenue: number
205
+ percentage_of_total: number
206
+ }> = {}
207
+
208
+ methodStats.forEach((item: any) => {
209
+ const count = Number(item.count || 0)
210
+ const revenue = Number(item.revenue || 0)
211
+ const percentageOfTotal = totalCount > 0 ? (count / totalCount) * 100 : 0
212
+
213
+ result[item.method] = {
214
+ count,
215
+ revenue,
216
+ percentage_of_total: percentageOfTotal,
217
+ }
218
+ })
219
+
220
+ return result
221
+ }
222
+
223
+ /**
224
+ * Fetch monthly payment trends for the last 12 months
225
+ */
226
+ export async function fetchMonthlyPaymentTrends(): Promise<Array<{
227
+ month: string
228
+ year: number
229
+ transactions: number
230
+ revenue: number
231
+ average: number
232
+ }>> {
233
+ // Calculate date 12 months ago
234
+ const today = new Date()
235
+
236
+ const twelveMonthsAgo = new Date(today)
237
+ twelveMonthsAgo.setMonth(today.getMonth() - 11)
238
+
239
+ // Set to first day of that month
240
+ twelveMonthsAgo.setDate(1)
241
+
242
+ // Fetch all completed payments within the date range
243
+ const payments = await db
244
+ .selectFrom('payments')
245
+ .selectAll()
246
+ .where('created_at', '>=', formatDate(twelveMonthsAgo))
247
+ .where('status', '=', 'completed')
248
+ .execute() as any[]
249
+
250
+ // Group by year-month in JavaScript
251
+ const monthlyMap: Record<string, { year: number, month: number, transactions: number, revenue: number }> = {}
252
+
253
+ for (const payment of payments) {
254
+ const createdAt = payment.created_at ? new Date(payment.created_at) : new Date()
255
+ const year = createdAt.getFullYear()
256
+ const month = createdAt.getMonth() + 1 // 1-indexed
257
+ const key = `${year}-${String(month).padStart(2, '0')}`
258
+
259
+ if (!monthlyMap[key]) {
260
+ monthlyMap[key] = { year, month, transactions: 0, revenue: 0 }
261
+ }
262
+
263
+ monthlyMap[key].transactions++
264
+ monthlyMap[key].revenue += Number(payment.amount || 0)
265
+ }
266
+
267
+ // Convert to array and sort by year-month
268
+ const sortedKeys = Object.keys(monthlyMap).sort()
269
+
270
+ return sortedKeys.map((key) => {
271
+ const item = monthlyMap[key]!
272
+ const transactions = item.transactions
273
+ const revenue = item.revenue
274
+ const average = transactions > 0 ? revenue / transactions : 0
275
+
276
+ // Format month name
277
+ const monthDate = new Date(item.year, item.month - 1, 1)
278
+ const monthName = monthDate.toLocaleString('default', { month: 'short' })
279
+
280
+ return {
281
+ month: monthName,
282
+ year: item.year,
283
+ transactions,
284
+ revenue,
285
+ average,
286
+ }
287
+ })
288
+ }
@@ -0,0 +1,16 @@
1
+ export { bulkDestroy, destroy } from './destroy'
2
+
3
+ // Fetch operations - from fetch.ts
4
+ export {
5
+ fetchAll,
6
+ fetchById,
7
+ fetchMonthlyPaymentTrends,
8
+ fetchPaymentStats,
9
+ fetchPaymentStatsByMethod,
10
+ } from './fetch'
11
+
12
+ // Store operations - from store.ts
13
+ export { store } from './store'
14
+
15
+ // Update operations - from update.ts
16
+ export { update } from './update'
@@ -0,0 +1,63 @@
1
+ type PaymentJsonResponse = ModelRow<typeof Payment>
2
+ type NewPayment = NewModelData<typeof Payment>
3
+ import { randomUUIDv7 } from 'bun'
4
+ import { db } from '@stacksjs/database'
5
+ import { HttpError } from '@stacksjs/error-handling'
6
+ import { isUniqueViolation } from '@stacksjs/orm'
7
+
8
+ /**
9
+ * Create a new payment
10
+ *
11
+ * @param data The payment data to store
12
+ * @returns The newly created payment record
13
+ */
14
+ export async function store(data: NewPayment): Promise<PaymentJsonResponse | undefined> {
15
+ const paymentData = {
16
+ ...data,
17
+ status: data.status || 'PENDING',
18
+ currency: data.currency || 'USD',
19
+ uuid: randomUUIDv7(),
20
+ }
21
+
22
+ try {
23
+ // Insert the payment record
24
+ const createdPayment = await db
25
+ .insertInto('payments')
26
+ .values(paymentData as NewPayment)
27
+ .executeTakeFirst()
28
+
29
+ const insertId = Number(createdPayment.insertId) || Number(createdPayment.numInsertedOrUpdatedRows)
30
+
31
+ // If insert was successful, retrieve the newly created payment
32
+ if (insertId) {
33
+ const payment = await db
34
+ .selectFrom('payments')
35
+ .where('id', '=', insertId)
36
+ .selectAll()
37
+ .executeTakeFirst()
38
+
39
+ return payment as PaymentJsonResponse | undefined
40
+ }
41
+
42
+ return undefined
43
+ }
44
+ catch (error) {
45
+ if (error instanceof HttpError)
46
+ throw error
47
+ // Cross-dialect duplicate detection (#1957) — checked before the
48
+ // insufficient-funds sniff (a duplicate is never an insufficient-funds
49
+ // error, and vice versa, so order is safe).
50
+ if (isUniqueViolation(error))
51
+ throw new HttpError(409, 'A payment with this transaction ID already exists')
52
+ if (error instanceof Error) {
53
+ // Check for insufficient amount errors or other payment-specific validations
54
+ if (error.message.includes('Insufficient funds')) {
55
+ throw new Error('Insufficient funds for this payment')
56
+ }
57
+
58
+ throw new Error(`Failed to create payment: ${error.message}`)
59
+ }
60
+
61
+ throw error
62
+ }
63
+ }
@@ -0,0 +1,52 @@
1
+ import { db } from '@stacksjs/database'
2
+ import { formatDate } from '@stacksjs/orm'
3
+ import { fetchById } from './fetch'
4
+
5
+ type PaymentJsonResponse = ModelRow<typeof Payment>
6
+ type PaymentUpdate = UpdateModelData<typeof Payment>
7
+
8
+ /**
9
+ * Update a payment by ID.
10
+ *
11
+ * Supports the lifecycle a payment row goes through after creation —
12
+ * status transitions (pending → completed/failed/refunded), refund
13
+ * amount tracking, processor metadata (transaction ID, card brand),
14
+ * and operator notes. The original `amount` is intentionally fillable
15
+ * via this path because partial captures and adjustments can rewrite
16
+ * it before settlement; if your processor never adjusts captured
17
+ * amounts, just don't pass `amount` in `data`.
18
+ *
19
+ * @param id The ID of the payment to update
20
+ * @param data The updated payment data (omit `id`)
21
+ * @returns The updated payment record
22
+ */
23
+ export async function update(id: number, data: Omit<PaymentUpdate, 'id'>): Promise<PaymentJsonResponse | undefined> {
24
+ const existing = await fetchById(id)
25
+ if (!existing) {
26
+ throw new Error(`Payment with ID ${id} not found`)
27
+ }
28
+
29
+ try {
30
+ await db
31
+ .updateTable('payments')
32
+ .set({
33
+ ...data,
34
+ updated_at: formatDate(new Date()),
35
+ })
36
+ .where('id', '=', id)
37
+ .execute()
38
+
39
+ return await fetchById(id)
40
+ }
41
+ catch (error) {
42
+ if (error instanceof Error) {
43
+ // Same uniqueness collision the store path can hit — surface it
44
+ // with the same wording so callers can pattern-match.
45
+ if (error.message.includes('Duplicate entry') && error.message.includes('transaction_id')) {
46
+ throw new Error('A payment with this transaction ID already exists')
47
+ }
48
+ throw new Error(`Failed to update payment: ${error.message}`)
49
+ }
50
+ throw error
51
+ }
52
+ }
package/src/product.ts ADDED
File without changes
@@ -0,0 +1,103 @@
1
+ import { db } from '@stacksjs/database'
2
+ import { fetchById } from './fetch'
3
+
4
+ /**
5
+ * Delete a category by ID
6
+ * @param id The ID of the category to delete
7
+ * @returns A boolean indicating whether the deletion was successful
8
+ */
9
+ export async function remove(id: number): Promise<boolean> {
10
+ // First check if the category exists
11
+ const category = await fetchById(id)
12
+
13
+ if (!category) {
14
+ throw new Error(`Category with ID ${id} not found`)
15
+ }
16
+
17
+ // Delete the category
18
+ const result = await db
19
+ .deleteFrom('categories')
20
+ .where('id', '=', id)
21
+ .executeTakeFirst()
22
+
23
+ return !!result
24
+ }
25
+
26
+ /**
27
+ * Bulk delete multiple categories
28
+ * @param ids Array of category IDs to delete
29
+ * @returns Number of categories successfully deleted
30
+ */
31
+ export async function bulkRemove(ids: number[]): Promise<number> {
32
+ if (!ids.length) {
33
+ return 0
34
+ }
35
+
36
+ // Delete all categories in the array
37
+ const result = await db
38
+ .deleteFrom('categories')
39
+ .where('id', 'in', ids)
40
+ .executeTakeFirst()
41
+
42
+ return Number(result?.numDeletedRows) || 0
43
+ }
44
+
45
+ /**
46
+ * Delete child categories of a parent category
47
+ * @param parentId The ID of the parent category
48
+ * @returns Number of categories deleted
49
+ */
50
+ export async function removeChildCategories(parentId: string): Promise<number> {
51
+ // Delete categories with the specified parent_category_id
52
+ const result = await db
53
+ .deleteFrom('categories')
54
+ .where('parent_category_id', '=', parentId)
55
+ .executeTakeFirst()
56
+
57
+ return Number(result?.numDeletedRows) || 0
58
+ }
59
+
60
+ /**
61
+ * Deactivate a category (set is_active to false)
62
+ * @param id The ID of the category to deactivate
63
+ * @returns A boolean indicating whether the deactivation was successful
64
+ */
65
+ export async function deactivate(id: number): Promise<boolean> {
66
+ // First check if the category exists
67
+ const category = await fetchById(id)
68
+
69
+ if (!category) {
70
+ throw new Error(`Category with ID ${id} not found`)
71
+ }
72
+
73
+ // Update the category status
74
+ const result = await db
75
+ .updateTable('categories')
76
+ .set({
77
+ is_active: false,
78
+ updated_at: new Date().toISOString(),
79
+ })
80
+ .where('id', '=', id)
81
+ .executeTakeFirst()
82
+
83
+ return !!result
84
+ }
85
+
86
+ /**
87
+ * Deactivate all child categories of a parent category
88
+ * @param parentId The ID of the parent category
89
+ * @returns Number of categories deactivated
90
+ */
91
+ export async function deactivateChildCategories(parentId: string): Promise<number> {
92
+ // Update categories with the specified parent_category_id
93
+ const result = await db
94
+ .updateTable('categories')
95
+ .set({
96
+ is_active: false,
97
+ updated_at: new Date().toISOString(),
98
+ })
99
+ .where('parent_category_id', '=', parentId)
100
+ .execute()
101
+
102
+ return Number((result as any).length) || 0
103
+ }