@stacksjs/commerce 0.70.55 → 0.70.56

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 (297) hide show
  1. package/package.json +10 -7
  2. package/src/carts/destroy.ts +0 -54
  3. package/src/carts/fetch.ts +0 -20
  4. package/src/carts/index.ts +0 -13
  5. package/src/carts/store.ts +0 -75
  6. package/src/carts/update.ts +0 -78
  7. package/src/coupons/destroy.ts +0 -49
  8. package/src/coupons/fetch.ts +0 -510
  9. package/src/coupons/index.ts +0 -30
  10. package/src/coupons/store.ts +0 -46
  11. package/src/coupons/update.ts +0 -147
  12. package/src/customers/destroy.ts +0 -62
  13. package/src/customers/fetch.ts +0 -20
  14. package/src/customers/index.ts +0 -17
  15. package/src/customers/store.ts +0 -45
  16. package/src/customers/update.ts +0 -44
  17. package/src/devices/destroy.ts +0 -54
  18. package/src/devices/export.ts +0 -98
  19. package/src/devices/fetch.ts +0 -125
  20. package/src/devices/index.ts +0 -31
  21. package/src/devices/store.ts +0 -82
  22. package/src/devices/update.ts +0 -125
  23. package/src/errors/destroy.ts +0 -54
  24. package/src/errors/fetch.ts +0 -197
  25. package/src/errors/index.ts +0 -28
  26. package/src/errors/update.ts +0 -116
  27. package/src/gift-cards/destroy.ts +0 -87
  28. package/src/gift-cards/fetch.ts +0 -431
  29. package/src/gift-cards/index.ts +0 -26
  30. package/src/gift-cards/store.ts +0 -56
  31. package/src/gift-cards/update.ts +0 -102
  32. package/src/index.ts +0 -78
  33. package/src/orders/destroy.ts +0 -111
  34. package/src/orders/events.ts +0 -121
  35. package/src/orders/export.ts +0 -158
  36. package/src/orders/fetch.ts +0 -379
  37. package/src/orders/guards.ts +0 -261
  38. package/src/orders/index.ts +0 -91
  39. package/src/orders/place-order.ts +0 -274
  40. package/src/orders/store.ts +0 -51
  41. package/src/orders/totals.ts +0 -190
  42. package/src/orders/update.ts +0 -164
  43. package/src/orders/webhook.ts +0 -312
  44. package/src/payments/destroy.ts +0 -56
  45. package/src/payments/fetch.ts +0 -288
  46. package/src/payments/index.ts +0 -16
  47. package/src/payments/store.ts +0 -63
  48. package/src/payments/update.ts +0 -52
  49. package/src/product.ts +0 -0
  50. package/src/products/categories/destroy.ts +0 -103
  51. package/src/products/categories/fetch.ts +0 -285
  52. package/src/products/categories/index.ts +0 -36
  53. package/src/products/categories/store.ts +0 -87
  54. package/src/products/categories/update.ts +0 -251
  55. package/src/products/index.ts +0 -35
  56. package/src/products/items/destroy.ts +0 -54
  57. package/src/products/items/fetch.ts +0 -282
  58. package/src/products/items/index.ts +0 -26
  59. package/src/products/items/store.ts +0 -84
  60. package/src/products/items/update.ts +0 -324
  61. package/src/products/manufacturers/destroy.ts +0 -56
  62. package/src/products/manufacturers/fetch.ts +0 -117
  63. package/src/products/manufacturers/index.ts +0 -24
  64. package/src/products/manufacturers/store.ts +0 -82
  65. package/src/products/manufacturers/update.ts +0 -113
  66. package/src/products/reviews/destroy.ts +0 -56
  67. package/src/products/reviews/fetch.ts +0 -63
  68. package/src/products/reviews/index.ts +0 -25
  69. package/src/products/reviews/store.ts +0 -46
  70. package/src/products/reviews/update.ts +0 -82
  71. package/src/products/units/destroy.ts +0 -56
  72. package/src/products/units/fetch.ts +0 -20
  73. package/src/products/units/index.ts +0 -13
  74. package/src/products/units/store.ts +0 -162
  75. package/src/products/units/update.ts +0 -159
  76. package/src/products/variants/destroy.ts +0 -56
  77. package/src/products/variants/fetch.ts +0 -20
  78. package/src/products/variants/index.ts +0 -21
  79. package/src/products/variants/store.ts +0 -136
  80. package/src/products/variants/update.ts +0 -104
  81. package/src/receipts/destroy.ts +0 -54
  82. package/src/receipts/export.ts +0 -0
  83. package/src/receipts/fetch.ts +0 -222
  84. package/src/receipts/index.ts +0 -24
  85. package/src/receipts/printer.ts +0 -69
  86. package/src/receipts/store.ts +0 -69
  87. package/src/receipts/update.ts +0 -124
  88. package/src/shippings/delivery-routes/destroy.ts +0 -54
  89. package/src/shippings/delivery-routes/fetch.ts +0 -42
  90. package/src/shippings/delivery-routes/index.ts +0 -26
  91. package/src/shippings/delivery-routes/store.ts +0 -69
  92. package/src/shippings/delivery-routes/update.ts +0 -120
  93. package/src/shippings/digital-deliveries/destroy.ts +0 -111
  94. package/src/shippings/digital-deliveries/fetch.ts +0 -20
  95. package/src/shippings/digital-deliveries/index.ts +0 -26
  96. package/src/shippings/digital-deliveries/store.ts +0 -76
  97. package/src/shippings/digital-deliveries/update.ts +0 -129
  98. package/src/shippings/drivers/destroy.ts +0 -78
  99. package/src/shippings/drivers/fetch.ts +0 -20
  100. package/src/shippings/drivers/index.ts +0 -24
  101. package/src/shippings/drivers/store.ts +0 -69
  102. package/src/shippings/drivers/update.ts +0 -117
  103. package/src/shippings/index.ts +0 -39
  104. package/src/shippings/license-keys/destroy.ts +0 -111
  105. package/src/shippings/license-keys/fetch.ts +0 -20
  106. package/src/shippings/license-keys/index.ts +0 -26
  107. package/src/shippings/license-keys/store.ts +0 -77
  108. package/src/shippings/license-keys/update.ts +0 -117
  109. package/src/shippings/shipping-methods/destroy.ts +0 -111
  110. package/src/shippings/shipping-methods/fetch.ts +0 -62
  111. package/src/shippings/shipping-methods/index.ts +0 -28
  112. package/src/shippings/shipping-methods/store.ts +0 -123
  113. package/src/shippings/shipping-methods/update.ts +0 -120
  114. package/src/shippings/shipping-rates/destroy.ts +0 -102
  115. package/src/shippings/shipping-rates/fetch.ts +0 -320
  116. package/src/shippings/shipping-rates/index.ts +0 -36
  117. package/src/shippings/shipping-rates/store.ts +0 -102
  118. package/src/shippings/shipping-rates/update.ts +0 -139
  119. package/src/shippings/shipping-zones/destroy.ts +0 -111
  120. package/src/shippings/shipping-zones/fetch.ts +0 -96
  121. package/src/shippings/shipping-zones/index.ts +0 -26
  122. package/src/shippings/shipping-zones/store.ts +0 -74
  123. package/src/shippings/shipping-zones/update.ts +0 -156
  124. package/src/tax/destroy.ts +0 -54
  125. package/src/tax/fetch.ts +0 -20
  126. package/src/tax/index.ts +0 -24
  127. package/src/tax/store.ts +0 -69
  128. package/src/tax/update.ts +0 -119
  129. package/src/tests/coupons.test.ts +0 -90
  130. package/src/tests/customers.test.ts +0 -39
  131. package/src/tests/device.test.ts +0 -111
  132. package/src/tests/digitals.test.ts +0 -31
  133. package/src/tests/drivers.test.ts +0 -25
  134. package/src/tests/gift-cards.test.ts +0 -36
  135. package/src/tests/license.test.ts +0 -31
  136. package/src/tests/manufacturers.test.ts +0 -80
  137. package/src/tests/orders.test.ts +0 -25
  138. package/src/tests/payments.test.ts +0 -75
  139. package/src/tests/rates.test.ts +0 -367
  140. package/src/tests/receipts.test.ts +0 -287
  141. package/src/tests/restaurant.test.ts +0 -522
  142. package/src/tests/reviews.test.ts +0 -165
  143. package/src/tests/routes.test.ts +0 -51
  144. package/src/tests/setup.ts +0 -149
  145. package/src/tests/shipping.test.ts +0 -88
  146. package/src/tests/tax.test.ts +0 -25
  147. package/src/tests/units.test.ts +0 -110
  148. package/src/tests/variants.test.ts +0 -133
  149. package/src/tests/waitlist.test.ts +0 -303
  150. package/src/tests/zones.test.ts +0 -86
  151. package/src/types.ts +0 -312
  152. package/src/utils/typed-stats.ts +0 -67
  153. package/src/waitlists/index.ts +0 -19
  154. package/src/waitlists/products/destroy.ts +0 -65
  155. package/src/waitlists/products/export.ts +0 -113
  156. package/src/waitlists/products/fetch.ts +0 -362
  157. package/src/waitlists/products/index.ts +0 -33
  158. package/src/waitlists/products/store.ts +0 -112
  159. package/src/waitlists/products/update.ts +0 -125
  160. package/src/waitlists/restaurant/destroy.ts +0 -65
  161. package/src/waitlists/restaurant/export.ts +0 -107
  162. package/src/waitlists/restaurant/fetch.ts +0 -538
  163. package/src/waitlists/restaurant/index.ts +0 -43
  164. package/src/waitlists/restaurant/store.ts +0 -109
  165. package/src/waitlists/restaurant/update.ts +0 -198
  166. /package/dist/{src/coupons → coupons}/destroy.d.ts +0 -0
  167. /package/dist/{src/coupons → coupons}/fetch.d.ts +0 -0
  168. /package/dist/{src/coupons → coupons}/index.d.ts +0 -0
  169. /package/dist/{src/coupons → coupons}/store.d.ts +0 -0
  170. /package/dist/{src/coupons → coupons}/update.d.ts +0 -0
  171. /package/dist/{src/customers → customers}/destroy.d.ts +0 -0
  172. /package/dist/{src/customers → customers}/fetch.d.ts +0 -0
  173. /package/dist/{src/customers → customers}/index.d.ts +0 -0
  174. /package/dist/{src/customers → customers}/store.d.ts +0 -0
  175. /package/dist/{src/customers → customers}/update.d.ts +0 -0
  176. /package/dist/{src/devices → devices}/destroy.d.ts +0 -0
  177. /package/dist/{src/devices → devices}/export.d.ts +0 -0
  178. /package/dist/{src/devices → devices}/fetch.d.ts +0 -0
  179. /package/dist/{src/devices → devices}/index.d.ts +0 -0
  180. /package/dist/{src/devices → devices}/store.d.ts +0 -0
  181. /package/dist/{src/devices → devices}/update.d.ts +0 -0
  182. /package/dist/{src/errors → errors}/destroy.d.ts +0 -0
  183. /package/dist/{src/errors → errors}/fetch.d.ts +0 -0
  184. /package/dist/{src/errors → errors}/index.d.ts +0 -0
  185. /package/dist/{src/errors → errors}/update.d.ts +0 -0
  186. /package/dist/{src/gift-cards → gift-cards}/destroy.d.ts +0 -0
  187. /package/dist/{src/gift-cards → gift-cards}/fetch.d.ts +0 -0
  188. /package/dist/{src/gift-cards → gift-cards}/index.d.ts +0 -0
  189. /package/dist/{src/gift-cards → gift-cards}/store.d.ts +0 -0
  190. /package/dist/{src/gift-cards → gift-cards}/update.d.ts +0 -0
  191. /package/dist/{src/index.d.ts → index.d.ts} +0 -0
  192. /package/dist/{src/orders → orders}/destroy.d.ts +0 -0
  193. /package/dist/{src/orders → orders}/events.d.ts +0 -0
  194. /package/dist/{src/orders → orders}/export.d.ts +0 -0
  195. /package/dist/{src/orders → orders}/fetch.d.ts +0 -0
  196. /package/dist/{src/orders → orders}/guards.d.ts +0 -0
  197. /package/dist/{src/orders → orders}/index.d.ts +0 -0
  198. /package/dist/{src/orders → orders}/place-order.d.ts +0 -0
  199. /package/dist/{src/orders → orders}/store.d.ts +0 -0
  200. /package/dist/{src/orders → orders}/totals.d.ts +0 -0
  201. /package/dist/{src/orders → orders}/update.d.ts +0 -0
  202. /package/dist/{src/orders → orders}/webhook.d.ts +0 -0
  203. /package/dist/{src/payments → payments}/destroy.d.ts +0 -0
  204. /package/dist/{src/payments → payments}/fetch.d.ts +0 -0
  205. /package/dist/{src/payments → payments}/index.d.ts +0 -0
  206. /package/dist/{src/payments → payments}/store.d.ts +0 -0
  207. /package/dist/{src/payments → payments}/update.d.ts +0 -0
  208. /package/dist/{src/products → products}/categories/destroy.d.ts +0 -0
  209. /package/dist/{src/products → products}/categories/fetch.d.ts +0 -0
  210. /package/dist/{src/products → products}/categories/index.d.ts +0 -0
  211. /package/dist/{src/products → products}/categories/store.d.ts +0 -0
  212. /package/dist/{src/products → products}/categories/update.d.ts +0 -0
  213. /package/dist/{src/products → products}/index.d.ts +0 -0
  214. /package/dist/{src/products → products}/items/destroy.d.ts +0 -0
  215. /package/dist/{src/products → products}/items/fetch.d.ts +0 -0
  216. /package/dist/{src/products → products}/items/index.d.ts +0 -0
  217. /package/dist/{src/products → products}/items/store.d.ts +0 -0
  218. /package/dist/{src/products → products}/items/update.d.ts +0 -0
  219. /package/dist/{src/products → products}/manufacturers/destroy.d.ts +0 -0
  220. /package/dist/{src/products → products}/manufacturers/fetch.d.ts +0 -0
  221. /package/dist/{src/products → products}/manufacturers/index.d.ts +0 -0
  222. /package/dist/{src/products → products}/manufacturers/store.d.ts +0 -0
  223. /package/dist/{src/products → products}/manufacturers/update.d.ts +0 -0
  224. /package/dist/{src/products → products}/reviews/destroy.d.ts +0 -0
  225. /package/dist/{src/products → products}/reviews/fetch.d.ts +0 -0
  226. /package/dist/{src/products → products}/reviews/index.d.ts +0 -0
  227. /package/dist/{src/products → products}/reviews/store.d.ts +0 -0
  228. /package/dist/{src/products → products}/reviews/update.d.ts +0 -0
  229. /package/dist/{src/products → products}/units/destroy.d.ts +0 -0
  230. /package/dist/{src/products → products}/units/fetch.d.ts +0 -0
  231. /package/dist/{src/products → products}/units/index.d.ts +0 -0
  232. /package/dist/{src/products → products}/units/store.d.ts +0 -0
  233. /package/dist/{src/products → products}/units/update.d.ts +0 -0
  234. /package/dist/{src/products → products}/variants/destroy.d.ts +0 -0
  235. /package/dist/{src/products → products}/variants/fetch.d.ts +0 -0
  236. /package/dist/{src/products → products}/variants/index.d.ts +0 -0
  237. /package/dist/{src/products → products}/variants/store.d.ts +0 -0
  238. /package/dist/{src/products → products}/variants/update.d.ts +0 -0
  239. /package/dist/{src/receipts → receipts}/destroy.d.ts +0 -0
  240. /package/dist/{src/receipts → receipts}/fetch.d.ts +0 -0
  241. /package/dist/{src/receipts → receipts}/index.d.ts +0 -0
  242. /package/dist/{src/receipts → receipts}/store.d.ts +0 -0
  243. /package/dist/{src/receipts → receipts}/update.d.ts +0 -0
  244. /package/dist/{src/shippings → shippings}/delivery-routes/destroy.d.ts +0 -0
  245. /package/dist/{src/shippings → shippings}/delivery-routes/fetch.d.ts +0 -0
  246. /package/dist/{src/shippings → shippings}/delivery-routes/index.d.ts +0 -0
  247. /package/dist/{src/shippings → shippings}/delivery-routes/store.d.ts +0 -0
  248. /package/dist/{src/shippings → shippings}/delivery-routes/update.d.ts +0 -0
  249. /package/dist/{src/shippings → shippings}/digital-deliveries/destroy.d.ts +0 -0
  250. /package/dist/{src/shippings → shippings}/digital-deliveries/fetch.d.ts +0 -0
  251. /package/dist/{src/shippings → shippings}/digital-deliveries/index.d.ts +0 -0
  252. /package/dist/{src/shippings → shippings}/digital-deliveries/store.d.ts +0 -0
  253. /package/dist/{src/shippings → shippings}/digital-deliveries/update.d.ts +0 -0
  254. /package/dist/{src/shippings → shippings}/drivers/destroy.d.ts +0 -0
  255. /package/dist/{src/shippings → shippings}/drivers/fetch.d.ts +0 -0
  256. /package/dist/{src/shippings → shippings}/drivers/index.d.ts +0 -0
  257. /package/dist/{src/shippings → shippings}/drivers/store.d.ts +0 -0
  258. /package/dist/{src/shippings → shippings}/drivers/update.d.ts +0 -0
  259. /package/dist/{src/shippings → shippings}/index.d.ts +0 -0
  260. /package/dist/{src/shippings → shippings}/license-keys/destroy.d.ts +0 -0
  261. /package/dist/{src/shippings → shippings}/license-keys/fetch.d.ts +0 -0
  262. /package/dist/{src/shippings → shippings}/license-keys/index.d.ts +0 -0
  263. /package/dist/{src/shippings → shippings}/license-keys/store.d.ts +0 -0
  264. /package/dist/{src/shippings → shippings}/license-keys/update.d.ts +0 -0
  265. /package/dist/{src/shippings → shippings}/shipping-methods/destroy.d.ts +0 -0
  266. /package/dist/{src/shippings → shippings}/shipping-methods/fetch.d.ts +0 -0
  267. /package/dist/{src/shippings → shippings}/shipping-methods/index.d.ts +0 -0
  268. /package/dist/{src/shippings → shippings}/shipping-methods/store.d.ts +0 -0
  269. /package/dist/{src/shippings → shippings}/shipping-methods/update.d.ts +0 -0
  270. /package/dist/{src/shippings → shippings}/shipping-rates/destroy.d.ts +0 -0
  271. /package/dist/{src/shippings → shippings}/shipping-rates/fetch.d.ts +0 -0
  272. /package/dist/{src/shippings → shippings}/shipping-rates/index.d.ts +0 -0
  273. /package/dist/{src/shippings → shippings}/shipping-rates/store.d.ts +0 -0
  274. /package/dist/{src/shippings → shippings}/shipping-rates/update.d.ts +0 -0
  275. /package/dist/{src/shippings → shippings}/shipping-zones/destroy.d.ts +0 -0
  276. /package/dist/{src/shippings → shippings}/shipping-zones/fetch.d.ts +0 -0
  277. /package/dist/{src/shippings → shippings}/shipping-zones/index.d.ts +0 -0
  278. /package/dist/{src/shippings → shippings}/shipping-zones/store.d.ts +0 -0
  279. /package/dist/{src/shippings → shippings}/shipping-zones/update.d.ts +0 -0
  280. /package/dist/{src/tax → tax}/destroy.d.ts +0 -0
  281. /package/dist/{src/tax → tax}/fetch.d.ts +0 -0
  282. /package/dist/{src/tax → tax}/index.d.ts +0 -0
  283. /package/dist/{src/tax → tax}/store.d.ts +0 -0
  284. /package/dist/{src/tax → tax}/update.d.ts +0 -0
  285. /package/dist/{src/types.d.ts → types.d.ts} +0 -0
  286. /package/dist/{src/utils → utils}/typed-stats.d.ts +0 -0
  287. /package/dist/{src/waitlists → waitlists}/index.d.ts +0 -0
  288. /package/dist/{src/waitlists → waitlists}/products/destroy.d.ts +0 -0
  289. /package/dist/{src/waitlists → waitlists}/products/fetch.d.ts +0 -0
  290. /package/dist/{src/waitlists → waitlists}/products/index.d.ts +0 -0
  291. /package/dist/{src/waitlists → waitlists}/products/store.d.ts +0 -0
  292. /package/dist/{src/waitlists → waitlists}/products/update.d.ts +0 -0
  293. /package/dist/{src/waitlists → waitlists}/restaurant/destroy.d.ts +0 -0
  294. /package/dist/{src/waitlists → waitlists}/restaurant/fetch.d.ts +0 -0
  295. /package/dist/{src/waitlists → waitlists}/restaurant/index.d.ts +0 -0
  296. /package/dist/{src/waitlists → waitlists}/restaurant/store.d.ts +0 -0
  297. /package/dist/{src/waitlists → waitlists}/restaurant/update.d.ts +0 -0
@@ -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
- }
@@ -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
- }
@@ -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
- }