@aglyn/plugins-commerce 1.0.0-beta.285 → 1.0.0-beta.288

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 (430) hide show
  1. package/README.md +1 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/cart.js +25 -12
  4. package/src/lib/components/cart.js.map +1 -1
  5. package/src/lib/components/commerce-console-page.js +70 -13
  6. package/src/lib/components/commerce-console-page.js.map +1 -1
  7. package/src/lib/components/console/order-detail-dialog.component.js +2 -1
  8. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  9. package/src/lib/components/console/order-webhooks-card.component.js +1 -1
  10. package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
  11. package/src/lib/components/console/payment-methods-card.component.js +1 -1
  12. package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
  13. package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +5 -0
  14. package/src/lib/components/console/pos/pos-cart-panel.component.js +18 -12
  15. package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -1
  16. package/src/lib/components/console/pos/pos-hardware-api.d.ts +76 -5
  17. package/src/lib/components/console/pos/pos-hardware-api.js +53 -11
  18. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -1
  19. package/src/lib/components/console/pos/pos-hardware-buyer.component.d.ts +18 -0
  20. package/src/lib/components/console/pos/pos-hardware-buyer.component.js +629 -0
  21. package/src/lib/components/console/pos/pos-hardware-buyer.component.js.map +1 -0
  22. package/src/lib/components/console/pos/pos-hardware-sale-api.d.ts +162 -0
  23. package/src/lib/components/console/pos/pos-hardware-sale-api.js +122 -0
  24. package/src/lib/components/console/pos/pos-hardware-sale-api.js.map +1 -0
  25. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.d.ts +39 -0
  26. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js +251 -0
  27. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js.map +1 -0
  28. package/src/lib/components/console/pos/use-pos-display.js +50 -0
  29. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  30. package/src/lib/components/console/pos-cellular-control.component.d.ts +41 -0
  31. package/src/lib/components/console/pos-cellular-control.component.js +211 -0
  32. package/src/lib/components/console/pos-cellular-control.component.js.map +1 -0
  33. package/src/lib/components/console/pos-devices-card.component.d.ts +9 -1
  34. package/src/lib/components/console/pos-devices-card.component.js +47 -15
  35. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  36. package/src/lib/components/console/pos-display/pos-display-api.d.ts +6 -0
  37. package/src/lib/components/console/pos-display/pos-display-api.js +8 -1
  38. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  39. package/src/lib/components/console/pos-display-page.component.d.ts +13 -0
  40. package/src/lib/components/console/pos-display-page.component.js +44 -2
  41. package/src/lib/components/console/pos-display-page.component.js.map +1 -1
  42. package/src/lib/components/console/pos-hardware-card.component.d.ts +12 -4
  43. package/src/lib/components/console/pos-hardware-card.component.js +157 -36
  44. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -1
  45. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +360 -80
  46. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -1
  47. package/src/lib/components/console/pos-hardware-terms-page.component.d.ts +34 -0
  48. package/src/lib/components/console/pos-hardware-terms-page.component.js +257 -0
  49. package/src/lib/components/console/pos-hardware-terms-page.component.js.map +1 -0
  50. package/src/lib/components/console/pos-hardware-terms-sign-page.component.d.ts +27 -0
  51. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js +104 -0
  52. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js.map +1 -0
  53. package/src/lib/components/console/pos-hardware-waiting-card.component.d.ts +24 -0
  54. package/src/lib/components/console/pos-hardware-waiting-card.component.js +205 -0
  55. package/src/lib/components/console/pos-hardware-waiting-card.component.js.map +1 -0
  56. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +297 -201
  57. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -1
  58. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +3 -0
  59. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +260 -92
  60. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  61. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +9 -0
  62. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +15 -6
  63. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  64. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.d.ts +25 -0
  65. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js +136 -0
  66. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js.map +1 -0
  67. package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +6 -0
  68. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +48 -31
  69. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -1
  70. package/src/lib/components/console/pos-page.component.d.ts +1 -1
  71. package/src/lib/components/console/pos-page.component.js +63 -12
  72. package/src/lib/components/console/pos-page.component.js.map +1 -1
  73. package/src/lib/components/console/pos-reader-splash-setting.component.js +1 -1
  74. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -1
  75. package/src/lib/components/console/product-check-in-fields.component.d.ts +4 -2
  76. package/src/lib/components/console/product-check-in-fields.component.js +60 -14
  77. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -1
  78. package/src/lib/components/console/product-editor-dialog.component.js +44 -1
  79. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  80. package/src/lib/components/console/product-hardware-sku-field.component.d.ts +19 -0
  81. package/src/lib/components/console/product-hardware-sku-field.component.js +92 -0
  82. package/src/lib/components/console/product-hardware-sku-field.component.js.map +1 -0
  83. package/src/lib/components/console/product-kiosk-offer-field.component.d.ts +22 -0
  84. package/src/lib/components/console/product-kiosk-offer-field.component.js +94 -0
  85. package/src/lib/components/console/product-kiosk-offer-field.component.js.map +1 -0
  86. package/src/lib/components/console/product-register-fields.component.d.ts +4 -0
  87. package/src/lib/components/console/product-register-fields.component.js +31 -2
  88. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  89. package/src/lib/components/console/products-hub-card.component.js +11 -2
  90. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  91. package/src/lib/components/console/register-hardware-card.component.d.ts +11 -0
  92. package/src/lib/components/console/register-hardware-card.component.js +56 -27
  93. package/src/lib/components/console/register-hardware-card.component.js.map +1 -1
  94. package/src/lib/components/console/reservation-guests-dialog.component.d.ts +28 -0
  95. package/src/lib/components/console/reservation-guests-dialog.component.js +189 -0
  96. package/src/lib/components/console/reservation-guests-dialog.component.js.map +1 -0
  97. package/src/lib/components/console/reservations-card.component.d.ts +9 -0
  98. package/src/lib/components/console/reservations-card.component.js +158 -23
  99. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  100. package/src/lib/components/console/shippingeasy-card.component.js +1 -1
  101. package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
  102. package/src/lib/components/console/shipstation-card.component.d.ts +1 -1
  103. package/src/lib/components/console/shipstation-card.component.js +3 -3
  104. package/src/lib/components/console/shipstation-card.component.js.map +1 -1
  105. package/src/lib/components/product-detail.d.ts +4 -0
  106. package/src/lib/components/product-detail.js +40 -5
  107. package/src/lib/components/product-detail.js.map +1 -1
  108. package/src/lib/components/product-reviews.js +28 -11
  109. package/src/lib/components/product-reviews.js.map +1 -1
  110. package/src/lib/components/reservation-widget.js +28 -5
  111. package/src/lib/components/reservation-widget.js.map +1 -1
  112. package/src/lib/components/use-day-picker.d.ts +25 -0
  113. package/src/lib/components/use-day-picker.js +57 -0
  114. package/src/lib/components/use-day-picker.js.map +1 -0
  115. package/src/lib/constants/operator-alerts.d.ts +55 -0
  116. package/src/lib/constants/operator-alerts.js +156 -1
  117. package/src/lib/constants/operator-alerts.js.map +1 -1
  118. package/src/lib/declarations.console-server.js +54 -1
  119. package/src/lib/declarations.console-server.js.map +1 -1
  120. package/src/lib/declarations.d.ts +4 -2
  121. package/src/lib/declarations.js +15 -4
  122. package/src/lib/declarations.js.map +1 -1
  123. package/src/lib/declarations.server.js +9 -0
  124. package/src/lib/declarations.server.js.map +1 -1
  125. package/src/lib/model/commerce-cart.d.ts +9 -1
  126. package/src/lib/model/commerce-cart.js +4 -3
  127. package/src/lib/model/commerce-cart.js.map +1 -1
  128. package/src/lib/model/commerce-check-in-product.d.ts +26 -0
  129. package/src/lib/model/commerce-check-in-product.js +64 -2
  130. package/src/lib/model/commerce-check-in-product.js.map +1 -1
  131. package/src/lib/model/commerce-check-in-record.d.ts +127 -0
  132. package/src/lib/model/commerce-check-in-record.js +36 -0
  133. package/src/lib/model/commerce-check-in-record.js.map +1 -0
  134. package/src/lib/model/commerce-check-in.d.ts +91 -87
  135. package/src/lib/model/commerce-check-in.js +186 -62
  136. package/src/lib/model/commerce-check-in.js.map +1 -1
  137. package/src/lib/model/commerce-kiosk-offer.d.ts +31 -0
  138. package/src/lib/model/commerce-kiosk-offer.js +35 -0
  139. package/src/lib/model/commerce-kiosk-offer.js.map +1 -0
  140. package/src/lib/model/commerce-orders.d.ts +76 -0
  141. package/src/lib/model/commerce-orders.js +6 -0
  142. package/src/lib/model/commerce-orders.js.map +1 -1
  143. package/src/lib/model/commerce-pos-day-report.d.ts +54 -1
  144. package/src/lib/model/commerce-pos-day-report.js +39 -2
  145. package/src/lib/model/commerce-pos-day-report.js.map +1 -1
  146. package/src/lib/model/commerce-pos-offline.d.ts +1 -1
  147. package/src/lib/model/commerce-pos-offline.js +6 -1
  148. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  149. package/src/lib/model/commerce-pos-ops.js +3 -2
  150. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  151. package/src/lib/model/commerce-pos.d.ts +36 -3
  152. package/src/lib/model/commerce-pos.js +14 -6
  153. package/src/lib/model/commerce-pos.js.map +1 -1
  154. package/src/lib/model/commerce-receipt.js +3 -2
  155. package/src/lib/model/commerce-receipt.js.map +1 -1
  156. package/src/lib/model/commerce-reservations.d.ts +69 -4
  157. package/src/lib/model/commerce-reservations.js +57 -5
  158. package/src/lib/model/commerce-reservations.js.map +1 -1
  159. package/src/lib/model/commerce.d.ts +66 -0
  160. package/src/lib/model/commerce.js +15 -0
  161. package/src/lib/model/commerce.js.map +1 -1
  162. package/src/lib/model/order-list-fields.d.ts +11 -0
  163. package/src/lib/model/order-list-fields.js +17 -1
  164. package/src/lib/model/order-list-fields.js.map +1 -1
  165. package/src/lib/model/own-store-tax-summary.d.ts +152 -0
  166. package/src/lib/model/own-store-tax-summary.js +288 -0
  167. package/src/lib/model/own-store-tax-summary.js.map +1 -0
  168. package/src/lib/model/plugin-permissions.d.ts +74 -0
  169. package/src/lib/model/plugin-permissions.js +224 -7
  170. package/src/lib/model/plugin-permissions.js.map +1 -1
  171. package/src/lib/model/pos-cellular.d.ts +223 -0
  172. package/src/lib/model/pos-cellular.js +293 -0
  173. package/src/lib/model/pos-cellular.js.map +1 -0
  174. package/src/lib/model/pos-hardware-in-person.d.ts +98 -0
  175. package/src/lib/model/pos-hardware-in-person.js +224 -0
  176. package/src/lib/model/pos-hardware-in-person.js.map +1 -0
  177. package/src/lib/model/pos-hardware-tax-summary.d.ts +166 -0
  178. package/src/lib/model/pos-hardware-tax-summary.js +225 -0
  179. package/src/lib/model/pos-hardware-tax-summary.js.map +1 -0
  180. package/src/lib/model/pos-hardware-terms.d.ts +74 -0
  181. package/src/lib/model/pos-hardware-terms.js +115 -0
  182. package/src/lib/model/pos-hardware-terms.js.map +1 -0
  183. package/src/lib/model/pos-hardware.d.ts +349 -7
  184. package/src/lib/model/pos-hardware.js +291 -20
  185. package/src/lib/model/pos-hardware.js.map +1 -1
  186. package/src/lib/model/product-write.d.ts +10 -0
  187. package/src/lib/model/product-write.js +4 -2
  188. package/src/lib/model/product-write.js.map +1 -1
  189. package/src/lib/model/workspace-site-permissions.d.ts +26 -0
  190. package/src/lib/model/workspace-site-permissions.js +81 -0
  191. package/src/lib/model/workspace-site-permissions.js.map +1 -0
  192. package/src/lib/plugin.js +16 -0
  193. package/src/lib/plugin.js.map +1 -1
  194. package/src/lib/server/billing-webhook.js +25 -1
  195. package/src/lib/server/billing-webhook.js.map +1 -1
  196. package/src/lib/server/cancel-order.js +7 -5
  197. package/src/lib/server/cancel-order.js.map +1 -1
  198. package/src/lib/server/cart-checkout.js +20 -8
  199. package/src/lib/server/cart-checkout.js.map +1 -1
  200. package/src/lib/server/cart.js +29 -0
  201. package/src/lib/server/cart.js.map +1 -1
  202. package/src/lib/server/checkout.js +42 -13
  203. package/src/lib/server/checkout.js.map +1 -1
  204. package/src/lib/server/collection-membership.d.ts +4 -0
  205. package/src/lib/server/collection-membership.js +5 -10
  206. package/src/lib/server/collection-membership.js.map +1 -1
  207. package/src/lib/server/commerce-site-keys.d.ts +48 -0
  208. package/src/lib/server/commerce-site-keys.js +61 -0
  209. package/src/lib/server/commerce-site-keys.js.map +1 -0
  210. package/src/lib/server/draft-order.js +8 -5
  211. package/src/lib/server/draft-order.js.map +1 -1
  212. package/src/lib/server/fulfill-order.js +7 -5
  213. package/src/lib/server/fulfill-order.js.map +1 -1
  214. package/src/lib/server/gift-cards.js +8 -5
  215. package/src/lib/server/gift-cards.js.map +1 -1
  216. package/src/lib/server/local-fulfillment-status.js +9 -6
  217. package/src/lib/server/local-fulfillment-status.js.map +1 -1
  218. package/src/lib/server/member-post.js +6 -5
  219. package/src/lib/server/member-post.js.map +1 -1
  220. package/src/lib/server/membership-admin-password.d.ts +4 -2
  221. package/src/lib/server/membership-admin-password.js +10 -8
  222. package/src/lib/server/membership-admin-password.js.map +1 -1
  223. package/src/lib/server/membership-admin-remove.d.ts +7 -5
  224. package/src/lib/server/membership-admin-remove.js +9 -9
  225. package/src/lib/server/membership-admin-remove.js.map +1 -1
  226. package/src/lib/server/order-notifications.js +5 -2
  227. package/src/lib/server/order-notifications.js.map +1 -1
  228. package/src/lib/server/order-print.js +7 -5
  229. package/src/lib/server/order-print.js.map +1 -1
  230. package/src/lib/server/order-receipt-send.js +8 -5
  231. package/src/lib/server/order-receipt-send.js.map +1 -1
  232. package/src/lib/server/order-webhooks.js +8 -8
  233. package/src/lib/server/order-webhooks.js.map +1 -1
  234. package/src/lib/server/orders-shipping-prepare.js +9 -6
  235. package/src/lib/server/orders-shipping-prepare.js.map +1 -1
  236. package/src/lib/server/own-store-tax-return-source.d.ts +47 -0
  237. package/src/lib/server/own-store-tax-return-source.js +312 -0
  238. package/src/lib/server/own-store-tax-return-source.js.map +1 -0
  239. package/src/lib/server/payment-methods.js +13 -9
  240. package/src/lib/server/payment-methods.js.map +1 -1
  241. package/src/lib/server/pos-auth.d.ts +37 -6
  242. package/src/lib/server/pos-auth.js +40 -15
  243. package/src/lib/server/pos-auth.js.map +1 -1
  244. package/src/lib/server/pos-cellular-billing.d.ts +81 -0
  245. package/src/lib/server/pos-cellular-billing.js +711 -0
  246. package/src/lib/server/pos-cellular-billing.js.map +1 -0
  247. package/src/lib/server/pos-cellular-notify.d.ts +47 -0
  248. package/src/lib/server/pos-cellular-notify.js +150 -0
  249. package/src/lib/server/pos-cellular-notify.js.map +1 -0
  250. package/src/lib/server/pos-cellular.d.ts +88 -0
  251. package/src/lib/server/pos-cellular.js +339 -0
  252. package/src/lib/server/pos-cellular.js.map +1 -0
  253. package/src/lib/server/pos-check-in.d.ts +5 -1
  254. package/src/lib/server/pos-check-in.js +296 -28
  255. package/src/lib/server/pos-check-in.js.map +1 -1
  256. package/src/lib/server/pos-day-report.js +29 -3
  257. package/src/lib/server/pos-day-report.js.map +1 -1
  258. package/src/lib/server/pos-display.d.ts +16 -0
  259. package/src/lib/server/pos-display.js +129 -2
  260. package/src/lib/server/pos-display.js.map +1 -1
  261. package/src/lib/server/pos-hardware-admin.d.ts +10 -1
  262. package/src/lib/server/pos-hardware-admin.js +250 -84
  263. package/src/lib/server/pos-hardware-admin.js.map +1 -1
  264. package/src/lib/server/pos-hardware-attach.d.ts +105 -0
  265. package/src/lib/server/pos-hardware-attach.js +443 -0
  266. package/src/lib/server/pos-hardware-attach.js.map +1 -0
  267. package/src/lib/server/pos-hardware-buyer-lookup.d.ts +58 -0
  268. package/src/lib/server/pos-hardware-buyer-lookup.js +180 -0
  269. package/src/lib/server/pos-hardware-buyer-lookup.js.map +1 -0
  270. package/src/lib/server/pos-hardware-buyer-record.d.ts +29 -0
  271. package/src/lib/server/pos-hardware-buyer-record.js +86 -0
  272. package/src/lib/server/pos-hardware-buyer-record.js.map +1 -0
  273. package/src/lib/server/pos-hardware-cancel.d.ts +58 -0
  274. package/src/lib/server/pos-hardware-cancel.js +147 -0
  275. package/src/lib/server/pos-hardware-cancel.js.map +1 -0
  276. package/src/lib/server/pos-hardware-closure-notices.d.ts +27 -0
  277. package/src/lib/server/pos-hardware-closure-notices.js +85 -0
  278. package/src/lib/server/pos-hardware-closure-notices.js.map +1 -0
  279. package/src/lib/server/pos-hardware-closure.d.ts +60 -0
  280. package/src/lib/server/pos-hardware-closure.js +153 -0
  281. package/src/lib/server/pos-hardware-closure.js.map +1 -0
  282. package/src/lib/server/pos-hardware-in-person.d.ts +61 -0
  283. package/src/lib/server/pos-hardware-in-person.js +236 -0
  284. package/src/lib/server/pos-hardware-in-person.js.map +1 -0
  285. package/src/lib/server/pos-hardware-notify.d.ts +40 -2
  286. package/src/lib/server/pos-hardware-notify.js +195 -14
  287. package/src/lib/server/pos-hardware-notify.js.map +1 -1
  288. package/src/lib/server/pos-hardware-readers.d.ts +115 -0
  289. package/src/lib/server/pos-hardware-readers.js +430 -0
  290. package/src/lib/server/pos-hardware-readers.js.map +1 -0
  291. package/src/lib/server/pos-hardware-returns.d.ts +116 -0
  292. package/src/lib/server/pos-hardware-returns.js +502 -0
  293. package/src/lib/server/pos-hardware-returns.js.map +1 -0
  294. package/src/lib/server/pos-hardware-sale-fields.d.ts +28 -0
  295. package/src/lib/server/pos-hardware-sale-fields.js +44 -0
  296. package/src/lib/server/pos-hardware-sale-fields.js.map +1 -0
  297. package/src/lib/server/pos-hardware-sale.d.ts +54 -0
  298. package/src/lib/server/pos-hardware-sale.js +369 -0
  299. package/src/lib/server/pos-hardware-sale.js.map +1 -0
  300. package/src/lib/server/pos-hardware-tax-return-source.d.ts +99 -0
  301. package/src/lib/server/pos-hardware-tax-return-source.js +462 -0
  302. package/src/lib/server/pos-hardware-tax-return-source.js.map +1 -0
  303. package/src/lib/server/pos-hardware-terms.d.ts +64 -0
  304. package/src/lib/server/pos-hardware-terms.js +154 -0
  305. package/src/lib/server/pos-hardware-terms.js.map +1 -0
  306. package/src/lib/server/pos-hardware-user-eraser.d.ts +3 -0
  307. package/src/lib/server/pos-hardware-user-eraser.js +31 -7
  308. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -1
  309. package/src/lib/server/pos-hardware-webhook.d.ts +5 -0
  310. package/src/lib/server/pos-hardware-webhook.js +51 -6
  311. package/src/lib/server/pos-hardware-webhook.js.map +1 -1
  312. package/src/lib/server/pos-hardware.d.ts +74 -1
  313. package/src/lib/server/pos-hardware.js +175 -10
  314. package/src/lib/server/pos-hardware.js.map +1 -1
  315. package/src/lib/server/pos-kiosk.d.ts +2 -0
  316. package/src/lib/server/pos-kiosk.js +17 -12
  317. package/src/lib/server/pos-kiosk.js.map +1 -1
  318. package/src/lib/server/pos-offline-card-order.d.ts +64 -0
  319. package/src/lib/server/pos-offline-card-order.js +102 -0
  320. package/src/lib/server/pos-offline-card-order.js.map +1 -0
  321. package/src/lib/server/pos-offline-card-reconcile.d.ts +67 -1
  322. package/src/lib/server/pos-offline-card-reconcile.js +215 -18
  323. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -1
  324. package/src/lib/server/pos-offline-sync.d.ts +39 -0
  325. package/src/lib/server/pos-offline-sync.js +190 -55
  326. package/src/lib/server/pos-offline-sync.js.map +1 -1
  327. package/src/lib/server/pos-ops-gate.d.ts +28 -9
  328. package/src/lib/server/pos-ops-gate.js +50 -19
  329. package/src/lib/server/pos-ops-gate.js.map +1 -1
  330. package/src/lib/server/pos-ops-routes.js +24 -0
  331. package/src/lib/server/pos-ops-routes.js.map +1 -1
  332. package/src/lib/server/pos-order-refund-ids.d.ts +28 -0
  333. package/src/lib/server/pos-order-refund-ids.js +31 -0
  334. package/src/lib/server/pos-order-refund-ids.js.map +1 -0
  335. package/src/lib/server/pos-order.js +52 -49
  336. package/src/lib/server/pos-order.js.map +1 -1
  337. package/src/lib/server/pos-payment.js +1 -1
  338. package/src/lib/server/pos-payment.js.map +1 -1
  339. package/src/lib/server/pos-reader-splash-admin.js +30 -32
  340. package/src/lib/server/pos-reader-splash-admin.js.map +1 -1
  341. package/src/lib/server/pos-reader-splash-images-route.d.ts +17 -0
  342. package/src/lib/server/pos-reader-splash-images-route.js +113 -0
  343. package/src/lib/server/pos-reader-splash-images-route.js.map +1 -0
  344. package/src/lib/server/pos-reader-splash-images.d.ts +51 -0
  345. package/src/lib/server/pos-reader-splash-images.js +123 -0
  346. package/src/lib/server/pos-reader-splash-images.js.map +1 -0
  347. package/src/lib/server/pos-reader-splash.d.ts +68 -1
  348. package/src/lib/server/pos-reader-splash.js +182 -11
  349. package/src/lib/server/pos-reader-splash.js.map +1 -1
  350. package/src/lib/server/pos-readers.d.ts +29 -0
  351. package/src/lib/server/pos-readers.js +134 -35
  352. package/src/lib/server/pos-readers.js.map +1 -1
  353. package/src/lib/server/pos-refund-authority.d.ts +3 -1
  354. package/src/lib/server/pos-refund-authority.js +3 -2
  355. package/src/lib/server/pos-refund-authority.js.map +1 -1
  356. package/src/lib/server/pos-return.d.ts +10 -0
  357. package/src/lib/server/pos-return.js +29 -7
  358. package/src/lib/server/pos-return.js.map +1 -1
  359. package/src/lib/server/pos-sale.js +6 -1
  360. package/src/lib/server/pos-sale.js.map +1 -1
  361. package/src/lib/server/pos-shift.js +11 -0
  362. package/src/lib/server/pos-shift.js.map +1 -1
  363. package/src/lib/server/pos-staff-pin.js +29 -24
  364. package/src/lib/server/pos-staff-pin.js.map +1 -1
  365. package/src/lib/server/pos-terminal.d.ts +14 -4
  366. package/src/lib/server/pos-terminal.js +34 -38
  367. package/src/lib/server/pos-terminal.js.map +1 -1
  368. package/src/lib/server/printers.d.ts +7 -3
  369. package/src/lib/server/printers.js +18 -12
  370. package/src/lib/server/printers.js.map +1 -1
  371. package/src/lib/server/product-drafts.d.ts +11 -1
  372. package/src/lib/server/product-drafts.js +12 -8
  373. package/src/lib/server/product-drafts.js.map +1 -1
  374. package/src/lib/server/product.d.ts +15 -1
  375. package/src/lib/server/product.js +16 -3
  376. package/src/lib/server/product.js.map +1 -1
  377. package/src/lib/server/products-write.d.ts +26 -2
  378. package/src/lib/server/products-write.js +19 -7
  379. package/src/lib/server/products-write.js.map +1 -1
  380. package/src/lib/server/refund.js +55 -42
  381. package/src/lib/server/refund.js.map +1 -1
  382. package/src/lib/server/reservation-availability-query.d.ts +8 -0
  383. package/src/lib/server/reservation-availability-query.js +11 -1
  384. package/src/lib/server/reservation-availability-query.js.map +1 -1
  385. package/src/lib/server/reservation-availability.d.ts +1 -1
  386. package/src/lib/server/reservation-availability.js +7 -2
  387. package/src/lib/server/reservation-availability.js.map +1 -1
  388. package/src/lib/server/reservation-guests.d.ts +32 -0
  389. package/src/lib/server/reservation-guests.js +285 -0
  390. package/src/lib/server/reservation-guests.js.map +1 -0
  391. package/src/lib/server/reservation-walk-in.d.ts +11 -1
  392. package/src/lib/server/reservation-walk-in.js +56 -12
  393. package/src/lib/server/reservation-walk-in.js.map +1 -1
  394. package/src/lib/server/reserve.js +18 -5
  395. package/src/lib/server/reserve.js.map +1 -1
  396. package/src/lib/server/returns.js +9 -6
  397. package/src/lib/server/returns.js.map +1 -1
  398. package/src/lib/server/reviews.d.ts +22 -2
  399. package/src/lib/server/reviews.js +88 -20
  400. package/src/lib/server/reviews.js.map +1 -1
  401. package/src/lib/server/shipping-connectors.js +10 -11
  402. package/src/lib/server/shipping-connectors.js.map +1 -1
  403. package/src/lib/server/site-page-resolver.js +20 -3
  404. package/src/lib/server/site-page-resolver.js.map +1 -1
  405. package/src/lib/server/store-day.d.ts +33 -0
  406. package/src/lib/server/store-day.js +46 -0
  407. package/src/lib/server/store-day.js.map +1 -0
  408. package/src/lib/server/store-setup-ai-writer.d.ts +2 -1
  409. package/src/lib/server/store-setup-ai-writer.js +5 -6
  410. package/src/lib/server/store-setup-ai-writer.js.map +1 -1
  411. package/src/lib/server/storefront-tax.d.ts +13 -0
  412. package/src/lib/server/storefront-tax.js +8 -1
  413. package/src/lib/server/storefront-tax.js.map +1 -1
  414. package/src/lib/server/tax-return-source.d.ts +32 -3
  415. package/src/lib/server/tax-return-source.js +99 -20
  416. package/src/lib/server/tax-return-source.js.map +1 -1
  417. package/src/lib/server.js +100 -2
  418. package/src/lib/server.js.map +1 -1
  419. package/src/lib/testing/fake-firestore.js +58 -16
  420. package/src/lib/testing/fake-firestore.js.map +1 -1
  421. package/src/lib/testing/pos-ops-memory-firestore.js +18 -4
  422. package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -1
  423. package/src/lib/testing/site-register-permissions.d.ts +29 -0
  424. package/src/lib/testing/site-register-permissions.js +38 -0
  425. package/src/lib/testing/site-register-permissions.js.map +1 -0
  426. package/src/lib/transfer/tracking.server.js +8 -5
  427. package/src/lib/transfer/tracking.server.js.map +1 -1
  428. package/src/lib/utils/add-to-cart.d.ts +2 -0
  429. package/src/lib/utils/add-to-cart.js +6 -3
  430. package/src/lib/utils/add-to-cart.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/product.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\n\nexport interface PublicProductDetail {\n id: string\n name: string\n slug: string\n description?: string\n type: CommerceModel.ProductType\n mediaUrls: string[]\n options: CommerceModel.ProductOption[]\n variants: Array<{\n id: string\n options?: Record<string, string>\n /**\n * `null` for a variant listed before it has a price (AGL-3676): the page\n * says \"Price coming soon\" and offers nothing to buy, as no sale door\n * would sell it.\n */\n priceUsd: number | null\n compareAtPriceUsd?: number\n soldOut: boolean\n imageUrl?: string\n /**\n * Merchant's stock-keeping unit (AGL-686). Carried into the public DTO\n * so the server can put `sku` on the Product structured data — Google\n * Merchant treats it as required for a product offer, and without it a\n * listing is simply ineligible for rich results rather than warned about.\n * It is already public information: it prints on receipts and invoices.\n */\n sku?: string\n }>\n tags?: string[]\n /** Recurring billing (AGL-303) so the PDP frames the price. */\n subscription?: { interval: 'month' | 'year'; trialDays?: number }\n /** Buyer may choose one-time OR subscribe (AGL-545). */\n subscriptionOptional?: boolean\n}\n\n/**\n * Map a lifted product onto the public PDP DTO.\n *\n * Extracted (AGL-659) so the site-page resolver can put the SAME payload\n * into page props as the API returns — the PDP block used to fetch this in\n * an effect, which meant the server rendered a skeleton and crawlers saw a\n * product page with no product in it. One definition keeps the server-\n * rendered and client-fetched shapes from drifting.\n *\n * Display-only by design: stock is a `soldOut` boolean rather than a raw\n * count, and the charge still prices server-side.\n */\nexport function toPublicProductDetail(\n id: string,\n product: ReturnType<typeof CommerceModel.liftLegacyProduct>,\n): PublicProductDetail {\n return {\n id,\n name: product.name,\n slug: product.slug,\n ...(product.description ? { description: product.description } : {}),\n type: product.type,\n mediaUrls:\n product.mediaUrls ?? (product.imageUrl ? [product.imageUrl] : []),\n options: product.options ?? [],\n variants: product.variants.map((variant) => ({\n id: variant.id,\n ...(variant.options ? { options: variant.options } : {}),\n priceUsd: CommerceModel.variantHasPrice(variant) ? variant.priceUsd : null,\n ...(variant.compareAtPriceUsd\n ? { compareAtPriceUsd: variant.compareAtPriceUsd }\n : {}),\n soldOut:\n variant.inventory != null &&\n Number(variant.inventory) <= 0 &&\n product.oversellPolicy !== 'backorder',\n ...(variant.imageUrl ? { imageUrl: variant.imageUrl } : {}),\n ...(variant.sku ? { sku: variant.sku } : {}),\n })),\n ...(product.tags?.length ? { tags: product.tags } : {}),\n ...(product.subscription ? { subscription: product.subscription } : {}),\n ...(product.subscription && product.subscriptionOptional\n ? { subscriptionOptional: true }\n : {}),\n }\n}\n\n/**\n * Public product detail by slug (AGL-292): everything the PDP block\n * needs — variants with per-option prices and stock booleans, never raw\n * counts. Display-only; the charge still prices server-side.\n */\nexport const productHandler: PluginApiHandler = async (req, res) => {\n const hostId = String(req.query.hostId ?? '')\n const slug = String(req.query.slug ?? '')\n if (!hostId || !slug) {\n return res.status(400).json({ error: 'Missing hostId or slug' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('products')\n .where('slug', '==', slug)\n .limit(1)\n .get()\n const docSnapshot = snapshot.docs[0]\n const raw = docSnapshot?.data() as any\n // A product sold in person only (AGL-4060) has no page online: the same\n // 404 as a draft, so its address says nothing about it.\n if (!CommerceModel.productListedOnline(raw)) {\n return res.status(404).json({ error: 'Unknown product' })\n }\n const product = CommerceModel.liftLegacyProduct(raw)\n const detail = toPublicProductDetail(docSnapshot.id, product)\n res.setHeader(\n 'Cache-Control',\n 'public, s-maxage=60, stale-while-revalidate=300',\n )\n return res.status(200).json({ product: detail })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Product unavailable' })\n }\n}\n"],"names":["CommerceModel","firebaseAdmin","toPublicProductDetail","id","product","name","slug","description","type","mediaUrls","imageUrl","options","variants","map","variant","priceUsd","variantHasPrice","compareAtPriceUsd","soldOut","inventory","Number","oversellPolicy","sku","tags","length","subscription","subscriptionOptional","productHandler","req","res","hostId","String","query","status","json","error","firestore","app","snapshot","collection","doc","where","limit","get","docSnapshot","docs","raw","data","productListedOnline","liftLegacyProduct","detail","setHeader","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/product.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { readStoreDay } from './store-day'\n\nexport interface PublicProductDetail {\n id: string\n name: string\n slug: string\n description?: string\n type: CommerceModel.ProductType\n mediaUrls: string[]\n options: CommerceModel.ProductOption[]\n variants: Array<{\n id: string\n options?: Record<string, string>\n /**\n * `null` for a variant listed before it has a price (AGL-3676): the page\n * says \"Price coming soon\" and offers nothing to buy, as no sale door\n * would sell it.\n */\n priceUsd: number | null\n compareAtPriceUsd?: number\n soldOut: boolean\n imageUrl?: string\n /**\n * Merchant's stock-keeping unit (AGL-686). Carried into the public DTO\n * so the server can put `sku` on the Product structured data — Google\n * Merchant treats it as required for a product offer, and without it a\n * listing is simply ineligible for rich results rather than warned about.\n * It is already public information: it prints on receipts and invoices.\n */\n sku?: string\n /**\n * The buyer may choose the day they will use it (a pass bought ahead,\n * AGL-4043): the page offers an optional date. Absent when it does not.\n */\n asksUseDate?: true\n }>\n /**\n * The store's time zone, sent only when a variant asks for a use day, so\n * the page offers no day that has passed on the store's calendar.\n */\n useDayTimeZone?: string\n tags?: string[]\n /** Recurring billing (AGL-303) so the PDP frames the price. */\n subscription?: { interval: 'month' | 'year'; trialDays?: number }\n /** Buyer may choose one-time OR subscribe (AGL-545). */\n subscriptionOptional?: boolean\n}\n\n/**\n * Map a lifted product onto the public PDP DTO.\n *\n * Extracted (AGL-659) so the site-page resolver can put the SAME payload\n * into page props as the API returns — the PDP block used to fetch this in\n * an effect, which meant the server rendered a skeleton and crawlers saw a\n * product page with no product in it. One definition keeps the server-\n * rendered and client-fetched shapes from drifting.\n *\n * Display-only by design: stock is a `soldOut` boolean rather than a raw\n * count, and the charge still prices server-side.\n */\nexport function toPublicProductDetail(\n id: string,\n product: ReturnType<typeof CommerceModel.liftLegacyProduct>,\n options: { timeZone?: string | null } = {},\n): PublicProductDetail {\n const asksUseDate = productAsksUseDateAnywhere(product)\n return {\n id,\n name: product.name,\n slug: product.slug,\n ...(product.description ? { description: product.description } : {}),\n type: product.type,\n mediaUrls:\n product.mediaUrls ?? (product.imageUrl ? [product.imageUrl] : []),\n options: product.options ?? [],\n variants: product.variants.map((variant) => ({\n id: variant.id,\n ...(variant.options ? { options: variant.options } : {}),\n priceUsd: CommerceModel.variantHasPrice(variant) ? variant.priceUsd : null,\n ...(variant.compareAtPriceUsd\n ? { compareAtPriceUsd: variant.compareAtPriceUsd }\n : {}),\n soldOut:\n variant.inventory != null &&\n Number(variant.inventory) <= 0 &&\n product.oversellPolicy !== 'backorder',\n ...(variant.imageUrl ? { imageUrl: variant.imageUrl } : {}),\n ...(variant.sku ? { sku: variant.sku } : {}),\n ...(CommerceModel.productAsksUseDate(product, variant.id) ? { asksUseDate: true as const } : {}),\n })),\n ...(asksUseDate ? { useDayTimeZone: options.timeZone || 'UTC' } : {}),\n ...(product.tags?.length ? { tags: product.tags } : {}),\n ...(product.subscription ? { subscription: product.subscription } : {}),\n ...(product.subscription && product.subscriptionOptional\n ? { subscriptionOptional: true }\n : {}),\n }\n}\n\n/** Whether any variant of a product asks the buyer for a use day (AGL-4043). */\nexport function productAsksUseDateAnywhere(\n product: Pick<ReturnType<typeof CommerceModel.liftLegacyProduct>, 'variants' | 'checkIn'>,\n): boolean {\n return product.variants.some((variant) => CommerceModel.productAsksUseDate(product, variant.id))\n}\n\n/**\n * Public product detail by slug (AGL-292): everything the PDP block\n * needs — variants with per-option prices and stock booleans, never raw\n * counts. Display-only; the charge still prices server-side.\n */\nexport const productHandler: PluginApiHandler = async (req, res) => {\n const hostId = String(req.query.hostId ?? '')\n const slug = String(req.query.slug ?? '')\n if (!hostId || !slug) {\n return res.status(400).json({ error: 'Missing hostId or slug' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('products')\n .where('slug', '==', slug)\n .limit(1)\n .get()\n const docSnapshot = snapshot.docs[0]\n const raw = docSnapshot?.data() as any\n // A product sold in person only (AGL-4060) has no page online: the same\n // 404 as a draft, so its address says nothing about it.\n if (!CommerceModel.productListedOnline(raw)) {\n return res.status(404).json({ error: 'Unknown product' })\n }\n const product = CommerceModel.liftLegacyProduct(raw)\n // The store's calendar, only for a product that asks for a use day.\n const timeZone = productAsksUseDateAnywhere(product)\n ? (await readStoreDay(firestore.collection('hosts').doc(hostId))).timeZone\n : null\n const detail = toPublicProductDetail(docSnapshot.id, product, { timeZone })\n res.setHeader(\n 'Cache-Control',\n 'public, s-maxage=60, stale-while-revalidate=300',\n )\n return res.status(200).json({ product: detail })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Product unavailable' })\n }\n}\n"],"names":["CommerceModel","firebaseAdmin","readStoreDay","toPublicProductDetail","id","product","options","asksUseDate","productAsksUseDateAnywhere","name","slug","description","type","mediaUrls","imageUrl","variants","map","variant","priceUsd","variantHasPrice","compareAtPriceUsd","soldOut","inventory","Number","oversellPolicy","sku","productAsksUseDate","useDayTimeZone","timeZone","tags","length","subscription","subscriptionOptional","some","productHandler","req","res","hostId","String","query","status","json","error","firestore","app","snapshot","collection","doc","where","limit","get","docSnapshot","docs","raw","data","productListedOnline","liftLegacyProduct","detail","setHeader","console"],"mappings":"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"}
@@ -14,13 +14,15 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ import type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types';
17
18
  import { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
18
19
  import { type PluginApiHandler } from '@aglyn/aglyn/server';
19
20
  import { getLockdownVerdict } from '@aglyn/tenant-data-admin';
20
21
  import { FieldValue } from 'firebase-admin/firestore';
21
22
  import { type InventoryAdjustmentReason } from '../model';
22
23
  type Firestore = FirebaseFirestore.Firestore;
23
- export declare const ROLE_REFUSAL = "Editing requires the editor role";
24
+ /** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */
25
+ export declare const ROLE_REFUSAL: string;
24
26
  /** The reasons the Adjust stock dialog offers. */
25
27
  export declare const STOCK_ADJUST_REASONS: readonly InventoryAdjustmentReason[];
26
28
  export interface ProductsWriteDeps {
@@ -40,15 +42,37 @@ export declare function bodyOf(req: Parameters<PluginApiHandler>[0]): Record<str
40
42
  export declare const validId: (value: unknown) => value is string;
41
43
  export interface Gate {
42
44
  uid: string;
45
+ /** The caller's address, for an activity line; null when the token has none. */
46
+ email: string | null;
43
47
  staff: boolean;
44
48
  host: FirebaseFirestore.DocumentSnapshot;
45
49
  org: Record<string, unknown>;
46
50
  }
51
+ /**
52
+ * The permission a route built on {@link gate} checks instead of the site
53
+ * role (AGL-4047): a key whose preset is the site role, that preset (the
54
+ * declaring plugin's own table, so the answer does not depend on which
55
+ * plugins this process loaded), and the refusal naming it.
56
+ */
57
+ export interface GatePermission {
58
+ key: string;
59
+ presetFor: Readonly<Record<HostAccessRole, boolean>>;
60
+ refusal: string;
61
+ }
47
62
  /**
48
63
  * Who may write this site's products, as the rules decide it. Answers the
49
64
  * caller's reply on a refusal and returns null.
65
+ *
66
+ * A route that is not about products names the key it checks instead
67
+ * (`permission`, AGL-4047): the walk-in and the guests change ask their
68
+ * reservation key, read as the rules read it, from the site's
69
+ * `memberPermissions` stamp, else the preset of the member's `memberRoles`
70
+ * entry, so the route and the rules give one answer. Without it, the
71
+ * product routes ask "Edit site content" (`site.edit`) the same way.
50
72
  */
51
- export declare function gate(req: Parameters<PluginApiHandler>[0], res: Reply, hostId: string, deps: Required<ProductsWriteDeps>): Promise<Gate | null>;
73
+ export declare function gate(req: Parameters<PluginApiHandler>[0], res: Reply, hostId: string, deps: Required<ProductsWriteDeps>, options?: {
74
+ permission?: GatePermission;
75
+ }): Promise<Gate | null>;
52
76
  /** The site-wide cache drop a product change is owed (AGL-3386): an outbox entry in the write, then the drop. */
53
77
  export declare function outboxEntry(firestore: Firestore, hostId: string): {
54
78
  ref: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
@@ -15,16 +15,16 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */ import { createHash } from "node:crypto";
18
+ import { holdsSiteKey, hostStampedSitePermission, siteKeyRefusal } from "@aglyn/aglyn/app-utils/host-permissions";
18
19
  import { isRefusedIdToken } from "@aglyn/tenant-data-admin/server/id-token-refusal";
19
20
  import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
20
- import { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
21
21
  import { countLiveDocuments } from "@aglyn/aglyn/app-utils/live-document-count";
22
22
  import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
23
23
  import { firebaseAdmin, getLockdownVerdict, getOrgForHost, isImpersonationSession } from "@aglyn/tenant-data-admin";
24
24
  import { FieldValue } from "firebase-admin/firestore";
25
25
  import { liftLegacyProduct, productCollectionIds, productDuplicateFields, productDuplicateName, productDuplicateSlugBase, productEditWrite, productMembershipInput, productSaveFields, smartCollectionRulesOf, stockAdjustmentWrite, validateProduct } from "../model/index.js";
26
26
  import { planRefusal, roomRefusal } from "./product-drafts.js";
27
- export const ROLE_REFUSAL = 'Editing requires the editor role';
27
+ /** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */ export const ROLE_REFUSAL = siteKeyRefusal('site.edit');
28
28
  const MAX_BODY_BYTES = 256000;
29
29
  /** The reasons the Adjust stock dialog offers. */ export const STOCK_ADJUST_REASONS = [
30
30
  'restock',
@@ -48,8 +48,15 @@ export const validId = (value)=>typeof value === 'string' && /^[A-Za-z0-9_-]{1,1
48
48
  /**
49
49
  * Who may write this site's products, as the rules decide it. Answers the
50
50
  * caller's reply on a refusal and returns null.
51
- */ export async function gate(req, res, hostId, deps) {
52
- var _req_headers_authorization, _host_get, _ref;
51
+ *
52
+ * A route that is not about products names the key it checks instead
53
+ * (`permission`, AGL-4047): the walk-in and the guests change ask their
54
+ * reservation key, read as the rules read it, from the site's
55
+ * `memberPermissions` stamp, else the preset of the member's `memberRoles`
56
+ * entry, so the route and the rules give one answer. Without it, the
57
+ * product routes ask "Edit site content" (`site.edit`) the same way.
58
+ */ export async function gate(req, res, hostId, deps, options = {}) {
59
+ var _req_headers_authorization, _ref;
53
60
  var _this;
54
61
  const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
55
62
  const token = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : '';
@@ -76,9 +83,12 @@ export const validId = (value)=>typeof value === 'string' && /^[A-Za-z0-9_-]{1,1
76
83
  if (!host.exists) return res.status(404).json({
77
84
  error: 'Unknown site'
78
85
  }), null;
79
- if (!staff && !hostRoleCanWrite(((_host_get = host.get('memberRoles')) != null ? _host_get : {})[decoded.uid])) {
80
- return res.status(403).json({
81
- error: ROLE_REFUSAL
86
+ if (!staff) {
87
+ var _ref1;
88
+ const { permission } = options;
89
+ const allowed = permission ? hostStampedSitePermission(host.data(), decoded.uid, permission.key, permission.presetFor) : holdsSiteKey(host, decoded.uid, 'site.edit');
90
+ if (!allowed) return res.status(403).json({
91
+ error: (_ref1 = permission == null ? void 0 : permission.refusal) != null ? _ref1 : ROLE_REFUSAL
82
92
  }), null;
83
93
  }
84
94
  const org = (_ref = (_this = await deps.orgForHost(hostId)) == null ? void 0 : _this.org) != null ? _ref : {};
@@ -94,8 +104,10 @@ export const validId = (value)=>typeof value === 'string' && /^[A-Za-z0-9_-]{1,1
94
104
  if (locked) return res.status(423).json({
95
105
  error: 'This site is not accepting changes right now'
96
106
  }), null;
107
+ const email = typeof decoded['email'] === 'string' ? decoded['email'] : null;
97
108
  return {
98
109
  uid: decoded.uid,
110
+ email,
99
111
  staff,
100
112
  host,
101
113
  org
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/products-write.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { countLiveDocuments } from '@aglyn/aglyn/app-utils/live-document-count'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getLockdownVerdict, getOrgForHost, isImpersonationSession } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n type EditedProduct,\n liftLegacyProduct,\n productCollectionIds,\n productDuplicateFields,\n productDuplicateName,\n productDuplicateSlugBase,\n productEditWrite,\n productMembershipInput,\n productSaveFields,\n smartCollectionRulesOf,\n stockAdjustmentWrite,\n validateProduct,\n type InventoryAdjustmentReason,\n} from '../model'\nimport { planRefusal, roomRefusal } from './product-drafts'\n\n/*\n * `POST /api/commerce/products/save` and `POST /api/commerce/products/stock`\n * (AGL-3652): the console product editor's save and its Adjust stock dialog,\n * for the native apps.\n *\n * The console makes both writes from the browser, computing derived fields\n * first (search keys, stock verdict, price, image, smart-collection\n * membership). These routes run THAT computation (`model/product-write.ts`,\n * which the console imports too) and make the same writes, under the gate\n * the Firestore rules put on them: a verified member with a write role on the\n * site (`canWriteHostContent`), or staff, while the site, its org and the\n * platform are not frozen (`hostWritesFrozen`). A new product also meets the\n * create path's own door: the plan's `commerce` entitlement and its\n * `productsPerHost` quota, counted in the same transaction as the create.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const ROLE_REFUSAL = 'Editing requires the editor role'\nconst MAX_BODY_BYTES = 256_000\n\n/** The reasons the Adjust stock dialog offers. */\nexport const STOCK_ADJUST_REASONS: readonly InventoryAdjustmentReason[] = ['restock', 'correction', 'damage', 'refund']\n\nexport interface ProductsWriteDeps {\n firestore?: () => Firestore\n verifyIdToken?: (token: string) => Promise<Record<string, unknown> & { uid: string }>\n orgForHost?: (hostId: string) => Promise<{ org: Record<string, unknown> } | null>\n lockdown?: (options: Parameters<typeof getLockdownVerdict>[0]) => Promise<unknown>\n dropCache?: typeof dropPluginSiteCache\n now?: () => number\n}\n\ntype Reply = Parameters<PluginApiHandler>[1]\n\nexport function bodyOf(req: Parameters<PluginApiHandler>[0]): Record<string, unknown> {\n const raw = req.body\n if (typeof raw === 'string') {\n if (raw.length > MAX_BODY_BYTES) return {}\n try {\n return JSON.parse(raw || '{}')\n } catch {\n return {}\n }\n }\n return (raw ?? {}) as Record<string, unknown>\n}\n\nexport const validId = (value: unknown): value is string =>\n typeof value === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(value)\n\nexport interface Gate {\n uid: string\n staff: boolean\n host: FirebaseFirestore.DocumentSnapshot\n org: Record<string, unknown>\n}\n\n/**\n * Who may write this site's products, as the rules decide it. Answers the\n * caller's reply on a refusal and returns null.\n */\nexport async function gate(req: Parameters<PluginApiHandler>[0], res: Reply, hostId: string, deps: Required<ProductsWriteDeps>): Promise<Gate | null> {\n const authorization = String(req.headers.authorization ?? '')\n const token = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : ''\n if (!token) return void res.status(401).json({ error: 'Unauthenticated' }), null\n let decoded: Record<string, unknown> & { uid: string }\n try {\n decoded = await deps.verifyIdToken(token)\n } catch (error) {\n // Only a refused token is the caller's; an Auth outage is a 500 (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return void res.status(401).json({ error: 'Sign in again to continue' }), null\n }\n const staff = decoded['staff'] === true\n if (!staff && decoded['email_verified'] !== true && !isImpersonationSession(decoded as never)) {\n return void res.status(403).json({ error: 'Verify your email address to make changes' }), null\n }\n const host = await deps.firestore().collection('hosts').doc(hostId).get()\n if (!host.exists) return void res.status(404).json({ error: 'Unknown site' }), null\n if (!staff && !hostRoleCanWrite((host.get('memberRoles') ?? {})[decoded.uid])) {\n return void res.status(403).json({ error: ROLE_REFUSAL }), null\n }\n const org = ((await deps.orgForHost(hostId))?.org ?? {}) as Record<string, unknown>\n const locked = await deps.lockdown({ staff, uid: decoded.uid, org: org as never, host: host.data() as never, request: { method: 'POST' } as never })\n if (locked) return void res.status(423).json({ error: 'This site is not accepting changes right now' }), null\n return { uid: decoded.uid, staff, host, org }\n}\n\n/** The site-wide cache drop a product change is owed (AGL-3386): an outbox entry in the write, then the drop. */\nexport function outboxEntry(firestore: Firestore, hostId: string) {\n return {\n ref: firestore.collection('publishOutbox').doc(createResourceUid()),\n data: { hostId, paths: ['/'], createdAt: FieldValue.serverTimestamp(), attempts: 0, entireHost: true },\n }\n}\n\nexport async function settleCache(deps: Required<ProductsWriteDeps>, hostId: string, entry: FirebaseFirestore.DocumentReference, reason: string) {\n const dropped = await deps.dropCache({ hostIds: [hostId], reason }).catch(() => ({ complete: false }))\n if (dropped.complete) await entry.delete().catch(() => undefined)\n}\n\nexport function resolved(deps: ProductsWriteDeps): Required<ProductsWriteDeps> {\n return {\n firestore: deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore),\n verifyIdToken: deps.verifyIdToken ?? ((token) => firebaseAdmin.app().auth().verifyIdToken(token) as never),\n orgForHost: deps.orgForHost ?? ((hostId) => getOrgForHost(hostId) as never),\n lockdown: deps.lockdown ?? ((options) => getLockdownVerdict(options)),\n dropCache: deps.dropCache ?? dropPluginSiteCache,\n now: deps.now ?? (() => Date.now()),\n }\n}\n\n/**\n * Saves a product as the console editor does. `create: true` makes a new\n * product under the client-minted `productId` (a retry finds its own product\n * and answers `replayed`); otherwise it replaces the live product, keeping\n * its live stock holds.\n */\nexport function createProductSaveHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const product = body['product'] as EditedProduct | undefined\n if (!validId(hostId) || !validId(productId) || !product || typeof product !== 'object' || !Array.isArray(product.variants)) {\n return res.status(400).json({ error: 'Missing site, product or its variants' })\n }\n if (typeof product.name !== 'string' || !product.name.trim()) return res.status(400).json({ error: 'Product name is required' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const nowMs = deps.now()\n const fields = productSaveFields(product, nowMs)\n const problem = validateProduct(fields as never)\n if (problem) return res.status(400).json({ error: problem })\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const products = hostRef.collection('products')\n const productRef = products.doc(productId)\n const smartQuery = hostRef.collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart')\n const outbox = outboxEntry(db, hostId)\n const create = body['create'] === true\n const answer = await db.runTransaction(async (tx) => {\n const [existing, smart] = await Promise.all([tx.get(productRef), tx.get(smartQuery)])\n const membership = {\n collectionIds: productCollectionIds(\n productMembershipInput(fields, product) as never,\n smart.docs.map((doc) => smartCollectionRulesOf(doc.id, doc.data())),\n ),\n }\n if (create) {\n if (existing.exists) return { status: 200, body: { ok: true, id: productId, replayed: true } }\n const early = planRefusal(door.org as never)\n if (early) return { status: early.status, body: { error: early.error } }\n const used = await countLiveDocuments(products, (aggregate) => tx.get(aggregate))\n const room = roomRefusal(door.org as never, Number(used) || 0)\n if (room) return { status: room.status, body: { error: room.error } }\n tx.create(productRef, {\n ...fields,\n ...membership,\n createdAtMs: nowMs,\n deletedAt: null,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n createdBy: door.uid,\n })\n } else {\n if (!existing.exists || existing.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n tx.set(productRef, {\n ...storedTimestamps(existing.data() ?? {}, product as unknown as Record<string, unknown>),\n ...productEditWrite(fields, existing.get('stockHolds')),\n ...membership,\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, id: productId, replayed: false } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'product saved from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/save]', error)\n return res.status(500).json({ error: 'The product could not be saved' })\n }\n }\n}\n\n/**\n * The stored timestamps an edit keeps. The console's replace writes back the\n * Timestamps its seed carried (`createdAt`, a publish time); a client's JSON\n * cannot carry one, so each stored Timestamp field the request does not name\n * is kept as stored.\n */\nexport function storedTimestamps(stored: Record<string, unknown>, asked: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(stored).filter(\n ([key, value]) => !(key in asked) && typeof (value as { toDate?: unknown } | null)?.toDate === 'function',\n ),\n )\n}\n\n/** The ledger row id one attempt owns: the same key from the same member is the same row. */\nexport function stockAttemptId(uid: string, key: string): string {\n return 'app-' + createHash('sha256').update(`${uid}:${key}`).digest('hex').slice(0, 40)\n}\n\n/**\n * Adjusts one variant's stock as the Adjust stock dialog does: the variants\n * and stock fields on the product, and one `inventoryAdjustments` row, in one\n * transaction. The Idempotency-Key names that row, so a retried attempt finds\n * it and moves nothing twice.\n */\nexport function createProductStockHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const variantId = body['variantId']\n const key = String(req.headers['idempotency-key'] ?? '').trim().slice(0, 200)\n const reason = body['reason'] as InventoryAdjustmentReason\n const delta = Math.round(Number(body['delta']))\n const locationId = typeof body['locationId'] === 'string' && body['locationId'] ? body['locationId'] : undefined\n if (!validId(hostId) || !validId(productId) || typeof variantId !== 'string' || !variantId) {\n return res.status(400).json({ error: 'Missing site, product or variant' })\n }\n if (!key) return res.status(400).json({ error: 'Missing Idempotency-Key' })\n if (!STOCK_ADJUST_REASONS.includes(reason)) return res.status(400).json({ error: 'Unknown reason' })\n if (!Number.isFinite(delta) || delta === 0) return res.status(400).json({ error: 'Enter a number of units' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const productRef = hostRef.collection('products').doc(productId)\n const ledgerRef = hostRef.collection('inventoryAdjustments').doc(stockAttemptId(door.uid, key))\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const [ledger, stored] = await Promise.all([tx.get(ledgerRef), tx.get(productRef)])\n if (ledger.exists) {\n return { status: 200, body: { ok: true, replayed: true, inventory: stored.get('inventory') ?? null } }\n }\n if (!stored.exists || stored.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n const product = stored.data() as never as Parameters<typeof stockAdjustmentWrite>[0]\n if (!(product.variants ?? []).some((variant) => variant.id === variantId)) {\n return { status: 404, body: { error: 'That variant is no longer on this product' } }\n }\n const write = stockAdjustmentWrite(product, productId, { variantId, delta, reason, locationId }, deps.now())\n if (!write) return { status: 400, body: { error: 'Enter a number of units' } }\n tx.update(productRef, write.update)\n tx.create(ledgerRef, write.ledger)\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, replayed: false, inventory: (write.update as { inventory?: unknown }).inventory ?? null } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'stock adjusted from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/stock]', error)\n return res.status(500).json({ error: 'Stock could not be adjusted' })\n }\n }\n}\n\n/**\n * The first slug in `base`, `base-2`, `base-3`… no product of the site holds,\n * as the console's slug ledger claims one (`product-slugs.ts`): a second\n * Duplicate of the same product does not mint a second `-copy` (AGL-3321).\n */\nexport async function claimProductSlug(products: FirebaseFirestore.CollectionReference, base: string): Promise<string> {\n for (let suffix = 1; suffix < 1000; suffix += 1) {\n const candidate = suffix < 2 ? base : `${base}-${suffix}`\n const held = await products.where('slug', '==', candidate).limit(1).get()\n if (held.empty) return candidate\n }\n return `${base}-${Date.now()}`\n}\n\n/**\n * `POST /api/commerce/products/duplicate` (AGL-3841): the products list's\n * Duplicate for the native apps. The console builds the copy in the browser\n * (`productDuplicateFields`, its slug ledger, the copy's smart collections)\n * and creates it through the quota-enforcing resources route; this runs the\n * same model over the stored product and creates it under the save route's\n * create door (the plan's commerce entitlement and its product quota, counted\n * in the same transaction). `newProductId` is the client's, so a retry finds\n * its own copy and answers `replayed`.\n */\nexport function createProductDuplicateHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, productId, newProductId } = body\n if (!validId(hostId) || !validId(productId) || !validId(newProductId) || productId === newProductId) {\n return res.status(400).json({ error: 'Missing site or product' })\n }\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const products = hostRef.collection('products')\n const copyRef = products.doc(newProductId)\n const already = await copyRef.get()\n if (already.exists) return res.status(200).json({ ok: true, id: newProductId, replayed: true })\n const source = await products.doc(productId).get()\n if (!source.exists || source.get('deletedAt')) return res.status(404).json({ error: 'This product was deleted' })\n const product = liftLegacyProduct((source.data() ?? {}) as never)\n const slug = await claimProductSlug(products, productDuplicateSlugBase(product.slug, product.name))\n const nowMs = deps.now()\n const fields = productDuplicateFields(product, slug, nowMs)\n const smartQuery = hostRef.collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart')\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const [existing, smart] = await Promise.all([tx.get(copyRef), tx.get(smartQuery)])\n if (existing.exists) return { status: 200, body: { ok: true, id: newProductId, replayed: true } }\n const early = planRefusal(door.org as never)\n if (early) return { status: early.status, body: { error: early.error } }\n const used = await countLiveDocuments(products, (aggregate) => tx.get(aggregate))\n const room = roomRefusal(door.org as never, Number(used) || 0)\n if (room) return { status: room.status, body: { error: room.error } }\n // The COPY's smart collections: its name differs from the source's.\n const collectionIds = productCollectionIds(\n productMembershipInput({ name: productDuplicateName(product.name) }, product) as never,\n smart.docs.map((doc) => smartCollectionRulesOf(doc.id, doc.data())),\n )\n tx.create(copyRef, {\n ...fields,\n collectionIds,\n deletedAt: null,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n createdBy: door.uid,\n })\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, id: newProductId, replayed: false, slug } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'product duplicated from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/duplicate]', error)\n return res.status(500).json({ error: 'Could not duplicate product' })\n }\n }\n}\n\n/**\n * `POST /api/commerce/products/delete` (AGL-3841): the products list's Delete\n * for the native apps, the console's write (`deletedAt` on the product, which\n * stops it being purchasable) with the site-wide cache drop, under the save\n * route's gate. Deleting a deleted product answers `already`.\n */\nexport function createProductDeleteHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, productId } = body\n if (!validId(hostId) || !validId(productId)) return res.status(400).json({ error: 'Missing site or product' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const productRef = db.collection('hosts').doc(hostId).collection('products').doc(productId)\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const stored = await tx.get(productRef)\n if (!stored.exists) return { status: 404, body: { error: 'This product no longer exists' } }\n if (stored.get('deletedAt')) return { status: 200, body: { ok: true, already: true } }\n tx.update(productRef, { deletedAt: FieldValue.serverTimestamp() })\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, already: false } }\n })\n if (answer.status === 200 && !(answer.body as { already?: boolean }).already) {\n await settleCache(deps, hostId, outbox.ref, 'product deleted from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/delete]', error)\n return res.status(500).json({ error: 'Could not delete product' })\n }\n }\n}\n\nexport const productSaveHandler = createProductSaveHandler()\nexport const productStockHandler = createProductStockHandler()\nexport const productDuplicateHandler = createProductDuplicateHandler()\nexport const productDeleteHandler = createProductDeleteHandler()\n"],"names":["createHash","isRefusedIdToken","createResourceUid","hostRoleCanWrite","countLiveDocuments","dropPluginSiteCache","firebaseAdmin","getLockdownVerdict","getOrgForHost","isImpersonationSession","FieldValue","liftLegacyProduct","productCollectionIds","productDuplicateFields","productDuplicateName","productDuplicateSlugBase","productEditWrite","productMembershipInput","productSaveFields","smartCollectionRulesOf","stockAdjustmentWrite","validateProduct","planRefusal","roomRefusal","ROLE_REFUSAL","MAX_BODY_BYTES","STOCK_ADJUST_REASONS","bodyOf","req","raw","body","length","JSON","parse","validId","value","test","gate","res","hostId","deps","host","authorization","String","headers","token","startsWith","slice","status","json","error","decoded","verifyIdToken","staff","firestore","collection","doc","get","exists","uid","org","orgForHost","locked","lockdown","data","request","method","outboxEntry","ref","paths","createdAt","serverTimestamp","attempts","entireHost","settleCache","entry","reason","dropped","dropCache","hostIds","catch","complete","delete","undefined","resolved","app","auth","options","now","Date","createProductSaveHandler","productId","product","Array","isArray","variants","name","trim","door","nowMs","fields","problem","db","hostRef","products","productRef","smartQuery","where","outbox","create","answer","runTransaction","tx","existing","smart","Promise","all","membership","collectionIds","docs","map","id","ok","replayed","early","used","aggregate","room","Number","createdAtMs","deletedAt","updatedAt","createdBy","set","storedTimestamps","console","stored","asked","Object","fromEntries","entries","filter","key","toDate","stockAttemptId","update","digest","createProductStockHandler","variantId","delta","Math","round","locationId","includes","isFinite","ledgerRef","ledger","inventory","some","variant","write","claimProductSlug","base","suffix","candidate","held","limit","empty","createProductDuplicateHandler","newProductId","source","copyRef","already","slug","createProductDeleteHandler","productSaveHandler","productStockHandler","productDuplicateHandler","productDeleteHandler"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/products-write.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport { holdsSiteKey, hostStampedSitePermission, siteKeyRefusal } from '@aglyn/aglyn/app-utils/host-permissions'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport { countLiveDocuments } from '@aglyn/aglyn/app-utils/live-document-count'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getLockdownVerdict, getOrgForHost, isImpersonationSession } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n type EditedProduct,\n liftLegacyProduct,\n productCollectionIds,\n productDuplicateFields,\n productDuplicateName,\n productDuplicateSlugBase,\n productEditWrite,\n productMembershipInput,\n productSaveFields,\n smartCollectionRulesOf,\n stockAdjustmentWrite,\n validateProduct,\n type InventoryAdjustmentReason,\n} from '../model'\nimport { planRefusal, roomRefusal } from './product-drafts'\n\n/*\n * `POST /api/commerce/products/save` and `POST /api/commerce/products/stock`\n * (AGL-3652): the console product editor's save and its Adjust stock dialog,\n * for the native apps.\n *\n * The console makes both writes from the browser, computing derived fields\n * first (search keys, stock verdict, price, image, smart-collection\n * membership). These routes run THAT computation (`model/product-write.ts`,\n * which the console imports too) and make the same writes, under the gate\n * the Firestore rules put on them: a verified member with a write role on the\n * site (`canWriteHostContent`), or staff, while the site, its org and the\n * platform are not frozen (`hostWritesFrozen`). A new product also meets the\n * create path's own door: the plan's `commerce` entitlement and its\n * `productsPerHost` quota, counted in the same transaction as the create.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** Names \"Edit site content\" (`site.edit`, AGL-4047), never a role. */\nexport const ROLE_REFUSAL = siteKeyRefusal('site.edit')\nconst MAX_BODY_BYTES = 256_000\n\n/** The reasons the Adjust stock dialog offers. */\nexport const STOCK_ADJUST_REASONS: readonly InventoryAdjustmentReason[] = ['restock', 'correction', 'damage', 'refund']\n\nexport interface ProductsWriteDeps {\n firestore?: () => Firestore\n verifyIdToken?: (token: string) => Promise<Record<string, unknown> & { uid: string }>\n orgForHost?: (hostId: string) => Promise<{ org: Record<string, unknown> } | null>\n lockdown?: (options: Parameters<typeof getLockdownVerdict>[0]) => Promise<unknown>\n dropCache?: typeof dropPluginSiteCache\n now?: () => number\n}\n\ntype Reply = Parameters<PluginApiHandler>[1]\n\nexport function bodyOf(req: Parameters<PluginApiHandler>[0]): Record<string, unknown> {\n const raw = req.body\n if (typeof raw === 'string') {\n if (raw.length > MAX_BODY_BYTES) return {}\n try {\n return JSON.parse(raw || '{}')\n } catch {\n return {}\n }\n }\n return (raw ?? {}) as Record<string, unknown>\n}\n\nexport const validId = (value: unknown): value is string =>\n typeof value === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(value)\n\nexport interface Gate {\n uid: string\n /** The caller's address, for an activity line; null when the token has none. */\n email: string | null\n staff: boolean\n host: FirebaseFirestore.DocumentSnapshot\n org: Record<string, unknown>\n}\n\n/**\n * The permission a route built on {@link gate} checks instead of the site\n * role (AGL-4047): a key whose preset is the site role, that preset (the\n * declaring plugin's own table, so the answer does not depend on which\n * plugins this process loaded), and the refusal naming it.\n */\nexport interface GatePermission {\n key: string\n presetFor: Readonly<Record<HostAccessRole, boolean>>\n refusal: string\n}\n\n/**\n * Who may write this site's products, as the rules decide it. Answers the\n * caller's reply on a refusal and returns null.\n *\n * A route that is not about products names the key it checks instead\n * (`permission`, AGL-4047): the walk-in and the guests change ask their\n * reservation key, read as the rules read it, from the site's\n * `memberPermissions` stamp, else the preset of the member's `memberRoles`\n * entry, so the route and the rules give one answer. Without it, the\n * product routes ask \"Edit site content\" (`site.edit`) the same way.\n */\nexport async function gate(\n req: Parameters<PluginApiHandler>[0],\n res: Reply,\n hostId: string,\n deps: Required<ProductsWriteDeps>,\n options: { permission?: GatePermission } = {},\n): Promise<Gate | null> {\n const authorization = String(req.headers.authorization ?? '')\n const token = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : ''\n if (!token) return void res.status(401).json({ error: 'Unauthenticated' }), null\n let decoded: Record<string, unknown> & { uid: string }\n try {\n decoded = await deps.verifyIdToken(token)\n } catch (error) {\n // Only a refused token is the caller's; an Auth outage is a 500 (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return void res.status(401).json({ error: 'Sign in again to continue' }), null\n }\n const staff = decoded['staff'] === true\n if (!staff && decoded['email_verified'] !== true && !isImpersonationSession(decoded as never)) {\n return void res.status(403).json({ error: 'Verify your email address to make changes' }), null\n }\n const host = await deps.firestore().collection('hosts').doc(hostId).get()\n if (!host.exists) return void res.status(404).json({ error: 'Unknown site' }), null\n if (!staff) {\n const { permission } = options\n const allowed = permission\n ? hostStampedSitePermission(host.data(), decoded.uid, permission.key, permission.presetFor)\n : holdsSiteKey(host, decoded.uid, 'site.edit')\n if (!allowed) return void res.status(403).json({ error: permission?.refusal ?? ROLE_REFUSAL }), null\n }\n const org = ((await deps.orgForHost(hostId))?.org ?? {}) as Record<string, unknown>\n const locked = await deps.lockdown({ staff, uid: decoded.uid, org: org as never, host: host.data() as never, request: { method: 'POST' } as never })\n if (locked) return void res.status(423).json({ error: 'This site is not accepting changes right now' }), null\n const email = typeof decoded['email'] === 'string' ? decoded['email'] : null\n return { uid: decoded.uid, email, staff, host, org }\n}\n\n/** The site-wide cache drop a product change is owed (AGL-3386): an outbox entry in the write, then the drop. */\nexport function outboxEntry(firestore: Firestore, hostId: string) {\n return {\n ref: firestore.collection('publishOutbox').doc(createResourceUid()),\n data: { hostId, paths: ['/'], createdAt: FieldValue.serverTimestamp(), attempts: 0, entireHost: true },\n }\n}\n\nexport async function settleCache(deps: Required<ProductsWriteDeps>, hostId: string, entry: FirebaseFirestore.DocumentReference, reason: string) {\n const dropped = await deps.dropCache({ hostIds: [hostId], reason }).catch(() => ({ complete: false }))\n if (dropped.complete) await entry.delete().catch(() => undefined)\n}\n\nexport function resolved(deps: ProductsWriteDeps): Required<ProductsWriteDeps> {\n return {\n firestore: deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore),\n verifyIdToken: deps.verifyIdToken ?? ((token) => firebaseAdmin.app().auth().verifyIdToken(token) as never),\n orgForHost: deps.orgForHost ?? ((hostId) => getOrgForHost(hostId) as never),\n lockdown: deps.lockdown ?? ((options) => getLockdownVerdict(options)),\n dropCache: deps.dropCache ?? dropPluginSiteCache,\n now: deps.now ?? (() => Date.now()),\n }\n}\n\n/**\n * Saves a product as the console editor does. `create: true` makes a new\n * product under the client-minted `productId` (a retry finds its own product\n * and answers `replayed`); otherwise it replaces the live product, keeping\n * its live stock holds.\n */\nexport function createProductSaveHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const product = body['product'] as EditedProduct | undefined\n if (!validId(hostId) || !validId(productId) || !product || typeof product !== 'object' || !Array.isArray(product.variants)) {\n return res.status(400).json({ error: 'Missing site, product or its variants' })\n }\n if (typeof product.name !== 'string' || !product.name.trim()) return res.status(400).json({ error: 'Product name is required' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const nowMs = deps.now()\n const fields = productSaveFields(product, nowMs)\n const problem = validateProduct(fields as never)\n if (problem) return res.status(400).json({ error: problem })\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const products = hostRef.collection('products')\n const productRef = products.doc(productId)\n const smartQuery = hostRef.collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart')\n const outbox = outboxEntry(db, hostId)\n const create = body['create'] === true\n const answer = await db.runTransaction(async (tx) => {\n const [existing, smart] = await Promise.all([tx.get(productRef), tx.get(smartQuery)])\n const membership = {\n collectionIds: productCollectionIds(\n productMembershipInput(fields, product) as never,\n smart.docs.map((doc) => smartCollectionRulesOf(doc.id, doc.data())),\n ),\n }\n if (create) {\n if (existing.exists) return { status: 200, body: { ok: true, id: productId, replayed: true } }\n const early = planRefusal(door.org as never)\n if (early) return { status: early.status, body: { error: early.error } }\n const used = await countLiveDocuments(products, (aggregate) => tx.get(aggregate))\n const room = roomRefusal(door.org as never, Number(used) || 0)\n if (room) return { status: room.status, body: { error: room.error } }\n tx.create(productRef, {\n ...fields,\n ...membership,\n createdAtMs: nowMs,\n deletedAt: null,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n createdBy: door.uid,\n })\n } else {\n if (!existing.exists || existing.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n tx.set(productRef, {\n ...storedTimestamps(existing.data() ?? {}, product as unknown as Record<string, unknown>),\n ...productEditWrite(fields, existing.get('stockHolds')),\n ...membership,\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, id: productId, replayed: false } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'product saved from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/save]', error)\n return res.status(500).json({ error: 'The product could not be saved' })\n }\n }\n}\n\n/**\n * The stored timestamps an edit keeps. The console's replace writes back the\n * Timestamps its seed carried (`createdAt`, a publish time); a client's JSON\n * cannot carry one, so each stored Timestamp field the request does not name\n * is kept as stored.\n */\nexport function storedTimestamps(stored: Record<string, unknown>, asked: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(stored).filter(\n ([key, value]) => !(key in asked) && typeof (value as { toDate?: unknown } | null)?.toDate === 'function',\n ),\n )\n}\n\n/** The ledger row id one attempt owns: the same key from the same member is the same row. */\nexport function stockAttemptId(uid: string, key: string): string {\n return 'app-' + createHash('sha256').update(`${uid}:${key}`).digest('hex').slice(0, 40)\n}\n\n/**\n * Adjusts one variant's stock as the Adjust stock dialog does: the variants\n * and stock fields on the product, and one `inventoryAdjustments` row, in one\n * transaction. The Idempotency-Key names that row, so a retried attempt finds\n * it and moves nothing twice.\n */\nexport function createProductStockHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const variantId = body['variantId']\n const key = String(req.headers['idempotency-key'] ?? '').trim().slice(0, 200)\n const reason = body['reason'] as InventoryAdjustmentReason\n const delta = Math.round(Number(body['delta']))\n const locationId = typeof body['locationId'] === 'string' && body['locationId'] ? body['locationId'] : undefined\n if (!validId(hostId) || !validId(productId) || typeof variantId !== 'string' || !variantId) {\n return res.status(400).json({ error: 'Missing site, product or variant' })\n }\n if (!key) return res.status(400).json({ error: 'Missing Idempotency-Key' })\n if (!STOCK_ADJUST_REASONS.includes(reason)) return res.status(400).json({ error: 'Unknown reason' })\n if (!Number.isFinite(delta) || delta === 0) return res.status(400).json({ error: 'Enter a number of units' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const productRef = hostRef.collection('products').doc(productId)\n const ledgerRef = hostRef.collection('inventoryAdjustments').doc(stockAttemptId(door.uid, key))\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const [ledger, stored] = await Promise.all([tx.get(ledgerRef), tx.get(productRef)])\n if (ledger.exists) {\n return { status: 200, body: { ok: true, replayed: true, inventory: stored.get('inventory') ?? null } }\n }\n if (!stored.exists || stored.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n const product = stored.data() as never as Parameters<typeof stockAdjustmentWrite>[0]\n if (!(product.variants ?? []).some((variant) => variant.id === variantId)) {\n return { status: 404, body: { error: 'That variant is no longer on this product' } }\n }\n const write = stockAdjustmentWrite(product, productId, { variantId, delta, reason, locationId }, deps.now())\n if (!write) return { status: 400, body: { error: 'Enter a number of units' } }\n tx.update(productRef, write.update)\n tx.create(ledgerRef, write.ledger)\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, replayed: false, inventory: (write.update as { inventory?: unknown }).inventory ?? null } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'stock adjusted from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/stock]', error)\n return res.status(500).json({ error: 'Stock could not be adjusted' })\n }\n }\n}\n\n/**\n * The first slug in `base`, `base-2`, `base-3`… no product of the site holds,\n * as the console's slug ledger claims one (`product-slugs.ts`): a second\n * Duplicate of the same product does not mint a second `-copy` (AGL-3321).\n */\nexport async function claimProductSlug(products: FirebaseFirestore.CollectionReference, base: string): Promise<string> {\n for (let suffix = 1; suffix < 1000; suffix += 1) {\n const candidate = suffix < 2 ? base : `${base}-${suffix}`\n const held = await products.where('slug', '==', candidate).limit(1).get()\n if (held.empty) return candidate\n }\n return `${base}-${Date.now()}`\n}\n\n/**\n * `POST /api/commerce/products/duplicate` (AGL-3841): the products list's\n * Duplicate for the native apps. The console builds the copy in the browser\n * (`productDuplicateFields`, its slug ledger, the copy's smart collections)\n * and creates it through the quota-enforcing resources route; this runs the\n * same model over the stored product and creates it under the save route's\n * create door (the plan's commerce entitlement and its product quota, counted\n * in the same transaction). `newProductId` is the client's, so a retry finds\n * its own copy and answers `replayed`.\n */\nexport function createProductDuplicateHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, productId, newProductId } = body\n if (!validId(hostId) || !validId(productId) || !validId(newProductId) || productId === newProductId) {\n return res.status(400).json({ error: 'Missing site or product' })\n }\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const products = hostRef.collection('products')\n const copyRef = products.doc(newProductId)\n const already = await copyRef.get()\n if (already.exists) return res.status(200).json({ ok: true, id: newProductId, replayed: true })\n const source = await products.doc(productId).get()\n if (!source.exists || source.get('deletedAt')) return res.status(404).json({ error: 'This product was deleted' })\n const product = liftLegacyProduct((source.data() ?? {}) as never)\n const slug = await claimProductSlug(products, productDuplicateSlugBase(product.slug, product.name))\n const nowMs = deps.now()\n const fields = productDuplicateFields(product, slug, nowMs)\n const smartQuery = hostRef.collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart')\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const [existing, smart] = await Promise.all([tx.get(copyRef), tx.get(smartQuery)])\n if (existing.exists) return { status: 200, body: { ok: true, id: newProductId, replayed: true } }\n const early = planRefusal(door.org as never)\n if (early) return { status: early.status, body: { error: early.error } }\n const used = await countLiveDocuments(products, (aggregate) => tx.get(aggregate))\n const room = roomRefusal(door.org as never, Number(used) || 0)\n if (room) return { status: room.status, body: { error: room.error } }\n // The COPY's smart collections: its name differs from the source's.\n const collectionIds = productCollectionIds(\n productMembershipInput({ name: productDuplicateName(product.name) }, product) as never,\n smart.docs.map((doc) => smartCollectionRulesOf(doc.id, doc.data())),\n )\n tx.create(copyRef, {\n ...fields,\n collectionIds,\n deletedAt: null,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n createdBy: door.uid,\n })\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, id: newProductId, replayed: false, slug } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'product duplicated from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/duplicate]', error)\n return res.status(500).json({ error: 'Could not duplicate product' })\n }\n }\n}\n\n/**\n * `POST /api/commerce/products/delete` (AGL-3841): the products list's Delete\n * for the native apps, the console's write (`deletedAt` on the product, which\n * stops it being purchasable) with the site-wide cache drop, under the save\n * route's gate. Deleting a deleted product answers `already`.\n */\nexport function createProductDeleteHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, productId } = body\n if (!validId(hostId) || !validId(productId)) return res.status(400).json({ error: 'Missing site or product' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const productRef = db.collection('hosts').doc(hostId).collection('products').doc(productId)\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const stored = await tx.get(productRef)\n if (!stored.exists) return { status: 404, body: { error: 'This product no longer exists' } }\n if (stored.get('deletedAt')) return { status: 200, body: { ok: true, already: true } }\n tx.update(productRef, { deletedAt: FieldValue.serverTimestamp() })\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, already: false } }\n })\n if (answer.status === 200 && !(answer.body as { already?: boolean }).already) {\n await settleCache(deps, hostId, outbox.ref, 'product deleted from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/delete]', error)\n return res.status(500).json({ error: 'Could not delete product' })\n }\n }\n}\n\nexport const productSaveHandler = createProductSaveHandler()\nexport const productStockHandler = createProductStockHandler()\nexport const productDuplicateHandler = createProductDuplicateHandler()\nexport const productDeleteHandler = createProductDeleteHandler()\n"],"names":["createHash","holdsSiteKey","hostStampedSitePermission","siteKeyRefusal","isRefusedIdToken","createResourceUid","countLiveDocuments","dropPluginSiteCache","firebaseAdmin","getLockdownVerdict","getOrgForHost","isImpersonationSession","FieldValue","liftLegacyProduct","productCollectionIds","productDuplicateFields","productDuplicateName","productDuplicateSlugBase","productEditWrite","productMembershipInput","productSaveFields","smartCollectionRulesOf","stockAdjustmentWrite","validateProduct","planRefusal","roomRefusal","ROLE_REFUSAL","MAX_BODY_BYTES","STOCK_ADJUST_REASONS","bodyOf","req","raw","body","length","JSON","parse","validId","value","test","gate","res","hostId","deps","options","authorization","String","headers","token","startsWith","slice","status","json","error","decoded","verifyIdToken","staff","host","firestore","collection","doc","get","exists","permission","allowed","data","uid","key","presetFor","refusal","org","orgForHost","locked","lockdown","request","method","email","outboxEntry","ref","paths","createdAt","serverTimestamp","attempts","entireHost","settleCache","entry","reason","dropped","dropCache","hostIds","catch","complete","delete","undefined","resolved","app","auth","now","Date","createProductSaveHandler","productId","product","Array","isArray","variants","name","trim","door","nowMs","fields","problem","db","hostRef","products","productRef","smartQuery","where","outbox","create","answer","runTransaction","tx","existing","smart","Promise","all","membership","collectionIds","docs","map","id","ok","replayed","early","used","aggregate","room","Number","createdAtMs","deletedAt","updatedAt","createdBy","set","storedTimestamps","console","stored","asked","Object","fromEntries","entries","filter","toDate","stockAttemptId","update","digest","createProductStockHandler","variantId","delta","Math","round","locationId","includes","isFinite","ledgerRef","ledger","inventory","some","variant","write","claimProductSlug","base","suffix","candidate","held","limit","empty","createProductDuplicateHandler","newProductId","source","copyRef","already","slug","createProductDeleteHandler","productSaveHandler","productStockHandler","productDuplicateHandler","productDeleteHandler"],"mappings":"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"}
@@ -28,6 +28,11 @@ import { raiseOrderEvent } from "./order-events.js";
28
28
  import { reverseOrderConversion } from "@aglyn/aglyn/plugin-manager/plugin-conversion-credit";
29
29
  import { createStripeRefund } from "./stripe-refund.js";
30
30
  import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
31
+ import { holdsPosPermission } from "../model/plugin-permissions.js";
32
+ import { commerceWorkspacePermissionRefusal } from "../model/workspace-site-permissions.js";
33
+ import { holdsCommerceWorkspaceSiteKey } from "./commerce-site-keys.js";
34
+ import { posOrderRefundRecord, POS_ORDER_REFUNDS_COLLECTION } from "../model/commerce-pos-day-report.js";
35
+ import { posOrderRefundDialogId } from "./pos-order-refund-ids.js";
31
36
  /**
32
37
  * Retires the licence keys a refund withdrew (AGL-2454).
33
38
  *
@@ -148,8 +153,7 @@ import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
148
153
  }
149
154
  let claim = null;
150
155
  try {
151
- var _hostSnapshot_get, _order_lineItems, _order_paymentRefunds, _ref1, _ref2, _ref3;
152
- var _membership_permissions;
156
+ var _order_lineItems, _order_paymentRefunds, _ref1, _ref2, _ref3, _ref4;
153
157
  const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
154
158
  const firestore = firebaseAdmin.app().firestore();
155
159
  const hostRef = firestore.collection('hosts').doc(hostId);
@@ -159,46 +163,34 @@ import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
159
163
  error: 'Unknown site'
160
164
  });
161
165
  }
162
- // TWO CHECKS, and they answer different questions (AGL-2372).
163
- //
164
- // The first is the HOST-level fact the Firestore rules also enforce, and
165
- // it stays: `memberRoles` is the projection the rules read, so dropping it
166
- // here would let this route and the database disagree.
167
- const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
168
- // The second is WHOSE admin, and it is the one this gate was missing.
169
- //
170
- // `memberRoles` is a per-host projection of `hostAccess`, and
171
- // `/api/hosts/members` will grant a SITE COLLABORATOR `admin` on one site
172
- // (`hostAccess: { [hostId]: 'admin' }`, `allHosts: false`). That writes the
173
- // literal string `'admin'` into `memberRoles[uid]` — byte-identical to an
174
- // org owner's. So the check above cannot tell a contractor invited to run
175
- // one microsite from the person who owns the business, and refunding is
176
- // money leaving that business.
177
- //
178
- // `orgWide` is the discriminator, and it needs no new role: it is
179
- // `isOrgWideMember` (AGL-1026) — owner/admin of the org, an explicit
180
- // `allHosts` member, or the legacy pre-`allHosts` shape — and it is false
181
- // for every scoped collaborator. Same pairing `pos-order.ts` uses.
182
- //
183
- // BOTH halves are required. `hostRole` is re-tested rather than assumed
184
- // from `memberRole`: an org-wide member can still be scoped down to
185
- // `editor` on this host, and `resolveOrgPermissions` is the resolver that
186
- // knows it. It fails CLOSED on a lookup error when a host is named
187
- // (AGL-506), and `denied()` returns `orgWide: false` / `hostRole: null`,
188
- // so an absent membership refuses rather than folding to permitted.
166
+ // "Refund orders" (`commerce.refunds`, AGL-4047). Its preset is the gate
167
+ // this asked before, both halves (AGL-2372): the site's Admin, AND a
168
+ // member of the WHOLE workspace. `/api/hosts/members` can make a site
169
+ // collaborator the admin of one site, which writes the same `'admin'`
170
+ // into `memberRoles` as an owner's, and refunding is money leaving the
171
+ // business, so the key's site-role preset holds for members of the whole
172
+ // workspace only (`workspaceMembersOnly`). `resolveOrgPermissions` is the
173
+ // resolver that knows `orgWide` and this site's role now; it fails CLOSED
174
+ // on a lookup error when a host is named (AGL-506). The site's stamp,
175
+ // when the projection wrote one, carries a custom role's, an override's
176
+ // or the site's grant either way.
189
177
  const membership = await resolveOrgPermissions(decoded.uid, {
190
178
  hostId
191
179
  });
192
- const workspaceAdmin = memberRole === 'admin' && membership.orgWide === true && membership.hostRole === 'admin';
180
+ const refunder = holdsCommerceWorkspaceSiteKey(hostSnapshot, decoded.uid, 'commerce.refunds', membership);
193
181
  // THE REGISTER'S OWN REFUNDS (AGL-3609). A member who works the register
194
- // — `admin` or `editor` on this site with `managePos` — may refund a POS
195
- // order up to the site's cashier refund limit, and above it with a
196
- // workspace admin's PIN. Decided below, once the order and the amount are
197
- // known; every other refund still needs the workspace admin above.
198
- const registerStaff = !workspaceAdmin && (memberRole === 'admin' || memberRole === 'editor') && ((_membership_permissions = membership.permissions) == null ? void 0 : _membership_permissions.managePos) === true;
199
- if (!workspaceAdmin && !registerStaff) {
182
+ // — `managePos` with the register's refund key, as resolved for this site
183
+ // (AGL-4047), whatever their site role — may refund a POS order up to the
184
+ // site's cashier refund limit, and above it with the PIN of someone who
185
+ // holds "Approve refunds over the register's limit". Decided below, once
186
+ // the order and the amount are known; every other refund still needs
187
+ // "Refund orders" above.
188
+ const registerStaff = !refunder && // The register's refund key (AGL-4047), which follows `managePos`
189
+ // unless a role or the member sets it, and is never held without it.
190
+ holdsPosPermission(membership.permissions, 'posRefund');
191
+ if (!refunder && !registerStaff) {
200
192
  return res.status(403).json({
201
- error: memberRole !== 'admin' ? 'Refunds require a site admin' : 'Refunds require an admin of the whole workspace'
193
+ error: commerceWorkspacePermissionRefusal('commerce.refunds')
202
194
  });
203
195
  }
204
196
  const orderRef = hostRef.collection('orders').doc(orderId);
@@ -210,15 +202,18 @@ import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
210
202
  }
211
203
  const order = CommerceModel.liftLegacyOrder(orderSnapshot.data());
212
204
  if (registerStaff) {
213
- var _ref4, _body_registerId;
205
+ var _ref5, _body_registerId;
214
206
  const authority = await posRegisterRefundAuthority({
215
207
  hostId,
216
208
  hostRef,
217
209
  order,
218
210
  amountCents,
219
211
  lineCents: requestedLineIds.length ? CommerceModel.orderLineRefundCents(order, requestedLineIds) : null,
220
- registerId: String((_ref4 = (_body_registerId = body.registerId) != null ? _body_registerId : order.registerId) != null ? _ref4 : ''),
221
- managerAssertion: body.managerAssertion
212
+ registerId: String((_ref5 = (_body_registerId = body.registerId) != null ? _body_registerId : order.registerId) != null ? _ref5 : ''),
213
+ managerAssertion: body.managerAssertion,
214
+ // Their own approval, when a role or the site grants it without
215
+ // "Refund orders" (AGL-4047); under the presets the two go together.
216
+ isManager: holdsCommerceWorkspaceSiteKey(hostSnapshot, decoded.uid, 'commerce.posApprovals', membership)
222
217
  });
223
218
  if ('body' in authority) return res.status(403).json(authority.body);
224
219
  }
@@ -335,10 +330,10 @@ import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
335
330
  if (claimRef) {
336
331
  const ownerOrg = await getOrgForHost(hostId);
337
332
  try {
338
- var _ref5;
333
+ var _ref6;
339
334
  var _ownerOrg_org;
340
335
  await claimRef.create({
341
- orgId: String((_ref5 = ownerOrg == null ? void 0 : (_ownerOrg_org = ownerOrg.org) == null ? void 0 : _ownerOrg_org.id) != null ? _ref5 : '') || null,
336
+ orgId: String((_ref6 = ownerOrg == null ? void 0 : (_ownerOrg_org = ownerOrg.org) == null ? void 0 : _ownerOrg_org.id) != null ? _ref6 : '') || null,
342
337
  hostId,
343
338
  orderId,
344
339
  kind: 'commerce-refund',
@@ -510,6 +505,23 @@ import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
510
505
  let refundedCents = 0;
511
506
  let fullyRefunded = false;
512
507
  let closedTheOrder = false;
508
+ // A register sale's refund counts in the day totals on the day it was
509
+ // given, wherever it was given (AGL-4037): recorded beside the register's
510
+ // own returns, in the same transaction as the order's count, so the two
511
+ // cannot disagree. Online orders leave none.
512
+ const registerRefund = posOrderRefundRecord({
513
+ orderId,
514
+ order: order,
515
+ refundedCents: refundCents,
516
+ paymentIntentId: String(paymentIntentId),
517
+ cashierId: decoded.uid,
518
+ source: 'order_dialog',
519
+ stripeRefundId: refund.refundId || null,
520
+ atMs: Date.now()
521
+ });
522
+ const registerRefundRef = registerRefund ? hostRef.collection('registers').doc(registerRefund.registerId).collection(POS_ORDER_REFUNDS_COLLECTION)// One record per Stripe refund; a keyed retry replays at the claim
523
+ // above and never reaches here.
524
+ .doc(refund.refundId || posOrderRefundDialogId(orderId, (_ref4 = claim == null ? void 0 : claim.stripeKey) != null ? _ref4 : registerRefund.atMs)) : null;
513
525
  await firestore.runTransaction(async (transaction)=>{
514
526
  var _data, _fresh_refundedCents;
515
527
  const fresh = CommerceModel.liftLegacyOrder((_data = (await transaction.get(orderRef)).data()) != null ? _data : {});
@@ -539,6 +551,7 @@ import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
539
551
  }), {
540
552
  merge: true
541
553
  });
554
+ if (registerRefund && registerRefundRef) transaction.set(registerRefundRef, registerRefund);
542
555
  });
543
556
  const payload = {
544
557
  refundedCents,