@stacksjs/commerce 0.70.55 → 0.70.57
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 +10 -7
- package/src/carts/destroy.ts +0 -54
- package/src/carts/fetch.ts +0 -20
- package/src/carts/index.ts +0 -13
- package/src/carts/store.ts +0 -75
- package/src/carts/update.ts +0 -78
- package/src/coupons/destroy.ts +0 -49
- package/src/coupons/fetch.ts +0 -510
- package/src/coupons/index.ts +0 -30
- package/src/coupons/store.ts +0 -46
- package/src/coupons/update.ts +0 -147
- package/src/customers/destroy.ts +0 -62
- package/src/customers/fetch.ts +0 -20
- package/src/customers/index.ts +0 -17
- package/src/customers/store.ts +0 -45
- package/src/customers/update.ts +0 -44
- package/src/devices/destroy.ts +0 -54
- package/src/devices/export.ts +0 -98
- package/src/devices/fetch.ts +0 -125
- package/src/devices/index.ts +0 -31
- package/src/devices/store.ts +0 -82
- package/src/devices/update.ts +0 -125
- package/src/errors/destroy.ts +0 -54
- package/src/errors/fetch.ts +0 -197
- package/src/errors/index.ts +0 -28
- package/src/errors/update.ts +0 -116
- package/src/gift-cards/destroy.ts +0 -87
- package/src/gift-cards/fetch.ts +0 -431
- package/src/gift-cards/index.ts +0 -26
- package/src/gift-cards/store.ts +0 -56
- package/src/gift-cards/update.ts +0 -102
- package/src/index.ts +0 -78
- package/src/orders/destroy.ts +0 -111
- package/src/orders/events.ts +0 -121
- package/src/orders/export.ts +0 -158
- package/src/orders/fetch.ts +0 -379
- package/src/orders/guards.ts +0 -261
- package/src/orders/index.ts +0 -91
- package/src/orders/place-order.ts +0 -274
- package/src/orders/store.ts +0 -51
- package/src/orders/totals.ts +0 -190
- package/src/orders/update.ts +0 -164
- package/src/orders/webhook.ts +0 -312
- package/src/payments/destroy.ts +0 -56
- package/src/payments/fetch.ts +0 -288
- package/src/payments/index.ts +0 -16
- package/src/payments/store.ts +0 -63
- package/src/payments/update.ts +0 -52
- package/src/product.ts +0 -0
- package/src/products/categories/destroy.ts +0 -103
- package/src/products/categories/fetch.ts +0 -285
- package/src/products/categories/index.ts +0 -36
- package/src/products/categories/store.ts +0 -87
- package/src/products/categories/update.ts +0 -251
- package/src/products/index.ts +0 -35
- package/src/products/items/destroy.ts +0 -54
- package/src/products/items/fetch.ts +0 -282
- package/src/products/items/index.ts +0 -26
- package/src/products/items/store.ts +0 -84
- package/src/products/items/update.ts +0 -324
- package/src/products/manufacturers/destroy.ts +0 -56
- package/src/products/manufacturers/fetch.ts +0 -117
- package/src/products/manufacturers/index.ts +0 -24
- package/src/products/manufacturers/store.ts +0 -82
- package/src/products/manufacturers/update.ts +0 -113
- package/src/products/reviews/destroy.ts +0 -56
- package/src/products/reviews/fetch.ts +0 -63
- package/src/products/reviews/index.ts +0 -25
- package/src/products/reviews/store.ts +0 -46
- package/src/products/reviews/update.ts +0 -82
- package/src/products/units/destroy.ts +0 -56
- package/src/products/units/fetch.ts +0 -20
- package/src/products/units/index.ts +0 -13
- package/src/products/units/store.ts +0 -162
- package/src/products/units/update.ts +0 -159
- package/src/products/variants/destroy.ts +0 -56
- package/src/products/variants/fetch.ts +0 -20
- package/src/products/variants/index.ts +0 -21
- package/src/products/variants/store.ts +0 -136
- package/src/products/variants/update.ts +0 -104
- package/src/receipts/destroy.ts +0 -54
- package/src/receipts/export.ts +0 -0
- package/src/receipts/fetch.ts +0 -222
- package/src/receipts/index.ts +0 -24
- package/src/receipts/printer.ts +0 -69
- package/src/receipts/store.ts +0 -69
- package/src/receipts/update.ts +0 -124
- package/src/shippings/delivery-routes/destroy.ts +0 -54
- package/src/shippings/delivery-routes/fetch.ts +0 -42
- package/src/shippings/delivery-routes/index.ts +0 -26
- package/src/shippings/delivery-routes/store.ts +0 -69
- package/src/shippings/delivery-routes/update.ts +0 -120
- package/src/shippings/digital-deliveries/destroy.ts +0 -111
- package/src/shippings/digital-deliveries/fetch.ts +0 -20
- package/src/shippings/digital-deliveries/index.ts +0 -26
- package/src/shippings/digital-deliveries/store.ts +0 -76
- package/src/shippings/digital-deliveries/update.ts +0 -129
- package/src/shippings/drivers/destroy.ts +0 -78
- package/src/shippings/drivers/fetch.ts +0 -20
- package/src/shippings/drivers/index.ts +0 -24
- package/src/shippings/drivers/store.ts +0 -69
- package/src/shippings/drivers/update.ts +0 -117
- package/src/shippings/index.ts +0 -39
- package/src/shippings/license-keys/destroy.ts +0 -111
- package/src/shippings/license-keys/fetch.ts +0 -20
- package/src/shippings/license-keys/index.ts +0 -26
- package/src/shippings/license-keys/store.ts +0 -77
- package/src/shippings/license-keys/update.ts +0 -117
- package/src/shippings/shipping-methods/destroy.ts +0 -111
- package/src/shippings/shipping-methods/fetch.ts +0 -62
- package/src/shippings/shipping-methods/index.ts +0 -28
- package/src/shippings/shipping-methods/store.ts +0 -123
- package/src/shippings/shipping-methods/update.ts +0 -120
- package/src/shippings/shipping-rates/destroy.ts +0 -102
- package/src/shippings/shipping-rates/fetch.ts +0 -320
- package/src/shippings/shipping-rates/index.ts +0 -36
- package/src/shippings/shipping-rates/store.ts +0 -102
- package/src/shippings/shipping-rates/update.ts +0 -139
- package/src/shippings/shipping-zones/destroy.ts +0 -111
- package/src/shippings/shipping-zones/fetch.ts +0 -96
- package/src/shippings/shipping-zones/index.ts +0 -26
- package/src/shippings/shipping-zones/store.ts +0 -74
- package/src/shippings/shipping-zones/update.ts +0 -156
- package/src/tax/destroy.ts +0 -54
- package/src/tax/fetch.ts +0 -20
- package/src/tax/index.ts +0 -24
- package/src/tax/store.ts +0 -69
- package/src/tax/update.ts +0 -119
- package/src/tests/coupons.test.ts +0 -90
- package/src/tests/customers.test.ts +0 -39
- package/src/tests/device.test.ts +0 -111
- package/src/tests/digitals.test.ts +0 -31
- package/src/tests/drivers.test.ts +0 -25
- package/src/tests/gift-cards.test.ts +0 -36
- package/src/tests/license.test.ts +0 -31
- package/src/tests/manufacturers.test.ts +0 -80
- package/src/tests/orders.test.ts +0 -25
- package/src/tests/payments.test.ts +0 -75
- package/src/tests/rates.test.ts +0 -367
- package/src/tests/receipts.test.ts +0 -287
- package/src/tests/restaurant.test.ts +0 -522
- package/src/tests/reviews.test.ts +0 -165
- package/src/tests/routes.test.ts +0 -51
- package/src/tests/setup.ts +0 -149
- package/src/tests/shipping.test.ts +0 -88
- package/src/tests/tax.test.ts +0 -25
- package/src/tests/units.test.ts +0 -110
- package/src/tests/variants.test.ts +0 -133
- package/src/tests/waitlist.test.ts +0 -303
- package/src/tests/zones.test.ts +0 -86
- package/src/types.ts +0 -312
- package/src/utils/typed-stats.ts +0 -67
- package/src/waitlists/index.ts +0 -19
- package/src/waitlists/products/destroy.ts +0 -65
- package/src/waitlists/products/export.ts +0 -113
- package/src/waitlists/products/fetch.ts +0 -362
- package/src/waitlists/products/index.ts +0 -33
- package/src/waitlists/products/store.ts +0 -112
- package/src/waitlists/products/update.ts +0 -125
- package/src/waitlists/restaurant/destroy.ts +0 -65
- package/src/waitlists/restaurant/export.ts +0 -107
- package/src/waitlists/restaurant/fetch.ts +0 -538
- package/src/waitlists/restaurant/index.ts +0 -43
- package/src/waitlists/restaurant/store.ts +0 -109
- package/src/waitlists/restaurant/update.ts +0 -198
- /package/dist/{src/coupons → coupons}/destroy.d.ts +0 -0
- /package/dist/{src/coupons → coupons}/fetch.d.ts +0 -0
- /package/dist/{src/coupons → coupons}/index.d.ts +0 -0
- /package/dist/{src/coupons → coupons}/store.d.ts +0 -0
- /package/dist/{src/coupons → coupons}/update.d.ts +0 -0
- /package/dist/{src/customers → customers}/destroy.d.ts +0 -0
- /package/dist/{src/customers → customers}/fetch.d.ts +0 -0
- /package/dist/{src/customers → customers}/index.d.ts +0 -0
- /package/dist/{src/customers → customers}/store.d.ts +0 -0
- /package/dist/{src/customers → customers}/update.d.ts +0 -0
- /package/dist/{src/devices → devices}/destroy.d.ts +0 -0
- /package/dist/{src/devices → devices}/export.d.ts +0 -0
- /package/dist/{src/devices → devices}/fetch.d.ts +0 -0
- /package/dist/{src/devices → devices}/index.d.ts +0 -0
- /package/dist/{src/devices → devices}/store.d.ts +0 -0
- /package/dist/{src/devices → devices}/update.d.ts +0 -0
- /package/dist/{src/errors → errors}/destroy.d.ts +0 -0
- /package/dist/{src/errors → errors}/fetch.d.ts +0 -0
- /package/dist/{src/errors → errors}/index.d.ts +0 -0
- /package/dist/{src/errors → errors}/update.d.ts +0 -0
- /package/dist/{src/gift-cards → gift-cards}/destroy.d.ts +0 -0
- /package/dist/{src/gift-cards → gift-cards}/fetch.d.ts +0 -0
- /package/dist/{src/gift-cards → gift-cards}/index.d.ts +0 -0
- /package/dist/{src/gift-cards → gift-cards}/store.d.ts +0 -0
- /package/dist/{src/gift-cards → gift-cards}/update.d.ts +0 -0
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/orders → orders}/destroy.d.ts +0 -0
- /package/dist/{src/orders → orders}/events.d.ts +0 -0
- /package/dist/{src/orders → orders}/export.d.ts +0 -0
- /package/dist/{src/orders → orders}/fetch.d.ts +0 -0
- /package/dist/{src/orders → orders}/guards.d.ts +0 -0
- /package/dist/{src/orders → orders}/index.d.ts +0 -0
- /package/dist/{src/orders → orders}/place-order.d.ts +0 -0
- /package/dist/{src/orders → orders}/store.d.ts +0 -0
- /package/dist/{src/orders → orders}/totals.d.ts +0 -0
- /package/dist/{src/orders → orders}/update.d.ts +0 -0
- /package/dist/{src/orders → orders}/webhook.d.ts +0 -0
- /package/dist/{src/payments → payments}/destroy.d.ts +0 -0
- /package/dist/{src/payments → payments}/fetch.d.ts +0 -0
- /package/dist/{src/payments → payments}/index.d.ts +0 -0
- /package/dist/{src/payments → payments}/store.d.ts +0 -0
- /package/dist/{src/payments → payments}/update.d.ts +0 -0
- /package/dist/{src/products → products}/categories/destroy.d.ts +0 -0
- /package/dist/{src/products → products}/categories/fetch.d.ts +0 -0
- /package/dist/{src/products → products}/categories/index.d.ts +0 -0
- /package/dist/{src/products → products}/categories/store.d.ts +0 -0
- /package/dist/{src/products → products}/categories/update.d.ts +0 -0
- /package/dist/{src/products → products}/index.d.ts +0 -0
- /package/dist/{src/products → products}/items/destroy.d.ts +0 -0
- /package/dist/{src/products → products}/items/fetch.d.ts +0 -0
- /package/dist/{src/products → products}/items/index.d.ts +0 -0
- /package/dist/{src/products → products}/items/store.d.ts +0 -0
- /package/dist/{src/products → products}/items/update.d.ts +0 -0
- /package/dist/{src/products → products}/manufacturers/destroy.d.ts +0 -0
- /package/dist/{src/products → products}/manufacturers/fetch.d.ts +0 -0
- /package/dist/{src/products → products}/manufacturers/index.d.ts +0 -0
- /package/dist/{src/products → products}/manufacturers/store.d.ts +0 -0
- /package/dist/{src/products → products}/manufacturers/update.d.ts +0 -0
- /package/dist/{src/products → products}/reviews/destroy.d.ts +0 -0
- /package/dist/{src/products → products}/reviews/fetch.d.ts +0 -0
- /package/dist/{src/products → products}/reviews/index.d.ts +0 -0
- /package/dist/{src/products → products}/reviews/store.d.ts +0 -0
- /package/dist/{src/products → products}/reviews/update.d.ts +0 -0
- /package/dist/{src/products → products}/units/destroy.d.ts +0 -0
- /package/dist/{src/products → products}/units/fetch.d.ts +0 -0
- /package/dist/{src/products → products}/units/index.d.ts +0 -0
- /package/dist/{src/products → products}/units/store.d.ts +0 -0
- /package/dist/{src/products → products}/units/update.d.ts +0 -0
- /package/dist/{src/products → products}/variants/destroy.d.ts +0 -0
- /package/dist/{src/products → products}/variants/fetch.d.ts +0 -0
- /package/dist/{src/products → products}/variants/index.d.ts +0 -0
- /package/dist/{src/products → products}/variants/store.d.ts +0 -0
- /package/dist/{src/products → products}/variants/update.d.ts +0 -0
- /package/dist/{src/receipts → receipts}/destroy.d.ts +0 -0
- /package/dist/{src/receipts → receipts}/fetch.d.ts +0 -0
- /package/dist/{src/receipts → receipts}/index.d.ts +0 -0
- /package/dist/{src/receipts → receipts}/store.d.ts +0 -0
- /package/dist/{src/receipts → receipts}/update.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/delivery-routes/destroy.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/delivery-routes/fetch.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/delivery-routes/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/delivery-routes/store.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/delivery-routes/update.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/digital-deliveries/destroy.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/digital-deliveries/fetch.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/digital-deliveries/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/digital-deliveries/store.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/digital-deliveries/update.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/drivers/destroy.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/drivers/fetch.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/drivers/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/drivers/store.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/drivers/update.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/license-keys/destroy.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/license-keys/fetch.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/license-keys/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/license-keys/store.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/license-keys/update.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-methods/destroy.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-methods/fetch.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-methods/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-methods/store.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-methods/update.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-rates/destroy.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-rates/fetch.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-rates/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-rates/store.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-rates/update.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-zones/destroy.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-zones/fetch.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-zones/index.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-zones/store.d.ts +0 -0
- /package/dist/{src/shippings → shippings}/shipping-zones/update.d.ts +0 -0
- /package/dist/{src/tax → tax}/destroy.d.ts +0 -0
- /package/dist/{src/tax → tax}/fetch.d.ts +0 -0
- /package/dist/{src/tax → tax}/index.d.ts +0 -0
- /package/dist/{src/tax → tax}/store.d.ts +0 -0
- /package/dist/{src/tax → tax}/update.d.ts +0 -0
- /package/dist/{src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{src/utils → utils}/typed-stats.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/index.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/products/destroy.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/products/fetch.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/products/index.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/products/store.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/products/update.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/restaurant/destroy.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/restaurant/fetch.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/restaurant/index.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/restaurant/store.d.ts +0 -0
- /package/dist/{src/waitlists → waitlists}/restaurant/update.d.ts +0 -0
package/src/orders/update.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { db } from '@stacksjs/database'
|
|
2
|
-
// Import dependencies
|
|
3
|
-
import { formatDate } from '@stacksjs/orm'
|
|
4
|
-
type OrderJsonResponse = ModelRow<typeof Order>
|
|
5
|
-
type OrderUpdate = UpdateModelData<typeof Order>
|
|
6
|
-
import { fetchById } from './fetch'
|
|
7
|
-
import type { OrderStatus } from './events'
|
|
8
|
-
import { canTransition, emitForStatus } from './events'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Update an order by ID
|
|
12
|
-
*
|
|
13
|
-
* @param id The ID of the order to update
|
|
14
|
-
* @param data The updated order data
|
|
15
|
-
* @returns The updated order record
|
|
16
|
-
*/
|
|
17
|
-
export async function update(id: number, data: Omit<OrderUpdate, 'id'>): Promise<OrderJsonResponse | undefined> {
|
|
18
|
-
// Check if order exists
|
|
19
|
-
const existingOrder = await fetchById(id)
|
|
20
|
-
if (!existingOrder) {
|
|
21
|
-
throw new Error(`Order with ID ${id} not found`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
// Update the order
|
|
26
|
-
await db
|
|
27
|
-
.updateTable('orders')
|
|
28
|
-
.set({
|
|
29
|
-
...data,
|
|
30
|
-
updated_at: formatDate(new Date()),
|
|
31
|
-
})
|
|
32
|
-
.where('id', '=', id)
|
|
33
|
-
.execute()
|
|
34
|
-
|
|
35
|
-
// Fetch and return the updated order
|
|
36
|
-
return await fetchById(id)
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
if (error instanceof Error) {
|
|
40
|
-
throw new TypeError(`Failed to update order: ${error.message}`)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
throw error
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Update an order's status
|
|
49
|
-
*
|
|
50
|
-
* @param id The ID of the order
|
|
51
|
-
* @param status The new order status
|
|
52
|
-
* @returns The updated order with the new status
|
|
53
|
-
*/
|
|
54
|
-
export async function updateStatus(
|
|
55
|
-
id: number,
|
|
56
|
-
status: OrderStatus,
|
|
57
|
-
): Promise<OrderJsonResponse | undefined> {
|
|
58
|
-
// Check if order exists
|
|
59
|
-
const order = await fetchById(id)
|
|
60
|
-
|
|
61
|
-
if (!order) {
|
|
62
|
-
throw new Error(`Order with ID ${id} not found`)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Guard against illegal status transitions (stacksjs/stacks#1879 Co-4).
|
|
66
|
-
// Pre-fix this accepted any of the 6 enum values without consulting
|
|
67
|
-
// the current status — admin tooling or buggy webhook handlers could
|
|
68
|
-
// set SHIPPED → PENDING and downstream refund logic that assumed
|
|
69
|
-
// status preconditions would silently misbehave.
|
|
70
|
-
const currentStatus = (order as { status?: OrderStatus }).status ?? 'PENDING'
|
|
71
|
-
if (!canTransition(currentStatus, status)) {
|
|
72
|
-
throw new Error(`[commerce/orders] illegal status transition ${currentStatus} → ${status}`)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
// Update the order status
|
|
77
|
-
await db
|
|
78
|
-
.updateTable('orders')
|
|
79
|
-
.set({
|
|
80
|
-
status,
|
|
81
|
-
updated_at: formatDate(new Date()),
|
|
82
|
-
})
|
|
83
|
-
.where('id', '=', id)
|
|
84
|
-
.execute()
|
|
85
|
-
|
|
86
|
-
// Fetch the updated order
|
|
87
|
-
const updated = await fetchById(id)
|
|
88
|
-
|
|
89
|
-
// Emit the status-specific event (stacksjs/stacks#1879 Co-18).
|
|
90
|
-
// Fire-and-forget — emission failures don't undo the write. The
|
|
91
|
-
// event payload carries the updated order so subscribers don't
|
|
92
|
-
// need to re-fetch.
|
|
93
|
-
if (updated) {
|
|
94
|
-
void emitForStatus(status, updated as unknown as Record<string, unknown>)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return updated
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
if (error instanceof Error) {
|
|
101
|
-
throw new TypeError(`Failed to update order status: ${error.message}`)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
throw error
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Update delivery information for an order
|
|
110
|
-
*
|
|
111
|
-
* @param id The ID of the order
|
|
112
|
-
* @param delivery_address The updated delivery address
|
|
113
|
-
* @param estimated_delivery_time The updated estimated delivery time
|
|
114
|
-
* @returns The updated order
|
|
115
|
-
*/
|
|
116
|
-
export async function updateDeliveryInfo(
|
|
117
|
-
id: number,
|
|
118
|
-
delivery_address?: string,
|
|
119
|
-
estimated_delivery_time?: string,
|
|
120
|
-
): Promise<OrderJsonResponse | undefined> {
|
|
121
|
-
// Check if order exists
|
|
122
|
-
const order = await fetchById(id)
|
|
123
|
-
|
|
124
|
-
if (!order) {
|
|
125
|
-
throw new Error(`Order with ID ${id} not found`)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Create update data with only provided fields
|
|
129
|
-
const updateData: Record<string, any> = {
|
|
130
|
-
updated_at: formatDate(new Date()),
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (delivery_address !== undefined) {
|
|
134
|
-
updateData.delivery_address = delivery_address
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (estimated_delivery_time !== undefined) {
|
|
138
|
-
updateData.estimated_delivery_time = estimated_delivery_time
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// If no delivery fields to update, just return the existing order
|
|
142
|
-
if (Object.keys(updateData).length === 1) { // Only updated_at was set
|
|
143
|
-
return order
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
// Update the order
|
|
148
|
-
await db
|
|
149
|
-
.updateTable('orders')
|
|
150
|
-
.set(updateData)
|
|
151
|
-
.where('id', '=', id)
|
|
152
|
-
.execute()
|
|
153
|
-
|
|
154
|
-
// Fetch the updated order
|
|
155
|
-
return await fetchById(id)
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
if (error instanceof Error) {
|
|
159
|
-
throw new TypeError(`Failed to update delivery information: ${error.message}`)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
throw error
|
|
163
|
-
}
|
|
164
|
-
}
|
package/src/orders/webhook.ts
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Commerce-side Stripe webhook handler (stacksjs/stacks#1879 Co-17).
|
|
3
|
-
*
|
|
4
|
-
* Background: `@stacksjs/payments` ships the signature-verification +
|
|
5
|
-
* event-routing primitives (`processWebhook`, `onPaymentIntent`,
|
|
6
|
-
* `onCharge` etc.) but nothing in commerce subscribes to them.
|
|
7
|
-
* Orders sat at PENDING forever when Stripe's webhook delivery
|
|
8
|
-
* hiccupped — customer re-checked out, double-charge risk.
|
|
9
|
-
*
|
|
10
|
-
* This module registers handlers for the four payment-related
|
|
11
|
-
* Stripe events commerce cares about:
|
|
12
|
-
*
|
|
13
|
-
* - `payment_intent.succeeded` → mark payment + order paid
|
|
14
|
-
* - `payment_intent.payment_failed` → mark order cancelled + record reason
|
|
15
|
-
* - `charge.refunded` → mark order refunded + emit event
|
|
16
|
-
* - `charge.dispute.created` → log + emit dispute event for ops
|
|
17
|
-
*
|
|
18
|
-
* Idempotency: every Stripe event has a unique `id` (e.g.
|
|
19
|
-
* `evt_1NxxXxxxxxxx`). We persist processed event IDs in a small
|
|
20
|
-
* `stripe_webhook_events` table; receiving the same event twice
|
|
21
|
-
* (which happens when Stripe retries) is a no-op. The atomic
|
|
22
|
-
* write happens inside a transaction so duplicate-detection and
|
|
23
|
-
* the payment/order update can't race.
|
|
24
|
-
*
|
|
25
|
-
* Signature verification happens before this module is reached —
|
|
26
|
-
* callers route the raw request through `payments.processWebhook`,
|
|
27
|
-
* which returns a verified `Stripe.Event`. Skipping that step is
|
|
28
|
-
* the obvious attack path (forge the body); see the wiring example
|
|
29
|
-
* at the bottom of this file.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```ts
|
|
33
|
-
* // routes/webhooks.ts
|
|
34
|
-
* import { processWebhook } from '@stacksjs/payments'
|
|
35
|
-
* import { registerCommerceWebhookHandlers } from '@stacksjs/commerce'
|
|
36
|
-
*
|
|
37
|
-
* // Once at boot:
|
|
38
|
-
* registerCommerceWebhookHandlers()
|
|
39
|
-
*
|
|
40
|
-
* // Per-request:
|
|
41
|
-
* route.post('/webhooks/stripe', async (req) => {
|
|
42
|
-
* const payload = await req.text()
|
|
43
|
-
* const signature = req.headers.get('stripe-signature') ?? ''
|
|
44
|
-
* const result = await processWebhook(payload, signature, {
|
|
45
|
-
* secret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
46
|
-
* tolerance: 300,
|
|
47
|
-
* })
|
|
48
|
-
* return Response.json(result, { status: result.success ? 200 : 400 })
|
|
49
|
-
* })
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
|
-
import { db } from '@stacksjs/database'
|
|
54
|
-
import { formatDate } from '@stacksjs/orm'
|
|
55
|
-
import { emitOrderCancelled, emitOrderPaid, emitOrderRefunded } from './events'
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Look up an order by its associated payment's `transaction_id`
|
|
59
|
-
* (which is the Stripe payment intent id). Returns null when no
|
|
60
|
-
* order matches — common during the brief window between Stripe
|
|
61
|
-
* sending `payment_intent.succeeded` and the local commit
|
|
62
|
-
* landing.
|
|
63
|
-
*/
|
|
64
|
-
async function findOrderByPaymentIntent(paymentIntentId: string): Promise<Record<string, unknown> | null> {
|
|
65
|
-
const payment = await (db as any)
|
|
66
|
-
.selectFrom('payments')
|
|
67
|
-
.where('transaction_id', '=', paymentIntentId)
|
|
68
|
-
.selectAll()
|
|
69
|
-
.executeTakeFirst()
|
|
70
|
-
if (!payment) return null
|
|
71
|
-
const order = await (db as any)
|
|
72
|
-
.selectFrom('orders')
|
|
73
|
-
.where('id', '=', payment.order_id)
|
|
74
|
-
.selectAll()
|
|
75
|
-
.executeTakeFirst()
|
|
76
|
-
return order ?? null
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Process-or-skip helper: returns true when this Stripe event id
|
|
81
|
-
* is new (and inserts the dedup row), false when we've seen it
|
|
82
|
-
* before. The insert happens inside the caller's transaction so
|
|
83
|
-
* a concurrent retry can't slip past.
|
|
84
|
-
*
|
|
85
|
-
* Falls back to a "best-effort, may double-process" path when the
|
|
86
|
-
* `stripe_webhook_events` table doesn't exist yet — the dedup
|
|
87
|
-
* surface is opt-in via migration. Apps that haven't created the
|
|
88
|
-
* table get a warn-once at startup.
|
|
89
|
-
*/
|
|
90
|
-
let warnedAboutMissingDedupTable = false
|
|
91
|
-
async function recordEventOrSkip(eventId: string, trx: any): Promise<boolean> {
|
|
92
|
-
try {
|
|
93
|
-
await trx
|
|
94
|
-
.insertInto('stripe_webhook_events')
|
|
95
|
-
.values({
|
|
96
|
-
event_id: eventId,
|
|
97
|
-
processed_at: formatDate(new Date()),
|
|
98
|
-
})
|
|
99
|
-
.execute()
|
|
100
|
-
return true
|
|
101
|
-
}
|
|
102
|
-
catch (err: any) {
|
|
103
|
-
// Unique constraint hit = already processed = skip.
|
|
104
|
-
if (err?.message?.includes('UNIQUE constraint') || err?.message?.includes('Duplicate entry'))
|
|
105
|
-
return false
|
|
106
|
-
// Missing table → degrade to "process every time" with a warn
|
|
107
|
-
// on first occurrence so ops can spot the missing migration.
|
|
108
|
-
if (err?.message?.includes('no such table') || err?.message?.includes("doesn't exist")) {
|
|
109
|
-
if (!warnedAboutMissingDedupTable) {
|
|
110
|
-
warnedAboutMissingDedupTable = true
|
|
111
|
-
// eslint-disable-next-line no-console
|
|
112
|
-
console.warn('[commerce/webhook] stripe_webhook_events table missing — Stripe retries may be processed multiple times. Run migrations to enable dedup.')
|
|
113
|
-
}
|
|
114
|
-
return true
|
|
115
|
-
}
|
|
116
|
-
throw err
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Handler for `payment_intent.succeeded`. Marks the linked payment
|
|
122
|
-
* row + order row as paid in a single transaction. Idempotent via
|
|
123
|
-
* `recordEventOrSkip`.
|
|
124
|
-
*/
|
|
125
|
-
async function handlePaymentIntentSucceeded(event: { id: string, data: { object: any } }): Promise<void> {
|
|
126
|
-
const paymentIntent = event.data.object
|
|
127
|
-
const paymentIntentId = paymentIntent?.id as string | undefined
|
|
128
|
-
if (!paymentIntentId) return
|
|
129
|
-
|
|
130
|
-
await db.transaction().execute(async (trx: any) => {
|
|
131
|
-
const isNew = await recordEventOrSkip(event.id, trx)
|
|
132
|
-
if (!isNew) return // already processed
|
|
133
|
-
|
|
134
|
-
const payment = await trx
|
|
135
|
-
.selectFrom('payments')
|
|
136
|
-
.where('transaction_id', '=', paymentIntentId)
|
|
137
|
-
.selectAll()
|
|
138
|
-
.executeTakeFirst()
|
|
139
|
-
if (!payment) return // payment row not yet inserted; commerce path will catch up
|
|
140
|
-
|
|
141
|
-
const now = formatDate(new Date())
|
|
142
|
-
await trx
|
|
143
|
-
.updateTable('payments')
|
|
144
|
-
.set({ status: 'succeeded', updated_at: now })
|
|
145
|
-
.where('id', '=', payment.id)
|
|
146
|
-
.execute()
|
|
147
|
-
|
|
148
|
-
// Move the order out of PENDING if it's still there. We use
|
|
149
|
-
// the same updateStatus guard via the state machine in events.ts
|
|
150
|
-
// (PENDING → PROCESSING is legal). Don't force the transition
|
|
151
|
-
// for orders already past PROCESSING — those advanced via a
|
|
152
|
-
// different path and we shouldn't regress them.
|
|
153
|
-
const order = await trx
|
|
154
|
-
.selectFrom('orders')
|
|
155
|
-
.where('id', '=', payment.order_id)
|
|
156
|
-
.selectAll()
|
|
157
|
-
.executeTakeFirst()
|
|
158
|
-
if (order && order.status === 'PENDING') {
|
|
159
|
-
await trx
|
|
160
|
-
.updateTable('orders')
|
|
161
|
-
.set({ status: 'PROCESSING', updated_at: now })
|
|
162
|
-
.where('id', '=', order.id)
|
|
163
|
-
.execute()
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
// Fire-and-forget event emission AFTER the transaction commits
|
|
168
|
-
// so subscribers see the post-write state. Lookup happens outside
|
|
169
|
-
// the transaction since it's read-only.
|
|
170
|
-
const order = await findOrderByPaymentIntent(paymentIntentId)
|
|
171
|
-
if (order) await emitOrderPaid(order)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Handler for `payment_intent.payment_failed`. Marks the payment
|
|
176
|
-
* as failed and cancels the linked order. Records the failure
|
|
177
|
-
* reason on the payment row for ops triage.
|
|
178
|
-
*/
|
|
179
|
-
async function handlePaymentIntentFailed(event: { id: string, data: { object: any } }): Promise<void> {
|
|
180
|
-
const paymentIntent = event.data.object
|
|
181
|
-
const paymentIntentId = paymentIntent?.id as string | undefined
|
|
182
|
-
if (!paymentIntentId) return
|
|
183
|
-
|
|
184
|
-
const lastError = paymentIntent?.last_payment_error
|
|
185
|
-
const reason = (lastError?.message as string | undefined) ?? 'payment failed'
|
|
186
|
-
|
|
187
|
-
await db.transaction().execute(async (trx: any) => {
|
|
188
|
-
const isNew = await recordEventOrSkip(event.id, trx)
|
|
189
|
-
if (!isNew) return
|
|
190
|
-
|
|
191
|
-
const payment = await trx
|
|
192
|
-
.selectFrom('payments')
|
|
193
|
-
.where('transaction_id', '=', paymentIntentId)
|
|
194
|
-
.selectAll()
|
|
195
|
-
.executeTakeFirst()
|
|
196
|
-
if (!payment) return
|
|
197
|
-
|
|
198
|
-
const now = formatDate(new Date())
|
|
199
|
-
await trx
|
|
200
|
-
.updateTable('payments')
|
|
201
|
-
.set({ status: 'failed', failure_reason: reason, updated_at: now })
|
|
202
|
-
.where('id', '=', payment.id)
|
|
203
|
-
.execute()
|
|
204
|
-
|
|
205
|
-
const order = await trx
|
|
206
|
-
.selectFrom('orders')
|
|
207
|
-
.where('id', '=', payment.order_id)
|
|
208
|
-
.selectAll()
|
|
209
|
-
.executeTakeFirst()
|
|
210
|
-
if (order && order.status === 'PENDING') {
|
|
211
|
-
await trx
|
|
212
|
-
.updateTable('orders')
|
|
213
|
-
.set({ status: 'CANCELLED', updated_at: now })
|
|
214
|
-
.where('id', '=', order.id)
|
|
215
|
-
.execute()
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
const order = await findOrderByPaymentIntent(paymentIntentId)
|
|
220
|
-
if (order) await emitOrderCancelled(order, reason)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Handler for `charge.refunded`. Marks the order REFUNDED and
|
|
225
|
-
* records the refund amount on the linked payment.
|
|
226
|
-
*/
|
|
227
|
-
async function handleChargeRefunded(event: { id: string, data: { object: any } }): Promise<void> {
|
|
228
|
-
const charge = event.data.object
|
|
229
|
-
const paymentIntentId = charge?.payment_intent as string | undefined
|
|
230
|
-
if (!paymentIntentId) return
|
|
231
|
-
|
|
232
|
-
const refundAmount = (charge?.amount_refunded as number | undefined) ?? 0
|
|
233
|
-
|
|
234
|
-
await db.transaction().execute(async (trx: any) => {
|
|
235
|
-
const isNew = await recordEventOrSkip(event.id, trx)
|
|
236
|
-
if (!isNew) return
|
|
237
|
-
|
|
238
|
-
const payment = await trx
|
|
239
|
-
.selectFrom('payments')
|
|
240
|
-
.where('transaction_id', '=', paymentIntentId)
|
|
241
|
-
.selectAll()
|
|
242
|
-
.executeTakeFirst()
|
|
243
|
-
if (!payment) return
|
|
244
|
-
|
|
245
|
-
const now = formatDate(new Date())
|
|
246
|
-
await trx
|
|
247
|
-
.updateTable('payments')
|
|
248
|
-
.set({ status: 'refunded', refund_amount: refundAmount, updated_at: now })
|
|
249
|
-
.where('id', '=', payment.id)
|
|
250
|
-
.execute()
|
|
251
|
-
|
|
252
|
-
const order = await trx
|
|
253
|
-
.selectFrom('orders')
|
|
254
|
-
.where('id', '=', payment.order_id)
|
|
255
|
-
.selectAll()
|
|
256
|
-
.executeTakeFirst()
|
|
257
|
-
if (order && order.status !== 'REFUNDED') {
|
|
258
|
-
await trx
|
|
259
|
-
.updateTable('orders')
|
|
260
|
-
.set({ status: 'REFUNDED', updated_at: now })
|
|
261
|
-
.where('id', '=', order.id)
|
|
262
|
-
.execute()
|
|
263
|
-
}
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
const order = await findOrderByPaymentIntent(paymentIntentId)
|
|
267
|
-
if (order) await emitOrderRefunded(order, refundAmount)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Wire commerce's Stripe webhook handlers into the payments
|
|
272
|
-
* package's event router. Call once at boot.
|
|
273
|
-
*
|
|
274
|
-
* Returns an unregister callback for tests that want to swap the
|
|
275
|
-
* handler set between cases.
|
|
276
|
-
*/
|
|
277
|
-
export async function registerCommerceWebhookHandlers(): Promise<() => void> {
|
|
278
|
-
const payments = await import('@stacksjs/payments').catch(() => null)
|
|
279
|
-
if (!payments) {
|
|
280
|
-
// eslint-disable-next-line no-console
|
|
281
|
-
console.warn('[commerce/webhook] @stacksjs/payments not installed — Stripe webhook handlers NOT registered')
|
|
282
|
-
return () => {}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const onPaymentIntent = (payments as { onPaymentIntent?: (h: Record<string, unknown>) => void }).onPaymentIntent
|
|
286
|
-
const onCharge = (payments as { onCharge?: (h: Record<string, unknown>) => void }).onCharge
|
|
287
|
-
if (typeof onPaymentIntent !== 'function' || typeof onCharge !== 'function') {
|
|
288
|
-
// eslint-disable-next-line no-console
|
|
289
|
-
console.warn('[commerce/webhook] @stacksjs/payments shape changed — handlers NOT registered')
|
|
290
|
-
return () => {}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
onPaymentIntent({
|
|
294
|
-
succeeded: handlePaymentIntentSucceeded,
|
|
295
|
-
failed: handlePaymentIntentFailed,
|
|
296
|
-
})
|
|
297
|
-
onCharge({
|
|
298
|
-
refunded: handleChargeRefunded,
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
// No first-class unregister in payments today — return a no-op
|
|
302
|
-
// so callers can plan for the future without baking the absence
|
|
303
|
-
// of unregister into their code.
|
|
304
|
-
return () => {}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Export the individual handlers for direct testing.
|
|
308
|
-
export {
|
|
309
|
-
handleChargeRefunded,
|
|
310
|
-
handlePaymentIntentFailed,
|
|
311
|
-
handlePaymentIntentSucceeded,
|
|
312
|
-
}
|
package/src/payments/destroy.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { db } from '@stacksjs/database'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Delete a payment by ID
|
|
5
|
-
*
|
|
6
|
-
* @param id The ID of the payment to delete
|
|
7
|
-
* @returns True if the payment was deleted, false otherwise
|
|
8
|
-
*/
|
|
9
|
-
export async function destroy(id: number): Promise<boolean> {
|
|
10
|
-
try {
|
|
11
|
-
// Perform the delete operation
|
|
12
|
-
const result = await db
|
|
13
|
-
.deleteFrom('payments')
|
|
14
|
-
.where('id', '=', id)
|
|
15
|
-
.executeTakeFirst()
|
|
16
|
-
|
|
17
|
-
// Return true if any row was affected (deleted)
|
|
18
|
-
return Number(result.numDeletedRows) > 0
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
if (error instanceof Error) {
|
|
22
|
-
throw new TypeError(`Failed to delete payment: ${error.message}`)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
throw error
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Delete multiple payments by ID
|
|
31
|
-
*
|
|
32
|
-
* @param ids Array of order IDs to delete
|
|
33
|
-
* @returns Number of payments deleted
|
|
34
|
-
*/
|
|
35
|
-
export async function bulkDestroy(ids: number[]): Promise<number> {
|
|
36
|
-
if (!ids.length)
|
|
37
|
-
return 0
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
// Perform the delete operation
|
|
41
|
-
const result = await db
|
|
42
|
-
.deleteFrom('payments')
|
|
43
|
-
.where('id', 'in', ids)
|
|
44
|
-
.executeTakeFirst()
|
|
45
|
-
|
|
46
|
-
// Return the number of deleted rows
|
|
47
|
-
return Number(result.numDeletedRows)
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
if (error instanceof Error) {
|
|
51
|
-
throw new TypeError(`Failed to delete payments: ${error.message}`)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
throw error
|
|
55
|
-
}
|
|
56
|
-
}
|