@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.
- package/package.json +3 -2
- package/src/carts/destroy.ts +54 -0
- package/src/carts/fetch.ts +20 -0
- package/src/carts/index.ts +13 -0
- package/src/carts/store.ts +75 -0
- package/src/carts/update.ts +78 -0
- package/src/coupons/destroy.ts +49 -0
- package/src/coupons/fetch.ts +510 -0
- package/src/coupons/index.ts +30 -0
- package/src/coupons/store.ts +46 -0
- package/src/coupons/update.ts +147 -0
- package/src/customers/destroy.ts +62 -0
- package/src/customers/fetch.ts +20 -0
- package/src/customers/index.ts +17 -0
- package/src/customers/store.ts +45 -0
- package/src/customers/update.ts +44 -0
- package/src/devices/destroy.ts +54 -0
- package/src/devices/export.ts +98 -0
- package/src/devices/fetch.ts +125 -0
- package/src/devices/index.ts +31 -0
- package/src/devices/store.ts +82 -0
- package/src/devices/update.ts +125 -0
- package/src/errors/destroy.ts +54 -0
- package/src/errors/fetch.ts +197 -0
- package/src/errors/index.ts +28 -0
- package/src/errors/update.ts +116 -0
- package/src/gift-cards/destroy.ts +87 -0
- package/src/gift-cards/fetch.ts +431 -0
- package/src/gift-cards/index.ts +26 -0
- package/src/gift-cards/store.ts +56 -0
- package/src/gift-cards/update.ts +102 -0
- package/src/index.ts +78 -0
- package/src/orders/destroy.ts +111 -0
- package/src/orders/events.ts +121 -0
- package/src/orders/export.ts +158 -0
- package/src/orders/fetch.ts +379 -0
- package/src/orders/guards.ts +261 -0
- package/src/orders/index.ts +91 -0
- package/src/orders/place-order.ts +274 -0
- package/src/orders/store.ts +51 -0
- package/src/orders/totals.ts +190 -0
- package/src/orders/update.ts +164 -0
- package/src/orders/webhook.ts +312 -0
- package/src/payments/destroy.ts +56 -0
- package/src/payments/fetch.ts +288 -0
- package/src/payments/index.ts +16 -0
- package/src/payments/store.ts +63 -0
- package/src/payments/update.ts +52 -0
- package/src/product.ts +0 -0
- package/src/products/categories/destroy.ts +103 -0
- package/src/products/categories/fetch.ts +285 -0
- package/src/products/categories/index.ts +36 -0
- package/src/products/categories/store.ts +87 -0
- package/src/products/categories/update.ts +251 -0
- package/src/products/index.ts +35 -0
- package/src/products/items/destroy.ts +54 -0
- package/src/products/items/fetch.ts +282 -0
- package/src/products/items/index.ts +26 -0
- package/src/products/items/store.ts +84 -0
- package/src/products/items/update.ts +324 -0
- package/src/products/manufacturers/destroy.ts +56 -0
- package/src/products/manufacturers/fetch.ts +117 -0
- package/src/products/manufacturers/index.ts +24 -0
- package/src/products/manufacturers/store.ts +82 -0
- package/src/products/manufacturers/update.ts +113 -0
- package/src/products/reviews/destroy.ts +56 -0
- package/src/products/reviews/fetch.ts +63 -0
- package/src/products/reviews/index.ts +25 -0
- package/src/products/reviews/store.ts +46 -0
- package/src/products/reviews/update.ts +82 -0
- package/src/products/units/destroy.ts +56 -0
- package/src/products/units/fetch.ts +20 -0
- package/src/products/units/index.ts +13 -0
- package/src/products/units/store.ts +162 -0
- package/src/products/units/update.ts +159 -0
- package/src/products/variants/destroy.ts +56 -0
- package/src/products/variants/fetch.ts +20 -0
- package/src/products/variants/index.ts +21 -0
- package/src/products/variants/store.ts +136 -0
- package/src/products/variants/update.ts +104 -0
- package/src/receipts/destroy.ts +54 -0
- package/src/receipts/export.ts +0 -0
- package/src/receipts/fetch.ts +222 -0
- package/src/receipts/index.ts +24 -0
- package/src/receipts/printer.ts +69 -0
- package/src/receipts/store.ts +69 -0
- package/src/receipts/update.ts +124 -0
- package/src/shippings/delivery-routes/destroy.ts +54 -0
- package/src/shippings/delivery-routes/fetch.ts +42 -0
- package/src/shippings/delivery-routes/index.ts +26 -0
- package/src/shippings/delivery-routes/store.ts +69 -0
- package/src/shippings/delivery-routes/update.ts +120 -0
- package/src/shippings/digital-deliveries/destroy.ts +111 -0
- package/src/shippings/digital-deliveries/fetch.ts +20 -0
- package/src/shippings/digital-deliveries/index.ts +26 -0
- package/src/shippings/digital-deliveries/store.ts +76 -0
- package/src/shippings/digital-deliveries/update.ts +129 -0
- package/src/shippings/drivers/destroy.ts +78 -0
- package/src/shippings/drivers/fetch.ts +20 -0
- package/src/shippings/drivers/index.ts +24 -0
- package/src/shippings/drivers/store.ts +69 -0
- package/src/shippings/drivers/update.ts +117 -0
- package/src/shippings/index.ts +39 -0
- package/src/shippings/license-keys/destroy.ts +111 -0
- package/src/shippings/license-keys/fetch.ts +20 -0
- package/src/shippings/license-keys/index.ts +26 -0
- package/src/shippings/license-keys/store.ts +77 -0
- package/src/shippings/license-keys/update.ts +117 -0
- package/src/shippings/shipping-methods/destroy.ts +111 -0
- package/src/shippings/shipping-methods/fetch.ts +62 -0
- package/src/shippings/shipping-methods/index.ts +28 -0
- package/src/shippings/shipping-methods/store.ts +123 -0
- package/src/shippings/shipping-methods/update.ts +120 -0
- package/src/shippings/shipping-rates/destroy.ts +102 -0
- package/src/shippings/shipping-rates/fetch.ts +320 -0
- package/src/shippings/shipping-rates/index.ts +36 -0
- package/src/shippings/shipping-rates/store.ts +102 -0
- package/src/shippings/shipping-rates/update.ts +139 -0
- package/src/shippings/shipping-zones/destroy.ts +111 -0
- package/src/shippings/shipping-zones/fetch.ts +96 -0
- package/src/shippings/shipping-zones/index.ts +26 -0
- package/src/shippings/shipping-zones/store.ts +74 -0
- package/src/shippings/shipping-zones/update.ts +156 -0
- package/src/tax/destroy.ts +54 -0
- package/src/tax/fetch.ts +20 -0
- package/src/tax/index.ts +24 -0
- package/src/tax/store.ts +69 -0
- package/src/tax/update.ts +119 -0
- package/src/tests/coupons.test.ts +90 -0
- package/src/tests/customers.test.ts +39 -0
- package/src/tests/device.test.ts +111 -0
- package/src/tests/digitals.test.ts +31 -0
- package/src/tests/drivers.test.ts +25 -0
- package/src/tests/gift-cards.test.ts +36 -0
- package/src/tests/license.test.ts +31 -0
- package/src/tests/manufacturers.test.ts +80 -0
- package/src/tests/orders.test.ts +25 -0
- package/src/tests/payments.test.ts +75 -0
- package/src/tests/rates.test.ts +367 -0
- package/src/tests/receipts.test.ts +287 -0
- package/src/tests/restaurant.test.ts +522 -0
- package/src/tests/reviews.test.ts +165 -0
- package/src/tests/routes.test.ts +51 -0
- package/src/tests/setup.ts +149 -0
- package/src/tests/shipping.test.ts +88 -0
- package/src/tests/tax.test.ts +25 -0
- package/src/tests/units.test.ts +110 -0
- package/src/tests/variants.test.ts +133 -0
- package/src/tests/waitlist.test.ts +303 -0
- package/src/tests/zones.test.ts +86 -0
- package/src/types.ts +312 -0
- package/src/utils/typed-stats.ts +67 -0
- package/src/waitlists/index.ts +19 -0
- package/src/waitlists/products/destroy.ts +65 -0
- package/src/waitlists/products/export.ts +113 -0
- package/src/waitlists/products/fetch.ts +362 -0
- package/src/waitlists/products/index.ts +33 -0
- package/src/waitlists/products/store.ts +112 -0
- package/src/waitlists/products/update.ts +125 -0
- package/src/waitlists/restaurant/destroy.ts +65 -0
- package/src/waitlists/restaurant/export.ts +107 -0
- package/src/waitlists/restaurant/fetch.ts +538 -0
- package/src/waitlists/restaurant/index.ts +43 -0
- package/src/waitlists/restaurant/store.ts +109 -0
- 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
|
+
}
|