@aglyn/plugins-commerce 1.0.0-beta.284 → 1.0.0-beta.287

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 (420) 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/payment-methods-card.component.js +1 -1
  10. package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
  11. package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +5 -0
  12. package/src/lib/components/console/pos/pos-cart-panel.component.js +18 -12
  13. package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -1
  14. package/src/lib/components/console/pos/pos-hardware-api.d.ts +76 -5
  15. package/src/lib/components/console/pos/pos-hardware-api.js +53 -11
  16. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -1
  17. package/src/lib/components/console/pos/pos-hardware-buyer.component.d.ts +18 -0
  18. package/src/lib/components/console/pos/pos-hardware-buyer.component.js +629 -0
  19. package/src/lib/components/console/pos/pos-hardware-buyer.component.js.map +1 -0
  20. package/src/lib/components/console/pos/pos-hardware-sale-api.d.ts +162 -0
  21. package/src/lib/components/console/pos/pos-hardware-sale-api.js +122 -0
  22. package/src/lib/components/console/pos/pos-hardware-sale-api.js.map +1 -0
  23. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.d.ts +39 -0
  24. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js +251 -0
  25. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js.map +1 -0
  26. package/src/lib/components/console/pos/use-pos-display.js +50 -0
  27. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  28. package/src/lib/components/console/pos-cellular-control.component.d.ts +41 -0
  29. package/src/lib/components/console/pos-cellular-control.component.js +211 -0
  30. package/src/lib/components/console/pos-cellular-control.component.js.map +1 -0
  31. package/src/lib/components/console/pos-devices-card.component.d.ts +9 -1
  32. package/src/lib/components/console/pos-devices-card.component.js +47 -15
  33. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  34. package/src/lib/components/console/pos-display/pos-display-api.d.ts +6 -0
  35. package/src/lib/components/console/pos-display/pos-display-api.js +8 -1
  36. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  37. package/src/lib/components/console/pos-display-page.component.d.ts +13 -0
  38. package/src/lib/components/console/pos-display-page.component.js +44 -2
  39. package/src/lib/components/console/pos-display-page.component.js.map +1 -1
  40. package/src/lib/components/console/pos-hardware-card.component.d.ts +12 -4
  41. package/src/lib/components/console/pos-hardware-card.component.js +157 -36
  42. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -1
  43. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +360 -80
  44. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -1
  45. package/src/lib/components/console/pos-hardware-terms-page.component.d.ts +34 -0
  46. package/src/lib/components/console/pos-hardware-terms-page.component.js +257 -0
  47. package/src/lib/components/console/pos-hardware-terms-page.component.js.map +1 -0
  48. package/src/lib/components/console/pos-hardware-terms-sign-page.component.d.ts +27 -0
  49. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js +104 -0
  50. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js.map +1 -0
  51. package/src/lib/components/console/pos-hardware-waiting-card.component.d.ts +24 -0
  52. package/src/lib/components/console/pos-hardware-waiting-card.component.js +205 -0
  53. package/src/lib/components/console/pos-hardware-waiting-card.component.js.map +1 -0
  54. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +297 -201
  55. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -1
  56. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +3 -0
  57. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +260 -92
  58. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  59. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +9 -0
  60. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +15 -6
  61. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  62. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.d.ts +25 -0
  63. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js +136 -0
  64. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js.map +1 -0
  65. package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +6 -0
  66. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +48 -31
  67. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -1
  68. package/src/lib/components/console/pos-page.component.d.ts +1 -1
  69. package/src/lib/components/console/pos-page.component.js +63 -12
  70. package/src/lib/components/console/pos-page.component.js.map +1 -1
  71. package/src/lib/components/console/pos-reader-splash-setting.component.js +1 -1
  72. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -1
  73. package/src/lib/components/console/product-check-in-fields.component.d.ts +4 -2
  74. package/src/lib/components/console/product-check-in-fields.component.js +60 -14
  75. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -1
  76. package/src/lib/components/console/product-editor-dialog.component.js +44 -1
  77. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  78. package/src/lib/components/console/product-hardware-sku-field.component.d.ts +19 -0
  79. package/src/lib/components/console/product-hardware-sku-field.component.js +92 -0
  80. package/src/lib/components/console/product-hardware-sku-field.component.js.map +1 -0
  81. package/src/lib/components/console/product-kiosk-offer-field.component.d.ts +22 -0
  82. package/src/lib/components/console/product-kiosk-offer-field.component.js +94 -0
  83. package/src/lib/components/console/product-kiosk-offer-field.component.js.map +1 -0
  84. package/src/lib/components/console/product-register-fields.component.d.ts +4 -0
  85. package/src/lib/components/console/product-register-fields.component.js +31 -2
  86. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  87. package/src/lib/components/console/products-hub-card.component.js +11 -2
  88. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  89. package/src/lib/components/console/register-hardware-card.component.d.ts +11 -0
  90. package/src/lib/components/console/register-hardware-card.component.js +56 -27
  91. package/src/lib/components/console/register-hardware-card.component.js.map +1 -1
  92. package/src/lib/components/console/reservation-guests-dialog.component.d.ts +28 -0
  93. package/src/lib/components/console/reservation-guests-dialog.component.js +189 -0
  94. package/src/lib/components/console/reservation-guests-dialog.component.js.map +1 -0
  95. package/src/lib/components/console/reservations-card.component.d.ts +9 -0
  96. package/src/lib/components/console/reservations-card.component.js +158 -23
  97. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  98. package/src/lib/components/console/shippingeasy-card.component.js +1 -1
  99. package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
  100. package/src/lib/components/console/shipstation-card.component.d.ts +1 -1
  101. package/src/lib/components/console/shipstation-card.component.js +3 -3
  102. package/src/lib/components/console/shipstation-card.component.js.map +1 -1
  103. package/src/lib/components/product-detail.d.ts +4 -0
  104. package/src/lib/components/product-detail.js +40 -5
  105. package/src/lib/components/product-detail.js.map +1 -1
  106. package/src/lib/components/product-reviews.js +28 -11
  107. package/src/lib/components/product-reviews.js.map +1 -1
  108. package/src/lib/components/reservation-widget.js +28 -5
  109. package/src/lib/components/reservation-widget.js.map +1 -1
  110. package/src/lib/components/use-day-picker.d.ts +25 -0
  111. package/src/lib/components/use-day-picker.js +57 -0
  112. package/src/lib/components/use-day-picker.js.map +1 -0
  113. package/src/lib/constants/operator-alerts.d.ts +55 -0
  114. package/src/lib/constants/operator-alerts.js +156 -1
  115. package/src/lib/constants/operator-alerts.js.map +1 -1
  116. package/src/lib/declarations.console-server.js +54 -1
  117. package/src/lib/declarations.console-server.js.map +1 -1
  118. package/src/lib/declarations.d.ts +4 -2
  119. package/src/lib/declarations.js +11 -4
  120. package/src/lib/declarations.js.map +1 -1
  121. package/src/lib/declarations.server.js +9 -0
  122. package/src/lib/declarations.server.js.map +1 -1
  123. package/src/lib/model/commerce-cart.d.ts +9 -1
  124. package/src/lib/model/commerce-cart.js +4 -3
  125. package/src/lib/model/commerce-cart.js.map +1 -1
  126. package/src/lib/model/commerce-check-in-product.d.ts +26 -0
  127. package/src/lib/model/commerce-check-in-product.js +64 -2
  128. package/src/lib/model/commerce-check-in-product.js.map +1 -1
  129. package/src/lib/model/commerce-check-in-record.d.ts +127 -0
  130. package/src/lib/model/commerce-check-in-record.js +36 -0
  131. package/src/lib/model/commerce-check-in-record.js.map +1 -0
  132. package/src/lib/model/commerce-check-in.d.ts +91 -87
  133. package/src/lib/model/commerce-check-in.js +186 -62
  134. package/src/lib/model/commerce-check-in.js.map +1 -1
  135. package/src/lib/model/commerce-kiosk-offer.d.ts +31 -0
  136. package/src/lib/model/commerce-kiosk-offer.js +35 -0
  137. package/src/lib/model/commerce-kiosk-offer.js.map +1 -0
  138. package/src/lib/model/commerce-orders.d.ts +76 -0
  139. package/src/lib/model/commerce-orders.js +6 -0
  140. package/src/lib/model/commerce-orders.js.map +1 -1
  141. package/src/lib/model/commerce-pos-day-report.d.ts +54 -1
  142. package/src/lib/model/commerce-pos-day-report.js +39 -2
  143. package/src/lib/model/commerce-pos-day-report.js.map +1 -1
  144. package/src/lib/model/commerce-pos-offline.d.ts +1 -1
  145. package/src/lib/model/commerce-pos-offline.js +6 -1
  146. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  147. package/src/lib/model/commerce-pos-ops.js +3 -2
  148. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  149. package/src/lib/model/commerce-pos.d.ts +36 -3
  150. package/src/lib/model/commerce-pos.js +14 -6
  151. package/src/lib/model/commerce-pos.js.map +1 -1
  152. package/src/lib/model/commerce-receipt.js +3 -2
  153. package/src/lib/model/commerce-receipt.js.map +1 -1
  154. package/src/lib/model/commerce-reservations.d.ts +69 -4
  155. package/src/lib/model/commerce-reservations.js +57 -5
  156. package/src/lib/model/commerce-reservations.js.map +1 -1
  157. package/src/lib/model/commerce.d.ts +66 -0
  158. package/src/lib/model/commerce.js +15 -0
  159. package/src/lib/model/commerce.js.map +1 -1
  160. package/src/lib/model/order-list-fields.d.ts +11 -0
  161. package/src/lib/model/order-list-fields.js +17 -1
  162. package/src/lib/model/order-list-fields.js.map +1 -1
  163. package/src/lib/model/own-store-tax-summary.d.ts +152 -0
  164. package/src/lib/model/own-store-tax-summary.js +288 -0
  165. package/src/lib/model/own-store-tax-summary.js.map +1 -0
  166. package/src/lib/model/plugin-permissions.d.ts +67 -0
  167. package/src/lib/model/plugin-permissions.js +224 -7
  168. package/src/lib/model/plugin-permissions.js.map +1 -1
  169. package/src/lib/model/pos-cellular.d.ts +223 -0
  170. package/src/lib/model/pos-cellular.js +293 -0
  171. package/src/lib/model/pos-cellular.js.map +1 -0
  172. package/src/lib/model/pos-hardware-in-person.d.ts +98 -0
  173. package/src/lib/model/pos-hardware-in-person.js +224 -0
  174. package/src/lib/model/pos-hardware-in-person.js.map +1 -0
  175. package/src/lib/model/pos-hardware-tax-summary.d.ts +166 -0
  176. package/src/lib/model/pos-hardware-tax-summary.js +225 -0
  177. package/src/lib/model/pos-hardware-tax-summary.js.map +1 -0
  178. package/src/lib/model/pos-hardware-terms.d.ts +74 -0
  179. package/src/lib/model/pos-hardware-terms.js +115 -0
  180. package/src/lib/model/pos-hardware-terms.js.map +1 -0
  181. package/src/lib/model/pos-hardware.d.ts +349 -7
  182. package/src/lib/model/pos-hardware.js +291 -20
  183. package/src/lib/model/pos-hardware.js.map +1 -1
  184. package/src/lib/model/product-write.d.ts +10 -0
  185. package/src/lib/model/product-write.js +4 -2
  186. package/src/lib/model/product-write.js.map +1 -1
  187. package/src/lib/plugin.js +16 -0
  188. package/src/lib/plugin.js.map +1 -1
  189. package/src/lib/server/billing-webhook.js +25 -1
  190. package/src/lib/server/billing-webhook.js.map +1 -1
  191. package/src/lib/server/cancel-order.js +7 -5
  192. package/src/lib/server/cancel-order.js.map +1 -1
  193. package/src/lib/server/cart-checkout.js +20 -8
  194. package/src/lib/server/cart-checkout.js.map +1 -1
  195. package/src/lib/server/cart.js +29 -0
  196. package/src/lib/server/cart.js.map +1 -1
  197. package/src/lib/server/checkout.js +42 -13
  198. package/src/lib/server/checkout.js.map +1 -1
  199. package/src/lib/server/collection-membership.d.ts +4 -0
  200. package/src/lib/server/collection-membership.js +5 -10
  201. package/src/lib/server/collection-membership.js.map +1 -1
  202. package/src/lib/server/commerce-site-keys.d.ts +29 -0
  203. package/src/lib/server/commerce-site-keys.js +40 -0
  204. package/src/lib/server/commerce-site-keys.js.map +1 -0
  205. package/src/lib/server/draft-order.js +8 -5
  206. package/src/lib/server/draft-order.js.map +1 -1
  207. package/src/lib/server/fulfill-order.js +7 -5
  208. package/src/lib/server/fulfill-order.js.map +1 -1
  209. package/src/lib/server/gift-cards.js +8 -5
  210. package/src/lib/server/gift-cards.js.map +1 -1
  211. package/src/lib/server/local-fulfillment-status.js +9 -6
  212. package/src/lib/server/local-fulfillment-status.js.map +1 -1
  213. package/src/lib/server/member-post.js +6 -5
  214. package/src/lib/server/member-post.js.map +1 -1
  215. package/src/lib/server/membership-admin-password.d.ts +4 -2
  216. package/src/lib/server/membership-admin-password.js +10 -8
  217. package/src/lib/server/membership-admin-password.js.map +1 -1
  218. package/src/lib/server/membership-admin-remove.d.ts +7 -5
  219. package/src/lib/server/membership-admin-remove.js +9 -9
  220. package/src/lib/server/membership-admin-remove.js.map +1 -1
  221. package/src/lib/server/order-notifications.js +5 -2
  222. package/src/lib/server/order-notifications.js.map +1 -1
  223. package/src/lib/server/order-print.js +7 -5
  224. package/src/lib/server/order-print.js.map +1 -1
  225. package/src/lib/server/order-receipt-send.js +8 -5
  226. package/src/lib/server/order-receipt-send.js.map +1 -1
  227. package/src/lib/server/orders-shipping-prepare.js +9 -6
  228. package/src/lib/server/orders-shipping-prepare.js.map +1 -1
  229. package/src/lib/server/own-store-tax-return-source.d.ts +47 -0
  230. package/src/lib/server/own-store-tax-return-source.js +312 -0
  231. package/src/lib/server/own-store-tax-return-source.js.map +1 -0
  232. package/src/lib/server/payment-methods.js +13 -9
  233. package/src/lib/server/payment-methods.js.map +1 -1
  234. package/src/lib/server/pos-auth.d.ts +37 -6
  235. package/src/lib/server/pos-auth.js +40 -15
  236. package/src/lib/server/pos-auth.js.map +1 -1
  237. package/src/lib/server/pos-cellular-billing.d.ts +81 -0
  238. package/src/lib/server/pos-cellular-billing.js +711 -0
  239. package/src/lib/server/pos-cellular-billing.js.map +1 -0
  240. package/src/lib/server/pos-cellular-notify.d.ts +47 -0
  241. package/src/lib/server/pos-cellular-notify.js +150 -0
  242. package/src/lib/server/pos-cellular-notify.js.map +1 -0
  243. package/src/lib/server/pos-cellular.d.ts +88 -0
  244. package/src/lib/server/pos-cellular.js +339 -0
  245. package/src/lib/server/pos-cellular.js.map +1 -0
  246. package/src/lib/server/pos-check-in.d.ts +5 -1
  247. package/src/lib/server/pos-check-in.js +296 -28
  248. package/src/lib/server/pos-check-in.js.map +1 -1
  249. package/src/lib/server/pos-day-report.js +29 -3
  250. package/src/lib/server/pos-day-report.js.map +1 -1
  251. package/src/lib/server/pos-display.d.ts +16 -0
  252. package/src/lib/server/pos-display.js +129 -2
  253. package/src/lib/server/pos-display.js.map +1 -1
  254. package/src/lib/server/pos-hardware-admin.d.ts +10 -1
  255. package/src/lib/server/pos-hardware-admin.js +250 -84
  256. package/src/lib/server/pos-hardware-admin.js.map +1 -1
  257. package/src/lib/server/pos-hardware-attach.d.ts +105 -0
  258. package/src/lib/server/pos-hardware-attach.js +443 -0
  259. package/src/lib/server/pos-hardware-attach.js.map +1 -0
  260. package/src/lib/server/pos-hardware-buyer-lookup.d.ts +58 -0
  261. package/src/lib/server/pos-hardware-buyer-lookup.js +180 -0
  262. package/src/lib/server/pos-hardware-buyer-lookup.js.map +1 -0
  263. package/src/lib/server/pos-hardware-buyer-record.d.ts +29 -0
  264. package/src/lib/server/pos-hardware-buyer-record.js +86 -0
  265. package/src/lib/server/pos-hardware-buyer-record.js.map +1 -0
  266. package/src/lib/server/pos-hardware-cancel.d.ts +58 -0
  267. package/src/lib/server/pos-hardware-cancel.js +147 -0
  268. package/src/lib/server/pos-hardware-cancel.js.map +1 -0
  269. package/src/lib/server/pos-hardware-closure-notices.d.ts +27 -0
  270. package/src/lib/server/pos-hardware-closure-notices.js +85 -0
  271. package/src/lib/server/pos-hardware-closure-notices.js.map +1 -0
  272. package/src/lib/server/pos-hardware-closure.d.ts +60 -0
  273. package/src/lib/server/pos-hardware-closure.js +153 -0
  274. package/src/lib/server/pos-hardware-closure.js.map +1 -0
  275. package/src/lib/server/pos-hardware-in-person.d.ts +61 -0
  276. package/src/lib/server/pos-hardware-in-person.js +236 -0
  277. package/src/lib/server/pos-hardware-in-person.js.map +1 -0
  278. package/src/lib/server/pos-hardware-notify.d.ts +40 -2
  279. package/src/lib/server/pos-hardware-notify.js +195 -14
  280. package/src/lib/server/pos-hardware-notify.js.map +1 -1
  281. package/src/lib/server/pos-hardware-readers.d.ts +115 -0
  282. package/src/lib/server/pos-hardware-readers.js +430 -0
  283. package/src/lib/server/pos-hardware-readers.js.map +1 -0
  284. package/src/lib/server/pos-hardware-returns.d.ts +116 -0
  285. package/src/lib/server/pos-hardware-returns.js +502 -0
  286. package/src/lib/server/pos-hardware-returns.js.map +1 -0
  287. package/src/lib/server/pos-hardware-sale-fields.d.ts +28 -0
  288. package/src/lib/server/pos-hardware-sale-fields.js +44 -0
  289. package/src/lib/server/pos-hardware-sale-fields.js.map +1 -0
  290. package/src/lib/server/pos-hardware-sale.d.ts +54 -0
  291. package/src/lib/server/pos-hardware-sale.js +369 -0
  292. package/src/lib/server/pos-hardware-sale.js.map +1 -0
  293. package/src/lib/server/pos-hardware-tax-return-source.d.ts +99 -0
  294. package/src/lib/server/pos-hardware-tax-return-source.js +462 -0
  295. package/src/lib/server/pos-hardware-tax-return-source.js.map +1 -0
  296. package/src/lib/server/pos-hardware-terms.d.ts +64 -0
  297. package/src/lib/server/pos-hardware-terms.js +154 -0
  298. package/src/lib/server/pos-hardware-terms.js.map +1 -0
  299. package/src/lib/server/pos-hardware-user-eraser.d.ts +3 -0
  300. package/src/lib/server/pos-hardware-user-eraser.js +31 -7
  301. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -1
  302. package/src/lib/server/pos-hardware-webhook.d.ts +5 -0
  303. package/src/lib/server/pos-hardware-webhook.js +51 -6
  304. package/src/lib/server/pos-hardware-webhook.js.map +1 -1
  305. package/src/lib/server/pos-hardware.d.ts +74 -1
  306. package/src/lib/server/pos-hardware.js +175 -10
  307. package/src/lib/server/pos-hardware.js.map +1 -1
  308. package/src/lib/server/pos-kiosk.d.ts +2 -0
  309. package/src/lib/server/pos-kiosk.js +17 -12
  310. package/src/lib/server/pos-kiosk.js.map +1 -1
  311. package/src/lib/server/pos-offline-card-order.d.ts +64 -0
  312. package/src/lib/server/pos-offline-card-order.js +102 -0
  313. package/src/lib/server/pos-offline-card-order.js.map +1 -0
  314. package/src/lib/server/pos-offline-card-reconcile.d.ts +67 -1
  315. package/src/lib/server/pos-offline-card-reconcile.js +215 -18
  316. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -1
  317. package/src/lib/server/pos-offline-sync.d.ts +39 -0
  318. package/src/lib/server/pos-offline-sync.js +190 -55
  319. package/src/lib/server/pos-offline-sync.js.map +1 -1
  320. package/src/lib/server/pos-ops-gate.d.ts +15 -4
  321. package/src/lib/server/pos-ops-gate.js +42 -9
  322. package/src/lib/server/pos-ops-gate.js.map +1 -1
  323. package/src/lib/server/pos-ops-routes.js +24 -0
  324. package/src/lib/server/pos-ops-routes.js.map +1 -1
  325. package/src/lib/server/pos-order-refund-ids.d.ts +28 -0
  326. package/src/lib/server/pos-order-refund-ids.js +31 -0
  327. package/src/lib/server/pos-order-refund-ids.js.map +1 -0
  328. package/src/lib/server/pos-order.js +52 -49
  329. package/src/lib/server/pos-order.js.map +1 -1
  330. package/src/lib/server/pos-payment.js +1 -1
  331. package/src/lib/server/pos-payment.js.map +1 -1
  332. package/src/lib/server/pos-reader-splash-admin.js +30 -32
  333. package/src/lib/server/pos-reader-splash-admin.js.map +1 -1
  334. package/src/lib/server/pos-reader-splash-images-route.d.ts +17 -0
  335. package/src/lib/server/pos-reader-splash-images-route.js +113 -0
  336. package/src/lib/server/pos-reader-splash-images-route.js.map +1 -0
  337. package/src/lib/server/pos-reader-splash-images.d.ts +51 -0
  338. package/src/lib/server/pos-reader-splash-images.js +123 -0
  339. package/src/lib/server/pos-reader-splash-images.js.map +1 -0
  340. package/src/lib/server/pos-reader-splash.d.ts +68 -1
  341. package/src/lib/server/pos-reader-splash.js +182 -11
  342. package/src/lib/server/pos-reader-splash.js.map +1 -1
  343. package/src/lib/server/pos-readers.d.ts +29 -0
  344. package/src/lib/server/pos-readers.js +134 -35
  345. package/src/lib/server/pos-readers.js.map +1 -1
  346. package/src/lib/server/pos-return.d.ts +10 -0
  347. package/src/lib/server/pos-return.js +26 -1
  348. package/src/lib/server/pos-return.js.map +1 -1
  349. package/src/lib/server/pos-sale.js +6 -1
  350. package/src/lib/server/pos-sale.js.map +1 -1
  351. package/src/lib/server/pos-shift.js +11 -0
  352. package/src/lib/server/pos-shift.js.map +1 -1
  353. package/src/lib/server/pos-staff-pin.js +25 -20
  354. package/src/lib/server/pos-staff-pin.js.map +1 -1
  355. package/src/lib/server/pos-terminal.d.ts +14 -4
  356. package/src/lib/server/pos-terminal.js +34 -38
  357. package/src/lib/server/pos-terminal.js.map +1 -1
  358. package/src/lib/server/printers.d.ts +7 -3
  359. package/src/lib/server/printers.js +18 -12
  360. package/src/lib/server/printers.js.map +1 -1
  361. package/src/lib/server/product-drafts.d.ts +11 -1
  362. package/src/lib/server/product-drafts.js +12 -8
  363. package/src/lib/server/product-drafts.js.map +1 -1
  364. package/src/lib/server/product.d.ts +15 -1
  365. package/src/lib/server/product.js +16 -3
  366. package/src/lib/server/product.js.map +1 -1
  367. package/src/lib/server/products-write.d.ts +26 -2
  368. package/src/lib/server/products-write.js +19 -7
  369. package/src/lib/server/products-write.js.map +1 -1
  370. package/src/lib/server/refund.js +34 -11
  371. package/src/lib/server/refund.js.map +1 -1
  372. package/src/lib/server/reservation-availability-query.d.ts +8 -0
  373. package/src/lib/server/reservation-availability-query.js +11 -1
  374. package/src/lib/server/reservation-availability-query.js.map +1 -1
  375. package/src/lib/server/reservation-availability.d.ts +1 -1
  376. package/src/lib/server/reservation-availability.js +7 -2
  377. package/src/lib/server/reservation-availability.js.map +1 -1
  378. package/src/lib/server/reservation-guests.d.ts +32 -0
  379. package/src/lib/server/reservation-guests.js +285 -0
  380. package/src/lib/server/reservation-guests.js.map +1 -0
  381. package/src/lib/server/reservation-walk-in.d.ts +11 -1
  382. package/src/lib/server/reservation-walk-in.js +56 -12
  383. package/src/lib/server/reservation-walk-in.js.map +1 -1
  384. package/src/lib/server/reserve.js +18 -5
  385. package/src/lib/server/reserve.js.map +1 -1
  386. package/src/lib/server/returns.js +9 -6
  387. package/src/lib/server/returns.js.map +1 -1
  388. package/src/lib/server/reviews.d.ts +22 -2
  389. package/src/lib/server/reviews.js +88 -20
  390. package/src/lib/server/reviews.js.map +1 -1
  391. package/src/lib/server/shipping-connectors.js +10 -11
  392. package/src/lib/server/shipping-connectors.js.map +1 -1
  393. package/src/lib/server/site-page-resolver.js +20 -3
  394. package/src/lib/server/site-page-resolver.js.map +1 -1
  395. package/src/lib/server/store-day.d.ts +33 -0
  396. package/src/lib/server/store-day.js +46 -0
  397. package/src/lib/server/store-day.js.map +1 -0
  398. package/src/lib/server/store-setup-ai-writer.d.ts +2 -1
  399. package/src/lib/server/store-setup-ai-writer.js +5 -6
  400. package/src/lib/server/store-setup-ai-writer.js.map +1 -1
  401. package/src/lib/server/storefront-tax.d.ts +13 -0
  402. package/src/lib/server/storefront-tax.js +8 -1
  403. package/src/lib/server/storefront-tax.js.map +1 -1
  404. package/src/lib/server/tax-return-source.d.ts +32 -3
  405. package/src/lib/server/tax-return-source.js +99 -20
  406. package/src/lib/server/tax-return-source.js.map +1 -1
  407. package/src/lib/server.js +100 -2
  408. package/src/lib/server.js.map +1 -1
  409. package/src/lib/testing/fake-firestore.js +58 -16
  410. package/src/lib/testing/fake-firestore.js.map +1 -1
  411. package/src/lib/testing/pos-ops-memory-firestore.js +18 -4
  412. package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -1
  413. package/src/lib/testing/site-register-permissions.d.ts +29 -0
  414. package/src/lib/testing/site-register-permissions.js +38 -0
  415. package/src/lib/testing/site-register-permissions.js.map +1 -0
  416. package/src/lib/transfer/tracking.server.js +8 -5
  417. package/src/lib/transfer/tracking.server.js.map +1 -1
  418. package/src/lib/utils/add-to-cart.d.ts +2 -0
  419. package/src/lib/utils/add-to-cart.js +6 -3
  420. package/src/lib/utils/add-to-cart.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/collection-membership.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 * as Aglyn from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\n\n/*\n * RE-STAMP A SMART COLLECTION'S MEMBERSHIP ON THE PRODUCTS (AGL-3321).\n *\n * The storefront reads a smart collection whose rules no query can express as\n * `collectionIds array-contains <id>`, so the membership lives on each\n * product. Product writers stamp it from the rules as they stand; when the\n * RULES change — a smart collection is created, edited or deleted — every\n * product of the site may move, and this is the one place that moves them.\n * The catalog card calls it after each save and delete.\n *\n * It only ever adds or removes THIS collection's id, and writes only a\n * product whose membership differs, so running it twice changes nothing the\n * second time and a run cut short is finished by the next. A collection that\n * is gone (or no longer smart) is taken off every product still naming it.\n *\n * Bounded per request: it walks the products in document-id order, at most\n * {@link MEMBERSHIP_SCAN_MAX} of them, and answers with a cursor the caller\n * passes back until `done`.\n */\n\n/** Products read (and written) per batch. */\nexport const MEMBERSHIP_PAGE = 300\n/** Products one request walks before it hands back a cursor. */\nexport const MEMBERSHIP_SCAN_MAX = 3000\n\nexport interface MembershipPass {\n done: boolean\n /** Where the next request starts, when not done. */\n after?: string\n scanned: number\n updated: number\n}\n\n/**\n * An allowlist (AGL-2372): the roles `/api/hosts/collections` lets edit a\n * collection, since whoever may change the rules may move what they match.\n */\nconst COLLECTION_EDITORS = new Set(['admin', 'editor', 'author'])\n\n/**\n * One bounded pass: every product from `after` onward, up to the scan cap,\n * given this collection's membership as the rules now say.\n */\nexport async function restampCollectionMembership(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n collectionId: string,\n after?: string,\n): Promise<MembershipPass> {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const collectionSnapshot = await hostRef.collection('collections').doc(collectionId).get()\n const data = collectionSnapshot.exists ? collectionSnapshot.data() : undefined\n const smart =\n data && Aglyn.hostCollectionKind(data) === 'catalog' && data['mode'] === 'smart'\n ? (data as CommerceModel.HostCollection)\n : null\n const { FieldValue, FieldPath } = firebaseAdmin.firestore\n const productsRef = hostRef.collection('products')\n let scanned = 0\n let updated = 0\n\n if (!smart) {\n // Gone, or no longer smart: off every product that still names it.\n for (;;) {\n const page = await productsRef\n .where(CommerceModel.PRODUCT_COLLECTION_IDS, 'array-contains', collectionId)\n .limit(MEMBERSHIP_PAGE)\n .get()\n if (page.empty) break\n const batch = firestore.batch()\n for (const entry of page.docs) {\n batch.update(entry.ref, {\n [CommerceModel.PRODUCT_COLLECTION_IDS]: FieldValue.arrayRemove(collectionId),\n })\n }\n await batch.commit()\n scanned += page.size\n updated += page.size\n if (page.size < MEMBERSHIP_PAGE || scanned >= MEMBERSHIP_SCAN_MAX) {\n return { done: page.size < MEMBERSHIP_PAGE, scanned, updated }\n }\n }\n return { done: true, scanned, updated }\n }\n\n let cursor = after\n while (scanned < MEMBERSHIP_SCAN_MAX) {\n let pageQuery = productsRef.orderBy(FieldPath.documentId()).limit(MEMBERSHIP_PAGE)\n if (cursor) pageQuery = pageQuery.startAfter(cursor)\n const page = await pageQuery.get()\n if (page.empty) return { done: true, scanned, updated }\n const batch = firestore.batch()\n let writes = 0\n for (const entry of page.docs) {\n const stored = entry.data() as Partial<CommerceModel.HostProduct>\n const member = CommerceModel.smartCollectionMatches(\n CommerceModel.liftLegacyProduct(stored),\n smart,\n )\n const holds = (stored.collectionIds ?? []).includes(collectionId)\n if (member === holds) continue\n batch.update(entry.ref, {\n [CommerceModel.PRODUCT_COLLECTION_IDS]: member\n ? FieldValue.arrayUnion(collectionId)\n : FieldValue.arrayRemove(collectionId),\n })\n writes += 1\n }\n if (writes) await batch.commit()\n scanned += page.size\n updated += writes\n cursor = page.docs[page.docs.length - 1].id\n if (page.size < MEMBERSHIP_PAGE) return { done: true, scanned, updated }\n }\n return { done: false, after: cursor, scanned, updated }\n}\n\n/** `POST /api/commerce/collection-membership` — `{ hostId, collectionId, after? }`. */\nexport const collectionMembershipHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const body =\n typeof req.body === 'string' ? JSON.parse(req.body || '{}') : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const collectionId = String(body.collectionId ?? '')\n const after = typeof body.after === 'string' && body.after ? body.after : undefined\n if (!hostId || !collectionId) {\n return res.status(400).json({ error: 'Missing hostId or collectionId' })\n }\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const hostSnapshot = await firestore.collection('hosts').doc(hostId).get()\n if (!hostSnapshot.exists) return res.status(404).json({ error: 'Unknown site' })\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (decoded['staff'] !== true && !COLLECTION_EDITORS.has(String(memberRole))) {\n return res.status(403).json({ error: 'Not permitted' })\n }\n const pass = await restampCollectionMembership(firestore, hostId, collectionId, after)\n return res.status(200).json(pass)\n } catch (error) {\n console.error('collection-membership', error)\n return res.status(500).json({ error: 'Collection membership failed' })\n }\n}\n"],"names":["Aglyn","firebaseAdmin","CommerceModel","MEMBERSHIP_PAGE","MEMBERSHIP_SCAN_MAX","COLLECTION_EDITORS","Set","restampCollectionMembership","firestore","hostId","collectionId","after","hostRef","collection","doc","collectionSnapshot","get","data","exists","undefined","smart","hostCollectionKind","FieldValue","FieldPath","productsRef","scanned","updated","page","where","PRODUCT_COLLECTION_IDS","limit","empty","batch","entry","docs","update","ref","arrayRemove","commit","size","done","cursor","pageQuery","orderBy","documentId","startAfter","writes","stored","member","smartCollectionMatches","liftLegacyProduct","holds","collectionIds","includes","arrayUnion","length","id","collectionMembershipHandler","req","res","body","method","status","json","error","authorization","String","headers","idToken","startsWith","slice","JSON","parse","hostSnapshot","decoded","app","auth","verifyIdToken","memberRole","uid","has","pass","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/collection-membership.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 * as Aglyn from '@aglyn/aglyn/server'\nimport { holdsSiteKey, siteKeyRefusal } from '@aglyn/aglyn/app-utils/host-permissions'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\n\n/*\n * RE-STAMP A SMART COLLECTION'S MEMBERSHIP ON THE PRODUCTS (AGL-3321).\n *\n * The storefront reads a smart collection whose rules no query can express as\n * `collectionIds array-contains <id>`, so the membership lives on each\n * product. Product writers stamp it from the rules as they stand; when the\n * RULES change — a smart collection is created, edited or deleted — every\n * product of the site may move, and this is the one place that moves them.\n * The catalog card calls it after each save and delete.\n *\n * It only ever adds or removes THIS collection's id, and writes only a\n * product whose membership differs, so running it twice changes nothing the\n * second time and a run cut short is finished by the next. A collection that\n * is gone (or no longer smart) is taken off every product still naming it.\n *\n * Bounded per request: it walks the products in document-id order, at most\n * {@link MEMBERSHIP_SCAN_MAX} of them, and answers with a cursor the caller\n * passes back until `done`.\n */\n\n/** Products read (and written) per batch. */\nexport const MEMBERSHIP_PAGE = 300\n/** Products one request walks before it hands back a cursor. */\nexport const MEMBERSHIP_SCAN_MAX = 3000\n\nexport interface MembershipPass {\n done: boolean\n /** Where the next request starts, when not done. */\n after?: string\n scanned: number\n updated: number\n}\n\n/**\n * An allowlist (AGL-2372): the roles `/api/hosts/collections` lets edit a\n * collection, since whoever may change the rules may move what they match.\n */\n\n/**\n * One bounded pass: every product from `after` onward, up to the scan cap,\n * given this collection's membership as the rules now say.\n */\nexport async function restampCollectionMembership(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n collectionId: string,\n after?: string,\n): Promise<MembershipPass> {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const collectionSnapshot = await hostRef.collection('collections').doc(collectionId).get()\n const data = collectionSnapshot.exists ? collectionSnapshot.data() : undefined\n const smart =\n data && Aglyn.hostCollectionKind(data) === 'catalog' && data['mode'] === 'smart'\n ? (data as CommerceModel.HostCollection)\n : null\n const { FieldValue, FieldPath } = firebaseAdmin.firestore\n const productsRef = hostRef.collection('products')\n let scanned = 0\n let updated = 0\n\n if (!smart) {\n // Gone, or no longer smart: off every product that still names it.\n for (;;) {\n const page = await productsRef\n .where(CommerceModel.PRODUCT_COLLECTION_IDS, 'array-contains', collectionId)\n .limit(MEMBERSHIP_PAGE)\n .get()\n if (page.empty) break\n const batch = firestore.batch()\n for (const entry of page.docs) {\n batch.update(entry.ref, {\n [CommerceModel.PRODUCT_COLLECTION_IDS]: FieldValue.arrayRemove(collectionId),\n })\n }\n await batch.commit()\n scanned += page.size\n updated += page.size\n if (page.size < MEMBERSHIP_PAGE || scanned >= MEMBERSHIP_SCAN_MAX) {\n return { done: page.size < MEMBERSHIP_PAGE, scanned, updated }\n }\n }\n return { done: true, scanned, updated }\n }\n\n let cursor = after\n while (scanned < MEMBERSHIP_SCAN_MAX) {\n let pageQuery = productsRef.orderBy(FieldPath.documentId()).limit(MEMBERSHIP_PAGE)\n if (cursor) pageQuery = pageQuery.startAfter(cursor)\n const page = await pageQuery.get()\n if (page.empty) return { done: true, scanned, updated }\n const batch = firestore.batch()\n let writes = 0\n for (const entry of page.docs) {\n const stored = entry.data() as Partial<CommerceModel.HostProduct>\n const member = CommerceModel.smartCollectionMatches(\n CommerceModel.liftLegacyProduct(stored),\n smart,\n )\n const holds = (stored.collectionIds ?? []).includes(collectionId)\n if (member === holds) continue\n batch.update(entry.ref, {\n [CommerceModel.PRODUCT_COLLECTION_IDS]: member\n ? FieldValue.arrayUnion(collectionId)\n : FieldValue.arrayRemove(collectionId),\n })\n writes += 1\n }\n if (writes) await batch.commit()\n scanned += page.size\n updated += writes\n cursor = page.docs[page.docs.length - 1].id\n if (page.size < MEMBERSHIP_PAGE) return { done: true, scanned, updated }\n }\n return { done: false, after: cursor, scanned, updated }\n}\n\n/** `POST /api/commerce/collection-membership` — `{ hostId, collectionId, after? }`. */\nexport const collectionMembershipHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const body =\n typeof req.body === 'string' ? JSON.parse(req.body || '{}') : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const collectionId = String(body.collectionId ?? '')\n const after = typeof body.after === 'string' && body.after ? body.after : undefined\n if (!hostId || !collectionId) {\n return res.status(400).json({ error: 'Missing hostId or collectionId' })\n }\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const hostSnapshot = await firestore.collection('hosts').doc(hostId).get()\n if (!hostSnapshot.exists) return res.status(404).json({ error: 'Unknown site' })\n // \"Edit site content\" (`site.edit`, AGL-4047), as the rules ask it of the catalog.\n if (decoded['staff'] !== true && !holdsSiteKey(hostSnapshot, decoded.uid, 'site.edit')) {\n return res.status(403).json({ error: siteKeyRefusal('site.edit') })\n }\n const pass = await restampCollectionMembership(firestore, hostId, collectionId, after)\n return res.status(200).json(pass)\n } catch (error) {\n console.error('collection-membership', error)\n return res.status(500).json({ error: 'Collection membership failed' })\n }\n}\n"],"names":["Aglyn","holdsSiteKey","siteKeyRefusal","firebaseAdmin","CommerceModel","MEMBERSHIP_PAGE","MEMBERSHIP_SCAN_MAX","restampCollectionMembership","firestore","hostId","collectionId","after","hostRef","collection","doc","collectionSnapshot","get","data","exists","undefined","smart","hostCollectionKind","FieldValue","FieldPath","productsRef","scanned","updated","page","where","PRODUCT_COLLECTION_IDS","limit","empty","batch","entry","docs","update","ref","arrayRemove","commit","size","done","cursor","pageQuery","orderBy","documentId","startAfter","writes","stored","member","smartCollectionMatches","liftLegacyProduct","holds","collectionIds","includes","arrayUnion","length","id","collectionMembershipHandler","req","res","body","method","status","json","error","authorization","String","headers","idToken","startsWith","slice","JSON","parse","decoded","app","auth","verifyIdToken","hostSnapshot","uid","pass","console"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type CommerceSitePermission } from '../model/plugin-permissions';
18
+ /**
19
+ * Does this member hold one of commerce's site keys on this site, as the
20
+ * rules would read it (AGL-4047): the site's `memberPermissions` stamp, else
21
+ * the preset of their `memberRoles` entry. `host` is the host snapshot or its
22
+ * data, which every door here reads already.
23
+ */
24
+ export declare function holdsCommerceSiteKey(host: {
25
+ get(field: string): unknown;
26
+ } | {
27
+ memberPermissions?: unknown;
28
+ memberRoles?: unknown;
29
+ } | null | undefined, uid: string, key: CommerceSitePermission): boolean;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { hostStampedSitePermission } from "@aglyn/aglyn/app-utils/host-permissions";
17
+ import { COMMERCE_SITE_PERMISSIONS } from "../model/plugin-permissions.js";
18
+ /** Each commerce site key's site-role preset: the declarations' own table. */ const COMMERCE_SITE_PRESETS = Object.fromEntries(COMMERCE_SITE_PERMISSIONS.map((entry)=>{
19
+ var _entry_hostRoleDefaults;
20
+ return [
21
+ entry.key,
22
+ (_entry_hostRoleDefaults = entry.hostRoleDefaults) != null ? _entry_hostRoleDefaults : {}
23
+ ];
24
+ }));
25
+ /**
26
+ * Does this member hold one of commerce's site keys on this site, as the
27
+ * rules would read it (AGL-4047): the site's `memberPermissions` stamp, else
28
+ * the preset of their `memberRoles` entry. `host` is the host snapshot or its
29
+ * data, which every door here reads already.
30
+ */ export function holdsCommerceSiteKey(host, uid, key) {
31
+ var _COMMERCE_SITE_PRESETS_key;
32
+ if (!host || !uid) return false;
33
+ const read = (field)=>typeof host.get === 'function' ? host.get(field) : host[field];
34
+ return hostStampedSitePermission({
35
+ memberPermissions: read('memberPermissions'),
36
+ memberRoles: read('memberRoles')
37
+ }, uid, key, (_COMMERCE_SITE_PRESETS_key = COMMERCE_SITE_PRESETS[key]) != null ? _COMMERCE_SITE_PRESETS_key : {});
38
+ }
39
+
40
+ //# sourceMappingURL=commerce-site-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/commerce-site-keys.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 { hostStampedSitePermission } from '@aglyn/aglyn/app-utils/host-permissions'\nimport type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport { COMMERCE_SITE_PERMISSIONS, type CommerceSitePermission } from '../model/plugin-permissions'\n\n/** Each commerce site key's site-role preset: the declarations' own table. */\nconst COMMERCE_SITE_PRESETS: Readonly<Record<string, Readonly<Partial<Record<HostAccessRole, boolean>>>>> =\n Object.fromEntries(COMMERCE_SITE_PERMISSIONS.map((entry) => [entry.key, entry.hostRoleDefaults ?? {}]))\n\n/**\n * Does this member hold one of commerce's site keys on this site, as the\n * rules would read it (AGL-4047): the site's `memberPermissions` stamp, else\n * the preset of their `memberRoles` entry. `host` is the host snapshot or its\n * data, which every door here reads already.\n */\nexport function holdsCommerceSiteKey(\n host: { get(field: string): unknown } | { memberPermissions?: unknown; memberRoles?: unknown } | null | undefined,\n uid: string,\n key: CommerceSitePermission,\n): boolean {\n if (!host || !uid) return false\n const read = (field: string): unknown =>\n typeof (host as { get?: unknown }).get === 'function'\n ? (host as { get(field: string): unknown }).get(field)\n : (host as Record<string, unknown>)[field]\n return hostStampedSitePermission(\n { memberPermissions: read('memberPermissions'), memberRoles: read('memberRoles') },\n uid,\n key,\n COMMERCE_SITE_PRESETS[key] ?? {},\n )\n}\n\n"],"names":["hostStampedSitePermission","COMMERCE_SITE_PERMISSIONS","COMMERCE_SITE_PRESETS","Object","fromEntries","map","entry","key","hostRoleDefaults","holdsCommerceSiteKey","host","uid","read","field","get","memberPermissions","memberRoles"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,yBAAyB,QAAQ,0CAAyC;AAEnF,SAASC,yBAAyB,QAAqC,iCAA6B;AAEpG,4EAA4E,GAC5E,MAAMC,wBACJC,OAAOC,WAAW,CAACH,0BAA0BI,GAAG,CAAC,CAACC;QAAsBA;WAAZ;QAACA,MAAMC,GAAG;SAAED,0BAAAA,MAAME,gBAAgB,YAAtBF,0BAA0B,CAAC;KAAE;;AAEvG;;;;;CAKC,GACD,OAAO,SAASG,qBACdC,IAAiH,EACjHC,GAAW,EACXJ,GAA2B;QAWzBL;IATF,IAAI,CAACQ,QAAQ,CAACC,KAAK,OAAO;IAC1B,MAAMC,OAAO,CAACC,QACZ,OAAO,AAACH,KAA2BI,GAAG,KAAK,aACvC,AAACJ,KAAyCI,GAAG,CAACD,SAC9C,AAACH,IAAgC,CAACG,MAAM;IAC9C,OAAOb,0BACL;QAAEe,mBAAmBH,KAAK;QAAsBI,aAAaJ,KAAK;IAAe,GACjFD,KACAJ,MACAL,6BAAAA,qBAAqB,CAACK,IAAI,YAA1BL,6BAA8B,CAAC;AAEnC"}
@@ -14,7 +14,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import * as Aglyn from "@aglyn/aglyn/server";
17
+ */ import { holdsCommerceSiteKey } from "./commerce-site-keys.js";
18
+ import { commerceSitePermissionRefusal } from "../model/plugin-permissions.js";
19
+ import * as Aglyn from "@aglyn/aglyn/server";
18
20
  import * as CommerceModel from "../model/index.js";
19
21
  import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
20
22
  import { merchantAccountIsReady } from "@aglyn/tenant-data-admin/server/payment-provider";
@@ -82,7 +84,7 @@ import { buildRoute, claimAttempt, Route } from "@aglyn/aglyn/server";
82
84
  if (slot) await slot.release();
83
85
  };
84
86
  try {
85
- var _hostSnapshot_get, _storeSettings_get, _taxSettings_origin, _product_type, _variant_weightGrams, _product_type1, _ref1;
87
+ var _storeSettings_get, _taxSettings_origin, _product_type, _variant_weightGrams, _product_type1, _ref1;
86
88
  var _ownerOrg_org, _ownerOrg_org1, _product_variants_, _ownerOrg_org2;
87
89
  const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
88
90
  const firestore = firebaseAdmin.app().firestore();
@@ -103,10 +105,11 @@ import { buildRoute, claimAttempt, Route } from "@aglyn/aglyn/server";
103
105
  // purpose — an author edits content and does not mint payment links — and
104
106
  // which the old denylist would have admitted. That is the allowlist form
105
107
  // earning its keep on a union that changed under it.
106
- const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
107
- if (memberRole !== 'admin' && memberRole !== 'editor') {
108
+ // "Manage orders" (`commerce.orders`, AGL-4047), whose preset is the site's Admin
109
+ // and Editor this asked for, read as the rules would read it.
110
+ if (!holdsCommerceSiteKey(hostSnapshot, decoded.uid, 'commerce.orders')) {
108
111
  return res.status(403).json({
109
- error: 'Not permitted'
112
+ error: commerceSitePermissionRefusal('commerce.orders')
110
113
  });
111
114
  }
112
115
  const productSnapshot = await hostRef.collection('products').doc(productId).get();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/draft-order.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 * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { merchantAccountIsReady } from '@aglyn/tenant-data-admin/server/payment-provider'\nimport { checkoutSessionCardAuthenticationParams } from '@aglyn/tenant-data-admin/server/stripe-card-authentication'\nimport {\n planCheckoutShippingWithCarriers,\n type CarrierShippingPlan,\n} from './carrier-shipping'\nimport { resolveManualTaxRateId } from './manual-tax-rate'\nimport {\n type PromotionSlotHold,\n holdPromotionSlot,\n promotionHoldKey,\n} from './promotion-hold'\nimport {\n buildRoute,\n claimAttempt,\n Route,\n type AttemptClaim,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\n\n/**\n * Draft orders (AGL-287, Shopify parity): a manager builds an order in\n * the console and sends the buyer a payment link. Creates the order doc\n * (status `pending`, channel `draft`) with a sequential number, then a\n * Stripe Checkout session on the merchant's connected account whose\n * webhook completion flips the SAME doc to `paid`\n * (metadata type `commerce-draft`).\n */\nexport const draftOrderHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n if (!process.env.STRIPE_SECRET_KEY) {\n return res.status(501).json({ error: 'Payments are not configured.' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const body =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const productId = String(body.productId ?? '')\n const variantId = String(body.variantId ?? '')\n const quantity = Math.max(1, Math.min(99, Math.round(Number(body.quantity ?? 1))))\n const email = String(body.email ?? '').trim()\n // A code the MERCHANT types while composing the order (AGL-305). The\n // storefront doors read this same field name, so one resolver answers it the\n // same way whoever pressed the button.\n const couponCode = String(body.couponCode ?? '').trim()\n // One draft attempt, minted by the console dialog (AGL-1697). Node\n // lowercases incoming headers, but read both spellings — the plugin API\n // request type makes no promise about casing.\n const idempotencyKey = String(\n req.headers['idempotency-key'] ?? req.headers['Idempotency-Key'] ?? '',\n ).trim().slice(0, 200)\n if (!hostId || !productId) {\n return res.status(400).json({ error: 'Missing hostId or productId' })\n }\n\n let claim: AttemptClaim | null = null\n /**\n * The redemption slot this attempt reserved (AGL-305), hoisted so every\n * refusal below the claim — and the catch — can hand it back. A throw after\n * the hold is placed is exactly the case where a slot would otherwise stand\n * invisibly against the merchant's cap until its TTL lapsed, and this path\n * rolls its order document back on two of those refusals already.\n */\n let discountSlot: PromotionSlotHold | null = null\n let discountHoldKey = ''\n const releaseDiscountSlot = async (): Promise<void> => {\n const slot = discountSlot\n discountSlot = null\n discountHoldKey = ''\n if (slot) await slot.release()\n }\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n // AN ALLOWLIST (AGL-2262), matching `cancel-order.ts`, `fulfill-order.ts`\n // and the Firestore rules, whose host-write predicate is\n // `hostMemberRole(hostId) in ['admin','editor']`. The old\n // `!role || role === 'viewer'` denylist caught the stranger but admitted\n // every OTHER string — a legacy value, a typo, any role added later — on\n // the one route that mints a live Stripe payment link. `HostAccessRole`\n // has since grown an `author` (AGL-2334), which this allowlist refuses on\n // purpose — an author edits content and does not mint payment links — and\n // which the old denylist would have admitted. That is the allowlist form\n // earning its keep on a union that changed under it.\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not permitted' })\n }\n const productSnapshot = await hostRef\n .collection('products')\n .doc(productId)\n .get()\n const raw = productSnapshot.data() as any\n if (!raw || raw.deletedAt) {\n return res.status(404).json({ error: 'Unknown product' })\n }\n const product = CommerceModel.liftLegacyProduct(raw)\n const variant = variantId\n ? product.variants.find((item) => item.id === variantId)\n : product.variants[0]\n if (!variant) return res.status(404).json({ error: 'Unknown variant' })\n // A variant with no price yet (AGL-2916) cannot be invoiced: the link\n // would ask for nothing, or for no amount at all.\n if (!CommerceModel.variantHasPrice(variant)) {\n return res.status(400).json({ error: `Set a price for ${product.name} before selling it.` })\n }\n\n // Merchant account like the storefront checkout (AGL-284).\n const ownerOrg = await getOrgForHost(hostId)\n // Plan gate, re-asked per request exactly as the storefront checkout\n // asks it (AGL-1873, the AGL-481 pattern): a free or lapsed org must not\n // keep minting payment links — at the free plan's 0% transaction fee —\n // through the one commerce door that skipped the entitlement. Sits\n // ABOVE the attempt claim with the other deterministic refusals, so it\n // never burns the merchant's idempotency key.\n if (!Aglyn.checkEntitlement(ownerOrg?.org as any, 'commerce')) {\n return res.status(403).json({ error: 'Selling is not enabled' })\n }\n const ownerId = ownerOrg?.org?.ownerUid\n const ownerProfile = ownerId\n ? await firestore.collection('profiles').doc(String(ownerId)).get()\n : null\n const accountId = ownerProfile?.get('stripeAccountId')\n if (\n !merchantAccountIsReady(\n {\n accountId,\n chargesEnabled: ownerProfile?.get('stripeChargesEnabled'),\n accountLivemode: ownerProfile?.get('stripeAccountLivemode'),\n },\n { subject: `draft order host ${hostId}` },\n )\n ) {\n return res.status(409).json({ error: 'Payments are not set up yet' })\n }\n\n // Stripe returns the merchant to the console Products page. This was\n // `/{hostDocId}/products`, the pre-AGL-621/622 shape, so paying (or\n // cancelling) a draft order landed on a 404 (AGL-685). Console products\n // is the plugin route keyed by org slug + host SUBDOMAIN; server code\n // cannot call useConsoleHostRoute, so resolve them here. Without both,\n // fall back to the origin root rather than a fabricated dead path.\n const consoleSubdomain = (\n await firestore.collection('hostIndex').doc(hostId).get()\n ).get('subdomain') as string | undefined\n const consoleOrgSlug = ownerOrg?.org?.slug as string | undefined\n // The console's own host, as the POS door reads it — never the `Origin`\n // header (AGL-3363). The CUSTOMER lands here after paying the link, so a\n // caller-written origin would send a merchant's customer anywhere.\n const origin = `https://${req.headers.host}`\n const consoleProductsUrl =\n consoleOrgSlug && consoleSubdomain\n ? `${origin}${buildRoute(Route.HOST_PLUGIN, {\n orgSlug: consoleOrgSlug,\n host: consoleSubdomain,\n pluginSlug: 'products',\n })}`\n : origin\n\n // WHAT THE COUNT SAYS, said out loud (AGL-2452).\n //\n // This handler contained no `canPurchase` and no `stockShortfall` — no\n // reference to inventory of any kind. It is the door that mints a LIVE,\n // payable Stripe link, and it would mint one for a sold-out variant whose\n // merchant had chosen `oversellPolicy: 'deny'`, with the `commerce-draft`\n // webhook branch then completing it into a `paid` order. `pos-order.ts`'s\n // own comment asserts \"every storefront door gates on it\"; that claim was\n // false for this one.\n //\n // WARN, NEVER BLOCK, matching the register (AGL-2357, the decision).\n // Both of these are MERCHANT-initiated doors rather than shopper-initiated\n // ones — the merchant may well be taking a deliberate pre-order or\n // backorder — so a stale count must not refuse a sale the merchant means to\n // make. What the merchant is entitled to is to be TOLD, which is the part\n // that was missing entirely. The refusal variant is a separate decision and\n // is not taken here.\n //\n // Computed from the SERVER's product document, and reported on the same\n // `stockWarnings` key and in the same `StockShortfall` shape the register\n // already returns, so one console surface can read either door.\n const shortfall = CommerceModel.stockShortfall(\n product,\n variantId || product.variants[0]?.id,\n quantity,\n )\n const stockWarnings: CommerceModel.StockShortfall[] = shortfall\n ? [\n {\n productId,\n ...(variantId ? { variantId } : {}),\n name: product.name,\n requested: quantity,\n available: shortfall.available,\n },\n ]\n : []\n\n const unitAmountCents = Math.round(Number(variant.priceUsd) * 100)\n const lineItems: CommerceModel.OrderLineItem[] = [\n {\n productId,\n ...(variantId ? { variantId } : {}),\n name: product.name,\n ...(variant.sku ? { sku: variant.sku } : {}),\n productType: product.type,\n quantity,\n unitAmountCents,\n },\n ]\n const itemsCents = unitAmountCents * quantity\n // THE DISCOUNTS HUB, ON THIS PATH TOO (AGL-305).\n //\n // `hosts/{hostId}/discounts` reached the cart and buy-now and stopped\n // there, so a store running \"Summer sale, 10% off\" sold at 10% off through\n // both storefront doors and at FULL price the moment the merchant invoiced\n // the same buyer for the same goods through a payment link. No code has to\n // be typed for that gap to open: an automatic promotion applies itself on\n // the storefront and applied itself nowhere here, so the merchant's own\n // payment link quietly undercut — and here overcharged against — their own\n // advertised price.\n //\n // Resolved through the SAME `resolveDiscount` the other doors use, so one\n // code cannot mean two amounts.\n //\n // RESOLVED BEFORE THE TAX BLOCK BELOW, and that ordering is the whole\n // point rather than a convenience: the manual rate is computed against\n // `chargedItemsCents`, so the discount lands BEFORE tax and the taxable\n // base is the discounted one. It has to be — the session applies that same\n // rate as `line_items[0][tax_rates][0]`, which Stripe evaluates against the\n // line amount it actually charges. Reducing the line while taxing the list\n // price would make our stored `taxCents` disagree with the tax Stripe\n // collects, on every discounted draft.\n const draftDiscountLines = [{ productId, amountCents: itemsCents }]\n const hubDiscounts = await hostRef.collection('discounts').limit(100).get()\n const resolvedDiscount = CommerceModel.resolveDiscount(\n hubDiscounts.docs.map((docSnapshot) => ({\n ...(docSnapshot.data() as CommerceModel.HostDiscount),\n $id: docSnapshot.id,\n })),\n {\n ...(couponCode ? { code: couponCode } : {}),\n subtotalCents: itemsCents,\n productIds: [productId],\n lines: draftDiscountLines,\n },\n )\n // Above the claim and above the order write with every other deterministic\n // refusal, so a merchant who fixes the code and presses Send again strands\n // no `pending` draft and burns no key. A code that RESOLVES but confers\n // nothing leaves through here too — `codeProblem` carries the benefit's own\n // reason — which is what stops this door repeating the defect that charged\n // full shipping on a free-shipping code.\n if (resolvedDiscount?.codeProblem) {\n return res.status(400).json({ error: resolvedDiscount.codeProblem })\n }\n const appliedDiscountId =\n resolvedDiscount && resolvedDiscount.benefit.kind !== 'none'\n ? resolvedDiscount.discountId\n : ''\n const discountCents = appliedDiscountId ? resolvedDiscount!.discountCents : 0\n // Stripe's charge minimum is 50¢ — never price a payment link below it,\n // the same floor buy-now applies.\n const chargedItemsCents = discountCents\n ? Math.max(50, itemsCents - discountCents)\n : itemsCents\n // What was ACTUALLY taken off, after that floor. The stored totals record\n // this rather than the resolver's figure, so the parts sum to the total on\n // a draft the floor moved.\n const appliedDiscountCents = itemsCents - chargedItemsCents\n // The fee and `totals` are both built BELOW the shipping plan (AGL-2152):\n // Stripe's card cost rides the whole payment-link total, so neither can be\n // known until the tax (AGL-1953) and the shipping options are.\n\n // Shipping (AGL-1792), from the same settings document the two storefront\n // paths read. This handler contained no `shipping` string at all: a\n // merchant who configured zones and rates collected them on a cart\n // (AGL-1707) and on buy-now (AGL-1720), and nothing when they invoiced the\n // same buyer for the same parcel through a payment link. The session\n // collected no address either, so the merchant was not even told where to\n // send it — the webhook records one now that Stripe is asked for it.\n //\n // The premise AGL-1792 filed this under was that \"the destination IS known\n // — the merchant typed it\". It is not: the draft dialog collects a product,\n // a variant, a quantity and an email, and this handler reads exactly those.\n // So the destination is as unknown here as it is in the cart, and the same\n // asking machinery applies rather than none of it.\n //\n // CONDITIONAL on the product being physical, exactly as buy-now is: this\n // dialog invoices downloads and consulting too, and charging that buyer\n // postage would be worse than the bug. A missing `type` reads as physical,\n // matching the fee ladder above — the two reads must not disagree about\n // what a part-migrated doc is. The one-time condition buy-now also carries\n // is structural here: this path only ever builds `mode: 'payment'`.\n //\n // RESOLVED BEFORE THE ORDER DOCUMENT, because the plan can refuse and the\n // order is written before Stripe is called. Refusing after it would strand\n // a `pending` draft on the merchant's orders list for every attempt —\n // this path's version of the orphaned coupon AGL-1721 kept out of the cart.\n const storeSettings = await hostRef\n .collection('settings')\n .doc('store')\n .get()\n const shippingSettings = storeSettings.get('shipping') as\n CommerceModel.ShippingSettings | undefined\n\n // Taxes (AGL-1953), from the same settings document. This handler had no\n // tax code at all in either mode, so a merchant who invoiced a buyer for\n // the same product they sell on the storefront charged them no tax — the\n // cart's defect, one door over, and worse here because the merchant\n // composed this order deliberately and would reasonably assume it matched\n // their store.\n //\n // Origin-based, exactly as buy-now and the cart resolve it. The AGL-1792\n // note above already established the premise: this dialog collects a\n // product, a variant, a quantity and an email, so the DESTINATION is as\n // unknown here as it is in the cart.\n //\n // Computed pure and BEFORE the claim so the stored totals can carry it;\n // the Stripe object it needs is minted below the point of no return.\n const taxSettings = (storeSettings.get('tax') ?? {}) as CommerceModel.TaxSettings\n // AGL-1999: an unset tax mode is NOT a decision, and it must not sell.\n // `mode` was `undefined` for every store whose owner never opened the\n // Taxes card — the default state of every new storefront — and each\n // branch below tested for a specific string, so no branch was taken, no\n // tax was computed, and the shopper was charged an untaxed total. See\n // `commerce-tax-decision.ts` for why refusing beats defaulting to either\n // mode. `mode: 'none'` is the explicit opt-out and passes straight\n // through.\n const taxDecision = CommerceModel.storefrontTaxDecision({\n settings: taxSettings,\n taxExempt: product.taxExempt,\n })\n if (taxDecision.kind === 'undecided') {\n return res\n .status(409)\n .json({ error: CommerceModel.STOREFRONT_TAX_UNDECIDED_MESSAGE })\n }\n const taxMisconfigured =\n CommerceModel.storefrontTaxMisconfiguration(taxSettings)\n if (taxMisconfigured) {\n // AGL-2145: the merchant DECIDED to collect and the sale would have\n // charged zero anyway — an unfinished settings card, not a\n // jurisdiction miss. Refused in the same words and the same place as\n // the undecided case above.\n return res.status(409).json({ error: taxMisconfigured })\n }\n const useStripeTax = taxDecision.kind === 'stripe-automatic'\n const manualRate =\n taxDecision.kind === 'manual' &&\n !taxSettings.pricesIncludeTax\n ? CommerceModel.resolveTaxRate(taxSettings, taxSettings.origin ?? {})\n : null\n // THE DISCOUNTED BASE (AGL-305). `chargedItemsCents` is what the line\n // will actually be worth, and the session applies this same rate to that\n // line as a Stripe Tax Rate — so taxing `itemsCents` here would record a\n // figure Stripe never charges.\n const taxCents = manualRate\n ? CommerceModel.computeTaxCents(chargedItemsCents, manualRate.pct)\n : 0\n // Stripe Tax mode leaves this 0 at composition time and the webhook folds\n // the computed figure in — the same additive shape AGL-1792 used for\n // shipping, and for the same reason: only Stripe knows it.\n // Live carrier rates (AGL-3612), quoted for the destination the manager\n // names; see `carrier-shipping.ts`.\n const shippingPlan: CarrierShippingPlan =\n (product.type ?? 'physical') === 'physical'\n ? await planCheckoutShippingWithCarriers({\n hostId,\n settings: shippingSettings,\n cart: {\n subtotalCents: itemsCents,\n totalGrams:\n Math.max(0, Number(variant.weightGrams ?? 0)) * quantity,\n ...(quantity === 1 ? { parcel: CommerceModel.productParcelDimensions(product) } : {}),\n },\n destination: {\n country: body.shippingCountry,\n postalCode: body.shippingPostalCode,\n },\n })\n : { countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES, options: [] }\n if (shippingPlan.needsPostalCode) {\n return res.status(400).json({\n error: 'Enter the postal code this order ships to.',\n needsShippingCountry: true,\n needsShippingPostalCode: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n if (shippingPlan.refusal === 'destination-required') {\n return res.status(400).json({\n error: 'Choose where this order ships to.',\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n if (shippingPlan.refusal === 'destination-unserved') {\n const declared = CommerceModel.normalizeCheckoutShippingCountry(\n body.shippingCountry,\n )\n return res.status(409).json({\n error: `This store does not ship to ${\n CommerceModel.CHECKOUT_SHIPPING_COUNTRY_NAMES[declared as string] ??\n 'that country'\n }.`,\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n // No rate of the merchant's reaches this order (AGL-2232). Above the\n // order write and the payment link, like every other refusal here, so a\n // manager who fixes the tier table and presses Send again strands nothing.\n if (shippingPlan.refusal === 'cart-unpriceable') {\n return res\n .status(409)\n .json({ error: CommerceModel.CART_UNPRICEABLE_SHIPPING_MESSAGE })\n }\n // FREE SHIPPING IS A ZEROED RATE, NOT A REDUCTION (AGL-305), the same\n // construction the cart uses and for the same reason: nothing has picked a\n // rate yet, so there is no amount to take off. The buyer chooses after the\n // link is minted, so the only exact answer is to offer every rate at zero.\n //\n // Every rate rather than the cheapest, because `free_shipping` carries no\n // field naming one — and zeroing them all keeps the fee, the transfer floor\n // and the ceiling below honest, since all three read these same options.\n //\n // Derived from `shippingPlan.options` and read EVERYWHERE below in its\n // place; a reader that reached past this to `shippingPlan.options` would\n // price carriage the buyer is not being charged for.\n const shippingOptions =\n resolvedDiscount?.freeShipping === true\n ? shippingPlan.options.map((option) => ({ ...option, amountCents: 0 }))\n : shippingPlan.options\n\n // THE PLATFORM FEE (AGL-2152) — the advertised take PLUS Stripe's card\n // cost, passed through at cost. A payment link is a destination charge, so\n // Stripe debits 2.9% + 30¢ from the PLATFORM's balance; the old take-only\n // figure meant every tier carrying a 0% rate invoiced a buyer at a loss to\n // Aglyn, and Starter's 2% is below 2.9% so it lost money at every size too.\n //\n // `?? 'physical'` (AGL-2251), matching `checkout.ts` and the cart. Without\n // it a product doc carrying no `type` fell to `resolveTransactionFeePct`'s\n // digital branch, so a merchant's payment link took a different cut of the\n // same product than their storefront did.\n //\n // The base is the whole card total: the goods, the manual tax line, and the DEAREST shipping\n // option the link offers (the buyer picks after the link is minted and\n // `application_fee_amount` is fixed at creation, so reaching for the\n // cheapest would put the difference back on Aglyn). A store on Stripe Tax\n // is the one residual — that tax is computed inside Stripe afterwards.\n const shippingCeilingCents = shippingOptions.reduce(\n (most, option) =>\n Math.max(most, Math.max(0, Number(option.amountCents ?? 0))),\n 0,\n )\n // Both bases are the DISCOUNTED goods (AGL-305). The platform take is a\n // cut of what the merchant actually sells for, and Stripe's card cost is\n // passed through on what the card actually runs for — reading `itemsCents`\n // here would invoice the merchant a fee on money nobody paid.\n const feeCents = Aglyn.resolveTransactionFeeCents(\n ownerOrg?.org as any,\n product.type ?? 'physical',\n chargedItemsCents,\n chargedItemsCents + taxCents + shippingCeilingCents,\n )\n // Stripe Tax mode leaves the tax 0 at composition time and the webhook\n // folds the computed figure in — the same additive shape AGL-1792 used for\n // shipping, and for the same reason: only Stripe knows it.\n const totals = CommerceModel.computeOrderTotals(lineItems, {\n feeCents,\n taxCents,\n // The line item still carries the LIST price, so the discount has to be\n // a named part of the totals or `itemsCents - discountCents + tax` would\n // not be the number on the payment link.\n discountCents: appliedDiscountCents,\n })\n\n // THE LINK CHARGES `chargedItemsCents`, EXACTLY (the AGL-2159 construction,\n // reused here because this path grew the same problem the moment it grew a\n // discount).\n //\n // A Stripe line is `quantity` units at ONE integer `unit_amount`, so it can\n // only express totals divisible by `quantity` — and a percentage discount\n // does not respect that. Dividing and multiplying back misses the intended\n // total by up to `quantity / 2` cents in either direction, which on this\n // path is worse than on buy-now: the frozen `totals` above are written to\n // the order document BEFORE the session is minted, so a link that charged\n // a different number would leave the merchant's own books disagreeing with\n // what their buyer paid, permanently and silently.\n //\n // So when the total does not divide evenly, the line becomes ONE unit at\n // the exact total with the count moved into the display name. The real\n // quantity is carried by the order document's line item, which is\n // untouched, so nothing downstream can tell the difference.\n //\n // The even case — every undiscounted draft, and most discounted ones —\n // keeps the per-unit shape byte for byte.\n const evenUnitAmountCents = Math.floor(chargedItemsCents / quantity)\n const remainderCents = chargedItemsCents - evenUnitAmountCents * quantity\n const chargedQuantity = remainderCents === 0 ? quantity : 1\n const chargedUnitAmountCents =\n remainderCents === 0 ? evenUnitAmountCents : chargedItemsCents\n const chargedLineName = `${product.name}${\n remainderCents === 0 ? '' : ` × ${quantity}`\n }`.slice(0, 120)\n\n // Point of no return (AGL-1697): everything past here writes an order and\n // mints a live payment link. Every deterministic refusal — unknown\n // product, payments not set up, the shipping-destination ask — sits above\n // this line, so none of them burns the key: the merchant fixes the input\n // and presses the same button. Before the claim, a double-submit minted a\n // SECOND order doc, burned a second sequential number, and returned two\n // live payment links bound to different orders, both payable.\n const claimed = await claimAttempt(firestore, {\n kind: 'commerce-draft',\n scopeId: hostId,\n orgId: String(ownerOrg?.org?.id ?? ''),\n key: idempotencyKey,\n busyMessage: 'This draft order is already being created',\n })\n if ('replay' in claimed) {\n return res.status(claimed.replay.status).json(claimed.replay.body)\n }\n claim = claimed.claim\n\n // THE REDEMPTION SLOT IS RESERVED HERE (AGL-305), on the same terms the\n // storefront doors reserve it.\n //\n // A payment link lives until the buyer pays it, so between this handler and\n // the `commerce-draft` webhook branch that settles the slot there is a\n // window measured in days — far wider than a Checkout Session's. Reading\n // `redemptions` and hoping would let a merchant mint ten links against a\n // cap of one and have all ten pay.\n //\n // BELOW the claim because the hold is keyed by the attempt: a retry of one\n // attempt must be able to re-claim the slot it already holds rather than be\n // refused by it. An uncapped promotion writes nothing and returns an empty\n // `holdKey`, which is what tells the webhook to fall back to the plain\n // increment.\n if (appliedDiscountId) {\n const slot = await holdPromotionSlot({\n firestore,\n ref: hostRef.collection('discounts').doc(appliedDiscountId),\n holdKey: promotionHoldKey(claimed.claim.stripeKey),\n label: `draft discount ${appliedDiscountId}`,\n })\n if (!slot.ok) {\n // Refused rather than quietly repriced, and that is deliberate for the\n // AUTOMATIC case too: the merchant composed this order at a price the\n // promotion was part of, and minting the link at full price would\n // invoice their buyer more than the store advertises without saying so.\n // Nothing has been written yet, so the key goes back and the retry\n // prices correctly — `resolveDiscount` counts live holds and simply\n // stops offering an exhausted promotion.\n await claim.release()\n return res.status(409).json({\n error:\n couponCode && resolvedDiscount?.discount.code\n ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE\n : CommerceModel.PROMOTION_UNAVAILABLE_MESSAGE,\n })\n }\n if (slot.holdKey) {\n discountSlot = slot\n discountHoldKey = slot.holdKey\n }\n }\n\n // Order doc first (transactional number), then the payment link.\n const orderRef = hostRef.collection('orders').doc()\n const counterRef = hostRef.collection('counters').doc('orders')\n await firestore.runTransaction(async (transaction) => {\n const counter = await transaction.get(counterRef)\n const number = Number(counter.get('next') ?? 1)\n transaction.set(counterRef, { next: number + 1 }, { merge: true })\n // With the fields the orders list queries by (AGL-3321).\n transaction.set(orderRef, CommerceModel.withOrderListFields(orderRef.id, {\n number,\n status: 'pending',\n channel: 'draft',\n lineItems,\n totals,\n // WHICH TAX REGIME THIS ORDER IS COMPOSED UNDER (AGL-2451), from the\n // decision taken above rather than from the frozen `taxCents` — a\n // Stripe Tax draft freezes 0 there and only the paid session knows the\n // figure, so a mode read back off the amount would call it `none`.\n //\n // Stamped at composition so a draft that is never paid still says what\n // it was priced under, and RESTATED by the `commerce-draft` webhook\n // branch from the session that actually charged it. The webhook's is\n // authoritative — it has Stripe's computed tax in hand — and for a\n // manual draft the two agree by construction.\n taxMode: CommerceModel.storefrontTaxModeForDecision(\n taxDecision,\n taxCents,\n ),\n customerEmail: email || null,\n timeline: [\n {\n atMs: Date.now(),\n event: 'draft',\n detail: `Draft created by ${decoded.email ?? decoded.uid}`,\n },\n ],\n createdAtMs: Date.now(),\n createdAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n }))\n })\n\n // The tax rate is minted here, BELOW the claim, because it creates a real\n // Stripe object (AGL-1953). Cached per host, so it is usually a read.\n let draftTaxRateId = ''\n if (taxCents > 0 && manualRate) {\n draftTaxRateId =\n (await resolveManualTaxRateId({\n hostRef,\n taxPct: manualRate.pct,\n taxLabel: manualRate.label || `Tax (${manualRate.pct}%)`,\n ...(claim.stripeKey\n ? { headers: { 'Idempotency-Key': `${claim.stripeKey}-tax-rate` } }\n : {}),\n })) ?? ''\n if (!draftTaxRateId) {\n // A VISIBLE refusal rather than an untaxed payment link — the silent\n // under-collection is the defect. The order doc is rolled back the\n // same way the session failure below rolls it back.\n await orderRef.delete().catch(() => undefined)\n await releaseDiscountSlot()\n await claim.release()\n return res.status(502).json({ error: 'Payment link creation failed' })\n }\n }\n const params = new URLSearchParams({\n mode: 'payment',\n // 3-D Secure, from the one seam every card payment shares (AGL-3360).\n ...checkoutSessionCardAuthenticationParams('payment'),\n ...(useStripeTax ? { 'automatic_tax[enabled]': 'true' } : {}),\n ...(draftTaxRateId ? { 'line_items[0][tax_rates][0]': draftTaxRateId } : {}),\n 'line_items[0][quantity]': String(chargedQuantity),\n 'line_items[0][price_data][currency]': 'usd',\n 'line_items[0][price_data][unit_amount]': String(chargedUnitAmountCents),\n 'line_items[0][price_data][product_data][name]': chargedLineName,\n // THE CONNECT SPLIT (AGL-1956). A Stripe Tax payment link computes its\n // tax against AGLYN's registrations and Aglyn remits it (AGL-1904), so\n // the merchant gets a FIXED `transfer_data[amount]` and no\n // `application_fee_amount`: the fee form transfers `amount_total − fee`,\n // and `amount_total` includes that tax. A manual-rate or untaxed link is\n // unchanged. See `commerce-connect-transfer.ts`.\n ...CommerceModel.destinationChargeParams({\n accountId: String(accountId),\n feeCents,\n taxOwner: useStripeTax ? 'platform' : 'merchant',\n // The DISCOUNTED goods (AGL-305): this is what the merchant is\n // transferred, and paying out the list price on a discounted link\n // would move money Stripe never collected.\n merchantGoodsCents: chargedItemsCents,\n shippingFloorCents: CommerceModel.shippingFloorCents(shippingOptions),\n }),\n ...(email ? { customer_email: email } : {}),\n success_url: `${consoleProductsUrl}?draft=paid`,\n cancel_url: `${consoleProductsUrl}?draft=canceled`,\n 'metadata[type]': 'commerce-draft',\n 'metadata[hostId]': hostId,\n 'metadata[orderId]': orderRef.id,\n 'metadata[productId]': productId,\n ...(variantId ? { 'metadata[variantId]': variantId } : {}),\n 'metadata[feeCents]': String(feeCents),\n ...(shippingPlan.quotedPostalCode\n ? { 'metadata[shippingQuotePostalCode]': shippingPlan.quotedPostalCode }\n : {}),\n // WHAT THE WEBHOOK SETTLES (AGL-305), in the field names the\n // `checkout.session.completed` branch already reads for the cart. The\n // hold key's ABSENCE is what tells it to fall back to the unconditional\n // increment, which is right for an uncapped promotion.\n ...(appliedDiscountId\n ? { 'metadata[discountId]': appliedDiscountId }\n : {}),\n ...(discountHoldKey ? { 'metadata[discountHoldKey]': discountHoldKey } : {}),\n // See the AGL-1956 note in `checkout.ts` — recorded only on the shape\n // where the merchant's payout is no longer the whole charge.\n ...(useStripeTax\n ? {\n 'metadata[transferCents]': String(\n CommerceModel.platformLiableTransferCents({\n feeCents,\n merchantGoodsCents: chargedItemsCents,\n shippingFloorCents:\n CommerceModel.shippingFloorCents(shippingOptions),\n }),\n ),\n 'metadata[transferShippingCents]': String(\n CommerceModel.shippingFloorCents(shippingOptions),\n ),\n }\n : {}),\n })\n // Both keys or neither (AGL-1720's rule, inherited). Stripe will not apply\n // a shipping rate without an address to ship to, and a merchant who\n // configured nothing plans to no options and so gets neither — a payment\n // link byte-identical to the one this handler built before shipping\n // existed on it.\n if (shippingOptions.length > 0) {\n CommerceModel.appendShippingAddressCollectionParams(\n params,\n shippingPlan.countries,\n )\n CommerceModel.appendCheckoutShippingParams(params, shippingOptions)\n }\n // The payment methods the merchant switched off reach the payment link\n // too (AGL-3629). A hosted Stripe page shows Apple Pay and Google Pay by\n // device and cannot hide them per merchant; everything else follows.\n CommerceModel.appendStorefrontPaymentMethodParams(\n params,\n CommerceModel.resolveStorefrontPaymentMethodControls(\n CommerceModel.normalizeStorefrontPaymentMethodSettings(\n storeSettings.get('paymentMethods'),\n ),\n ),\n )\n const response = await fetch(\n 'https://api.stripe.com/v1/checkout/sessions',\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n // The half that is a live payment link (AGL-1697). Stripe replays\n // the existing session for a repeated key instead of opening a\n // second one on the merchant's account, which covers the window\n // where our claim is written but the response never arrives.\n ...(claim.stripeKey ? { 'Idempotency-Key': claim.stripeKey } : {}),\n },\n body: params.toString(),\n },\n )\n const session = (await response.json()) as {\n url?: string\n id?: string\n error?: any\n }\n if (!response.ok || !session.url) {\n console.error('Stripe draft-order error', session.error)\n await orderRef.delete().catch(() => undefined)\n // The draft did not happen — hand the key back rather than locking this\n // dialog out over one flaky moment. The retry re-derives the SAME\n // digest, so if Stripe did create the session it replays it.\n await releaseDiscountSlot()\n await claim.release()\n return res.status(502).json({ error: 'Payment link creation failed' })\n }\n await orderRef.set(\n { checkoutSessionId: session.id, paymentLinkUrl: session.url },\n { merge: true },\n )\n // Spread on the same terms as the register's: present only when there is\n // something to say, so a caller can test the key rather than a length.\n const payload = {\n orderId: orderRef.id,\n url: session.url,\n ...(stockWarnings.length > 0 ? { stockWarnings } : {}),\n }\n await claim.record(200, payload)\n return res.status(200).json(payload)\n } catch (error) {\n console.error(error)\n // Release on the way out so a transient failure does not strand the key\n // (AGL-1691's rule). The order write may or may not have landed; the\n // merchant reconciles from the orders list, the same position they were\n // in before this endpoint had a claim at all. The redemption slot goes\n // back for the same reason — a throw is the one path that would otherwise\n // leave it standing against the cap until its TTL lapsed.\n await releaseDiscountSlot()\n await claim?.release()\n return res.status(500).json({ error: 'Draft order failed' })\n }\n}\n"],"names":["Aglyn","CommerceModel","firebaseAdmin","getOrgForHost","merchantAccountIsReady","checkoutSessionCardAuthenticationParams","planCheckoutShippingWithCarriers","resolveManualTaxRateId","holdPromotionSlot","promotionHoldKey","buildRoute","claimAttempt","Route","draftOrderHandler","req","res","body","method","status","json","error","process","env","STRIPE_SECRET_KEY","authorization","String","headers","idToken","startsWith","slice","length","undefined","JSON","parse","hostId","productId","variantId","quantity","Math","max","min","round","Number","email","trim","couponCode","idempotencyKey","claim","discountSlot","discountHoldKey","releaseDiscountSlot","slot","release","hostSnapshot","storeSettings","taxSettings","product","variant","ownerOrg","decoded","app","auth","verifyIdToken","firestore","hostRef","collection","doc","get","exists","memberRole","uid","productSnapshot","raw","data","deletedAt","liftLegacyProduct","variants","find","item","id","variantHasPrice","name","checkEntitlement","org","ownerId","ownerUid","ownerProfile","accountId","chargesEnabled","accountLivemode","subject","consoleSubdomain","consoleOrgSlug","slug","origin","host","consoleProductsUrl","HOST_PLUGIN","orgSlug","pluginSlug","shortfall","stockShortfall","stockWarnings","requested","available","unitAmountCents","priceUsd","lineItems","sku","productType","type","itemsCents","draftDiscountLines","amountCents","hubDiscounts","limit","resolvedDiscount","resolveDiscount","docs","map","docSnapshot","$id","code","subtotalCents","productIds","lines","codeProblem","appliedDiscountId","benefit","kind","discountId","discountCents","chargedItemsCents","appliedDiscountCents","shippingSettings","taxDecision","storefrontTaxDecision","settings","taxExempt","STOREFRONT_TAX_UNDECIDED_MESSAGE","taxMisconfigured","storefrontTaxMisconfiguration","useStripeTax","manualRate","pricesIncludeTax","resolveTaxRate","taxCents","computeTaxCents","pct","shippingPlan","cart","totalGrams","weightGrams","parcel","productParcelDimensions","destination","country","shippingCountry","postalCode","shippingPostalCode","countries","CHECKOUT_SHIPPING_COUNTRIES","options","needsPostalCode","needsShippingCountry","needsShippingPostalCode","shippingCountries","refusal","declared","normalizeCheckoutShippingCountry","CHECKOUT_SHIPPING_COUNTRY_NAMES","CART_UNPRICEABLE_SHIPPING_MESSAGE","shippingOptions","freeShipping","option","shippingCeilingCents","reduce","most","feeCents","resolveTransactionFeeCents","totals","computeOrderTotals","evenUnitAmountCents","floor","remainderCents","chargedQuantity","chargedUnitAmountCents","chargedLineName","claimed","scopeId","orgId","key","busyMessage","replay","ref","holdKey","stripeKey","label","ok","discount","PROMOTION_EXHAUSTED_MESSAGE","PROMOTION_UNAVAILABLE_MESSAGE","orderRef","counterRef","runTransaction","transaction","counter","number","set","next","merge","withOrderListFields","channel","taxMode","storefrontTaxModeForDecision","customerEmail","timeline","atMs","Date","now","event","detail","createdAtMs","createdAt","FieldValue","serverTimestamp","draftTaxRateId","taxPct","taxLabel","delete","catch","params","URLSearchParams","mode","destinationChargeParams","taxOwner","merchantGoodsCents","shippingFloorCents","customer_email","success_url","cancel_url","quotedPostalCode","platformLiableTransferCents","appendShippingAddressCollectionParams","appendCheckoutShippingParams","appendStorefrontPaymentMethodParams","resolveStorefrontPaymentMethodControls","normalizeStorefrontPaymentMethodSettings","response","fetch","Authorization","toString","session","url","console","checkoutSessionId","paymentLinkUrl","payload","orderId","record"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/draft-order.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 { holdsCommerceSiteKey } from './commerce-site-keys'\nimport { commerceSitePermissionRefusal } from '../model/plugin-permissions'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { merchantAccountIsReady } from '@aglyn/tenant-data-admin/server/payment-provider'\nimport { checkoutSessionCardAuthenticationParams } from '@aglyn/tenant-data-admin/server/stripe-card-authentication'\nimport {\n planCheckoutShippingWithCarriers,\n type CarrierShippingPlan,\n} from './carrier-shipping'\nimport { resolveManualTaxRateId } from './manual-tax-rate'\nimport {\n type PromotionSlotHold,\n holdPromotionSlot,\n promotionHoldKey,\n} from './promotion-hold'\nimport {\n buildRoute,\n claimAttempt,\n Route,\n type AttemptClaim,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\n\n/**\n * Draft orders (AGL-287, Shopify parity): a manager builds an order in\n * the console and sends the buyer a payment link. Creates the order doc\n * (status `pending`, channel `draft`) with a sequential number, then a\n * Stripe Checkout session on the merchant's connected account whose\n * webhook completion flips the SAME doc to `paid`\n * (metadata type `commerce-draft`).\n */\nexport const draftOrderHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n if (!process.env.STRIPE_SECRET_KEY) {\n return res.status(501).json({ error: 'Payments are not configured.' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const body =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const productId = String(body.productId ?? '')\n const variantId = String(body.variantId ?? '')\n const quantity = Math.max(1, Math.min(99, Math.round(Number(body.quantity ?? 1))))\n const email = String(body.email ?? '').trim()\n // A code the MERCHANT types while composing the order (AGL-305). The\n // storefront doors read this same field name, so one resolver answers it the\n // same way whoever pressed the button.\n const couponCode = String(body.couponCode ?? '').trim()\n // One draft attempt, minted by the console dialog (AGL-1697). Node\n // lowercases incoming headers, but read both spellings — the plugin API\n // request type makes no promise about casing.\n const idempotencyKey = String(\n req.headers['idempotency-key'] ?? req.headers['Idempotency-Key'] ?? '',\n ).trim().slice(0, 200)\n if (!hostId || !productId) {\n return res.status(400).json({ error: 'Missing hostId or productId' })\n }\n\n let claim: AttemptClaim | null = null\n /**\n * The redemption slot this attempt reserved (AGL-305), hoisted so every\n * refusal below the claim — and the catch — can hand it back. A throw after\n * the hold is placed is exactly the case where a slot would otherwise stand\n * invisibly against the merchant's cap until its TTL lapsed, and this path\n * rolls its order document back on two of those refusals already.\n */\n let discountSlot: PromotionSlotHold | null = null\n let discountHoldKey = ''\n const releaseDiscountSlot = async (): Promise<void> => {\n const slot = discountSlot\n discountSlot = null\n discountHoldKey = ''\n if (slot) await slot.release()\n }\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n // AN ALLOWLIST (AGL-2262), matching `cancel-order.ts`, `fulfill-order.ts`\n // and the Firestore rules, whose host-write predicate is\n // `hostMemberRole(hostId) in ['admin','editor']`. The old\n // `!role || role === 'viewer'` denylist caught the stranger but admitted\n // every OTHER string — a legacy value, a typo, any role added later — on\n // the one route that mints a live Stripe payment link. `HostAccessRole`\n // has since grown an `author` (AGL-2334), which this allowlist refuses on\n // purpose — an author edits content and does not mint payment links — and\n // which the old denylist would have admitted. That is the allowlist form\n // earning its keep on a union that changed under it.\n // \"Manage orders\" (`commerce.orders`, AGL-4047), whose preset is the site's Admin\n // and Editor this asked for, read as the rules would read it.\n if (!holdsCommerceSiteKey(hostSnapshot, decoded.uid, 'commerce.orders')) {\n return res.status(403).json({ error: commerceSitePermissionRefusal('commerce.orders') })\n }\n const productSnapshot = await hostRef\n .collection('products')\n .doc(productId)\n .get()\n const raw = productSnapshot.data() as any\n if (!raw || raw.deletedAt) {\n return res.status(404).json({ error: 'Unknown product' })\n }\n const product = CommerceModel.liftLegacyProduct(raw)\n const variant = variantId\n ? product.variants.find((item) => item.id === variantId)\n : product.variants[0]\n if (!variant) return res.status(404).json({ error: 'Unknown variant' })\n // A variant with no price yet (AGL-2916) cannot be invoiced: the link\n // would ask for nothing, or for no amount at all.\n if (!CommerceModel.variantHasPrice(variant)) {\n return res.status(400).json({ error: `Set a price for ${product.name} before selling it.` })\n }\n\n // Merchant account like the storefront checkout (AGL-284).\n const ownerOrg = await getOrgForHost(hostId)\n // Plan gate, re-asked per request exactly as the storefront checkout\n // asks it (AGL-1873, the AGL-481 pattern): a free or lapsed org must not\n // keep minting payment links — at the free plan's 0% transaction fee —\n // through the one commerce door that skipped the entitlement. Sits\n // ABOVE the attempt claim with the other deterministic refusals, so it\n // never burns the merchant's idempotency key.\n if (!Aglyn.checkEntitlement(ownerOrg?.org as any, 'commerce')) {\n return res.status(403).json({ error: 'Selling is not enabled' })\n }\n const ownerId = ownerOrg?.org?.ownerUid\n const ownerProfile = ownerId\n ? await firestore.collection('profiles').doc(String(ownerId)).get()\n : null\n const accountId = ownerProfile?.get('stripeAccountId')\n if (\n !merchantAccountIsReady(\n {\n accountId,\n chargesEnabled: ownerProfile?.get('stripeChargesEnabled'),\n accountLivemode: ownerProfile?.get('stripeAccountLivemode'),\n },\n { subject: `draft order host ${hostId}` },\n )\n ) {\n return res.status(409).json({ error: 'Payments are not set up yet' })\n }\n\n // Stripe returns the merchant to the console Products page. This was\n // `/{hostDocId}/products`, the pre-AGL-621/622 shape, so paying (or\n // cancelling) a draft order landed on a 404 (AGL-685). Console products\n // is the plugin route keyed by org slug + host SUBDOMAIN; server code\n // cannot call useConsoleHostRoute, so resolve them here. Without both,\n // fall back to the origin root rather than a fabricated dead path.\n const consoleSubdomain = (\n await firestore.collection('hostIndex').doc(hostId).get()\n ).get('subdomain') as string | undefined\n const consoleOrgSlug = ownerOrg?.org?.slug as string | undefined\n // The console's own host, as the POS door reads it — never the `Origin`\n // header (AGL-3363). The CUSTOMER lands here after paying the link, so a\n // caller-written origin would send a merchant's customer anywhere.\n const origin = `https://${req.headers.host}`\n const consoleProductsUrl =\n consoleOrgSlug && consoleSubdomain\n ? `${origin}${buildRoute(Route.HOST_PLUGIN, {\n orgSlug: consoleOrgSlug,\n host: consoleSubdomain,\n pluginSlug: 'products',\n })}`\n : origin\n\n // WHAT THE COUNT SAYS, said out loud (AGL-2452).\n //\n // This handler contained no `canPurchase` and no `stockShortfall` — no\n // reference to inventory of any kind. It is the door that mints a LIVE,\n // payable Stripe link, and it would mint one for a sold-out variant whose\n // merchant had chosen `oversellPolicy: 'deny'`, with the `commerce-draft`\n // webhook branch then completing it into a `paid` order. `pos-order.ts`'s\n // own comment asserts \"every storefront door gates on it\"; that claim was\n // false for this one.\n //\n // WARN, NEVER BLOCK, matching the register (AGL-2357, the decision).\n // Both of these are MERCHANT-initiated doors rather than shopper-initiated\n // ones — the merchant may well be taking a deliberate pre-order or\n // backorder — so a stale count must not refuse a sale the merchant means to\n // make. What the merchant is entitled to is to be TOLD, which is the part\n // that was missing entirely. The refusal variant is a separate decision and\n // is not taken here.\n //\n // Computed from the SERVER's product document, and reported on the same\n // `stockWarnings` key and in the same `StockShortfall` shape the register\n // already returns, so one console surface can read either door.\n const shortfall = CommerceModel.stockShortfall(\n product,\n variantId || product.variants[0]?.id,\n quantity,\n )\n const stockWarnings: CommerceModel.StockShortfall[] = shortfall\n ? [\n {\n productId,\n ...(variantId ? { variantId } : {}),\n name: product.name,\n requested: quantity,\n available: shortfall.available,\n },\n ]\n : []\n\n const unitAmountCents = Math.round(Number(variant.priceUsd) * 100)\n const lineItems: CommerceModel.OrderLineItem[] = [\n {\n productId,\n ...(variantId ? { variantId } : {}),\n name: product.name,\n ...(variant.sku ? { sku: variant.sku } : {}),\n productType: product.type,\n quantity,\n unitAmountCents,\n },\n ]\n const itemsCents = unitAmountCents * quantity\n // THE DISCOUNTS HUB, ON THIS PATH TOO (AGL-305).\n //\n // `hosts/{hostId}/discounts` reached the cart and buy-now and stopped\n // there, so a store running \"Summer sale, 10% off\" sold at 10% off through\n // both storefront doors and at FULL price the moment the merchant invoiced\n // the same buyer for the same goods through a payment link. No code has to\n // be typed for that gap to open: an automatic promotion applies itself on\n // the storefront and applied itself nowhere here, so the merchant's own\n // payment link quietly undercut — and here overcharged against — their own\n // advertised price.\n //\n // Resolved through the SAME `resolveDiscount` the other doors use, so one\n // code cannot mean two amounts.\n //\n // RESOLVED BEFORE THE TAX BLOCK BELOW, and that ordering is the whole\n // point rather than a convenience: the manual rate is computed against\n // `chargedItemsCents`, so the discount lands BEFORE tax and the taxable\n // base is the discounted one. It has to be — the session applies that same\n // rate as `line_items[0][tax_rates][0]`, which Stripe evaluates against the\n // line amount it actually charges. Reducing the line while taxing the list\n // price would make our stored `taxCents` disagree with the tax Stripe\n // collects, on every discounted draft.\n const draftDiscountLines = [{ productId, amountCents: itemsCents }]\n const hubDiscounts = await hostRef.collection('discounts').limit(100).get()\n const resolvedDiscount = CommerceModel.resolveDiscount(\n hubDiscounts.docs.map((docSnapshot) => ({\n ...(docSnapshot.data() as CommerceModel.HostDiscount),\n $id: docSnapshot.id,\n })),\n {\n ...(couponCode ? { code: couponCode } : {}),\n subtotalCents: itemsCents,\n productIds: [productId],\n lines: draftDiscountLines,\n },\n )\n // Above the claim and above the order write with every other deterministic\n // refusal, so a merchant who fixes the code and presses Send again strands\n // no `pending` draft and burns no key. A code that RESOLVES but confers\n // nothing leaves through here too — `codeProblem` carries the benefit's own\n // reason — which is what stops this door repeating the defect that charged\n // full shipping on a free-shipping code.\n if (resolvedDiscount?.codeProblem) {\n return res.status(400).json({ error: resolvedDiscount.codeProblem })\n }\n const appliedDiscountId =\n resolvedDiscount && resolvedDiscount.benefit.kind !== 'none'\n ? resolvedDiscount.discountId\n : ''\n const discountCents = appliedDiscountId ? resolvedDiscount!.discountCents : 0\n // Stripe's charge minimum is 50¢ — never price a payment link below it,\n // the same floor buy-now applies.\n const chargedItemsCents = discountCents\n ? Math.max(50, itemsCents - discountCents)\n : itemsCents\n // What was ACTUALLY taken off, after that floor. The stored totals record\n // this rather than the resolver's figure, so the parts sum to the total on\n // a draft the floor moved.\n const appliedDiscountCents = itemsCents - chargedItemsCents\n // The fee and `totals` are both built BELOW the shipping plan (AGL-2152):\n // Stripe's card cost rides the whole payment-link total, so neither can be\n // known until the tax (AGL-1953) and the shipping options are.\n\n // Shipping (AGL-1792), from the same settings document the two storefront\n // paths read. This handler contained no `shipping` string at all: a\n // merchant who configured zones and rates collected them on a cart\n // (AGL-1707) and on buy-now (AGL-1720), and nothing when they invoiced the\n // same buyer for the same parcel through a payment link. The session\n // collected no address either, so the merchant was not even told where to\n // send it — the webhook records one now that Stripe is asked for it.\n //\n // The premise AGL-1792 filed this under was that \"the destination IS known\n // — the merchant typed it\". It is not: the draft dialog collects a product,\n // a variant, a quantity and an email, and this handler reads exactly those.\n // So the destination is as unknown here as it is in the cart, and the same\n // asking machinery applies rather than none of it.\n //\n // CONDITIONAL on the product being physical, exactly as buy-now is: this\n // dialog invoices downloads and consulting too, and charging that buyer\n // postage would be worse than the bug. A missing `type` reads as physical,\n // matching the fee ladder above — the two reads must not disagree about\n // what a part-migrated doc is. The one-time condition buy-now also carries\n // is structural here: this path only ever builds `mode: 'payment'`.\n //\n // RESOLVED BEFORE THE ORDER DOCUMENT, because the plan can refuse and the\n // order is written before Stripe is called. Refusing after it would strand\n // a `pending` draft on the merchant's orders list for every attempt —\n // this path's version of the orphaned coupon AGL-1721 kept out of the cart.\n const storeSettings = await hostRef\n .collection('settings')\n .doc('store')\n .get()\n const shippingSettings = storeSettings.get('shipping') as\n CommerceModel.ShippingSettings | undefined\n\n // Taxes (AGL-1953), from the same settings document. This handler had no\n // tax code at all in either mode, so a merchant who invoiced a buyer for\n // the same product they sell on the storefront charged them no tax — the\n // cart's defect, one door over, and worse here because the merchant\n // composed this order deliberately and would reasonably assume it matched\n // their store.\n //\n // Origin-based, exactly as buy-now and the cart resolve it. The AGL-1792\n // note above already established the premise: this dialog collects a\n // product, a variant, a quantity and an email, so the DESTINATION is as\n // unknown here as it is in the cart.\n //\n // Computed pure and BEFORE the claim so the stored totals can carry it;\n // the Stripe object it needs is minted below the point of no return.\n const taxSettings = (storeSettings.get('tax') ?? {}) as CommerceModel.TaxSettings\n // AGL-1999: an unset tax mode is NOT a decision, and it must not sell.\n // `mode` was `undefined` for every store whose owner never opened the\n // Taxes card — the default state of every new storefront — and each\n // branch below tested for a specific string, so no branch was taken, no\n // tax was computed, and the shopper was charged an untaxed total. See\n // `commerce-tax-decision.ts` for why refusing beats defaulting to either\n // mode. `mode: 'none'` is the explicit opt-out and passes straight\n // through.\n const taxDecision = CommerceModel.storefrontTaxDecision({\n settings: taxSettings,\n taxExempt: product.taxExempt,\n })\n if (taxDecision.kind === 'undecided') {\n return res\n .status(409)\n .json({ error: CommerceModel.STOREFRONT_TAX_UNDECIDED_MESSAGE })\n }\n const taxMisconfigured =\n CommerceModel.storefrontTaxMisconfiguration(taxSettings)\n if (taxMisconfigured) {\n // AGL-2145: the merchant DECIDED to collect and the sale would have\n // charged zero anyway — an unfinished settings card, not a\n // jurisdiction miss. Refused in the same words and the same place as\n // the undecided case above.\n return res.status(409).json({ error: taxMisconfigured })\n }\n const useStripeTax = taxDecision.kind === 'stripe-automatic'\n const manualRate =\n taxDecision.kind === 'manual' &&\n !taxSettings.pricesIncludeTax\n ? CommerceModel.resolveTaxRate(taxSettings, taxSettings.origin ?? {})\n : null\n // THE DISCOUNTED BASE (AGL-305). `chargedItemsCents` is what the line\n // will actually be worth, and the session applies this same rate to that\n // line as a Stripe Tax Rate — so taxing `itemsCents` here would record a\n // figure Stripe never charges.\n const taxCents = manualRate\n ? CommerceModel.computeTaxCents(chargedItemsCents, manualRate.pct)\n : 0\n // Stripe Tax mode leaves this 0 at composition time and the webhook folds\n // the computed figure in — the same additive shape AGL-1792 used for\n // shipping, and for the same reason: only Stripe knows it.\n // Live carrier rates (AGL-3612), quoted for the destination the manager\n // names; see `carrier-shipping.ts`.\n const shippingPlan: CarrierShippingPlan =\n (product.type ?? 'physical') === 'physical'\n ? await planCheckoutShippingWithCarriers({\n hostId,\n settings: shippingSettings,\n cart: {\n subtotalCents: itemsCents,\n totalGrams:\n Math.max(0, Number(variant.weightGrams ?? 0)) * quantity,\n ...(quantity === 1 ? { parcel: CommerceModel.productParcelDimensions(product) } : {}),\n },\n destination: {\n country: body.shippingCountry,\n postalCode: body.shippingPostalCode,\n },\n })\n : { countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES, options: [] }\n if (shippingPlan.needsPostalCode) {\n return res.status(400).json({\n error: 'Enter the postal code this order ships to.',\n needsShippingCountry: true,\n needsShippingPostalCode: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n if (shippingPlan.refusal === 'destination-required') {\n return res.status(400).json({\n error: 'Choose where this order ships to.',\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n if (shippingPlan.refusal === 'destination-unserved') {\n const declared = CommerceModel.normalizeCheckoutShippingCountry(\n body.shippingCountry,\n )\n return res.status(409).json({\n error: `This store does not ship to ${\n CommerceModel.CHECKOUT_SHIPPING_COUNTRY_NAMES[declared as string] ??\n 'that country'\n }.`,\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n // No rate of the merchant's reaches this order (AGL-2232). Above the\n // order write and the payment link, like every other refusal here, so a\n // manager who fixes the tier table and presses Send again strands nothing.\n if (shippingPlan.refusal === 'cart-unpriceable') {\n return res\n .status(409)\n .json({ error: CommerceModel.CART_UNPRICEABLE_SHIPPING_MESSAGE })\n }\n // FREE SHIPPING IS A ZEROED RATE, NOT A REDUCTION (AGL-305), the same\n // construction the cart uses and for the same reason: nothing has picked a\n // rate yet, so there is no amount to take off. The buyer chooses after the\n // link is minted, so the only exact answer is to offer every rate at zero.\n //\n // Every rate rather than the cheapest, because `free_shipping` carries no\n // field naming one — and zeroing them all keeps the fee, the transfer floor\n // and the ceiling below honest, since all three read these same options.\n //\n // Derived from `shippingPlan.options` and read EVERYWHERE below in its\n // place; a reader that reached past this to `shippingPlan.options` would\n // price carriage the buyer is not being charged for.\n const shippingOptions =\n resolvedDiscount?.freeShipping === true\n ? shippingPlan.options.map((option) => ({ ...option, amountCents: 0 }))\n : shippingPlan.options\n\n // THE PLATFORM FEE (AGL-2152) — the advertised take PLUS Stripe's card\n // cost, passed through at cost. A payment link is a destination charge, so\n // Stripe debits 2.9% + 30¢ from the PLATFORM's balance; the old take-only\n // figure meant every tier carrying a 0% rate invoiced a buyer at a loss to\n // Aglyn, and Starter's 2% is below 2.9% so it lost money at every size too.\n //\n // `?? 'physical'` (AGL-2251), matching `checkout.ts` and the cart. Without\n // it a product doc carrying no `type` fell to `resolveTransactionFeePct`'s\n // digital branch, so a merchant's payment link took a different cut of the\n // same product than their storefront did.\n //\n // The base is the whole card total: the goods, the manual tax line, and the DEAREST shipping\n // option the link offers (the buyer picks after the link is minted and\n // `application_fee_amount` is fixed at creation, so reaching for the\n // cheapest would put the difference back on Aglyn). A store on Stripe Tax\n // is the one residual — that tax is computed inside Stripe afterwards.\n const shippingCeilingCents = shippingOptions.reduce(\n (most, option) =>\n Math.max(most, Math.max(0, Number(option.amountCents ?? 0))),\n 0,\n )\n // Both bases are the DISCOUNTED goods (AGL-305). The platform take is a\n // cut of what the merchant actually sells for, and Stripe's card cost is\n // passed through on what the card actually runs for — reading `itemsCents`\n // here would invoice the merchant a fee on money nobody paid.\n const feeCents = Aglyn.resolveTransactionFeeCents(\n ownerOrg?.org as any,\n product.type ?? 'physical',\n chargedItemsCents,\n chargedItemsCents + taxCents + shippingCeilingCents,\n )\n // Stripe Tax mode leaves the tax 0 at composition time and the webhook\n // folds the computed figure in — the same additive shape AGL-1792 used for\n // shipping, and for the same reason: only Stripe knows it.\n const totals = CommerceModel.computeOrderTotals(lineItems, {\n feeCents,\n taxCents,\n // The line item still carries the LIST price, so the discount has to be\n // a named part of the totals or `itemsCents - discountCents + tax` would\n // not be the number on the payment link.\n discountCents: appliedDiscountCents,\n })\n\n // THE LINK CHARGES `chargedItemsCents`, EXACTLY (the AGL-2159 construction,\n // reused here because this path grew the same problem the moment it grew a\n // discount).\n //\n // A Stripe line is `quantity` units at ONE integer `unit_amount`, so it can\n // only express totals divisible by `quantity` — and a percentage discount\n // does not respect that. Dividing and multiplying back misses the intended\n // total by up to `quantity / 2` cents in either direction, which on this\n // path is worse than on buy-now: the frozen `totals` above are written to\n // the order document BEFORE the session is minted, so a link that charged\n // a different number would leave the merchant's own books disagreeing with\n // what their buyer paid, permanently and silently.\n //\n // So when the total does not divide evenly, the line becomes ONE unit at\n // the exact total with the count moved into the display name. The real\n // quantity is carried by the order document's line item, which is\n // untouched, so nothing downstream can tell the difference.\n //\n // The even case — every undiscounted draft, and most discounted ones —\n // keeps the per-unit shape byte for byte.\n const evenUnitAmountCents = Math.floor(chargedItemsCents / quantity)\n const remainderCents = chargedItemsCents - evenUnitAmountCents * quantity\n const chargedQuantity = remainderCents === 0 ? quantity : 1\n const chargedUnitAmountCents =\n remainderCents === 0 ? evenUnitAmountCents : chargedItemsCents\n const chargedLineName = `${product.name}${\n remainderCents === 0 ? '' : ` × ${quantity}`\n }`.slice(0, 120)\n\n // Point of no return (AGL-1697): everything past here writes an order and\n // mints a live payment link. Every deterministic refusal — unknown\n // product, payments not set up, the shipping-destination ask — sits above\n // this line, so none of them burns the key: the merchant fixes the input\n // and presses the same button. Before the claim, a double-submit minted a\n // SECOND order doc, burned a second sequential number, and returned two\n // live payment links bound to different orders, both payable.\n const claimed = await claimAttempt(firestore, {\n kind: 'commerce-draft',\n scopeId: hostId,\n orgId: String(ownerOrg?.org?.id ?? ''),\n key: idempotencyKey,\n busyMessage: 'This draft order is already being created',\n })\n if ('replay' in claimed) {\n return res.status(claimed.replay.status).json(claimed.replay.body)\n }\n claim = claimed.claim\n\n // THE REDEMPTION SLOT IS RESERVED HERE (AGL-305), on the same terms the\n // storefront doors reserve it.\n //\n // A payment link lives until the buyer pays it, so between this handler and\n // the `commerce-draft` webhook branch that settles the slot there is a\n // window measured in days — far wider than a Checkout Session's. Reading\n // `redemptions` and hoping would let a merchant mint ten links against a\n // cap of one and have all ten pay.\n //\n // BELOW the claim because the hold is keyed by the attempt: a retry of one\n // attempt must be able to re-claim the slot it already holds rather than be\n // refused by it. An uncapped promotion writes nothing and returns an empty\n // `holdKey`, which is what tells the webhook to fall back to the plain\n // increment.\n if (appliedDiscountId) {\n const slot = await holdPromotionSlot({\n firestore,\n ref: hostRef.collection('discounts').doc(appliedDiscountId),\n holdKey: promotionHoldKey(claimed.claim.stripeKey),\n label: `draft discount ${appliedDiscountId}`,\n })\n if (!slot.ok) {\n // Refused rather than quietly repriced, and that is deliberate for the\n // AUTOMATIC case too: the merchant composed this order at a price the\n // promotion was part of, and minting the link at full price would\n // invoice their buyer more than the store advertises without saying so.\n // Nothing has been written yet, so the key goes back and the retry\n // prices correctly — `resolveDiscount` counts live holds and simply\n // stops offering an exhausted promotion.\n await claim.release()\n return res.status(409).json({\n error:\n couponCode && resolvedDiscount?.discount.code\n ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE\n : CommerceModel.PROMOTION_UNAVAILABLE_MESSAGE,\n })\n }\n if (slot.holdKey) {\n discountSlot = slot\n discountHoldKey = slot.holdKey\n }\n }\n\n // Order doc first (transactional number), then the payment link.\n const orderRef = hostRef.collection('orders').doc()\n const counterRef = hostRef.collection('counters').doc('orders')\n await firestore.runTransaction(async (transaction) => {\n const counter = await transaction.get(counterRef)\n const number = Number(counter.get('next') ?? 1)\n transaction.set(counterRef, { next: number + 1 }, { merge: true })\n // With the fields the orders list queries by (AGL-3321).\n transaction.set(orderRef, CommerceModel.withOrderListFields(orderRef.id, {\n number,\n status: 'pending',\n channel: 'draft',\n lineItems,\n totals,\n // WHICH TAX REGIME THIS ORDER IS COMPOSED UNDER (AGL-2451), from the\n // decision taken above rather than from the frozen `taxCents` — a\n // Stripe Tax draft freezes 0 there and only the paid session knows the\n // figure, so a mode read back off the amount would call it `none`.\n //\n // Stamped at composition so a draft that is never paid still says what\n // it was priced under, and RESTATED by the `commerce-draft` webhook\n // branch from the session that actually charged it. The webhook's is\n // authoritative — it has Stripe's computed tax in hand — and for a\n // manual draft the two agree by construction.\n taxMode: CommerceModel.storefrontTaxModeForDecision(\n taxDecision,\n taxCents,\n ),\n customerEmail: email || null,\n timeline: [\n {\n atMs: Date.now(),\n event: 'draft',\n detail: `Draft created by ${decoded.email ?? decoded.uid}`,\n },\n ],\n createdAtMs: Date.now(),\n createdAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n }))\n })\n\n // The tax rate is minted here, BELOW the claim, because it creates a real\n // Stripe object (AGL-1953). Cached per host, so it is usually a read.\n let draftTaxRateId = ''\n if (taxCents > 0 && manualRate) {\n draftTaxRateId =\n (await resolveManualTaxRateId({\n hostRef,\n taxPct: manualRate.pct,\n taxLabel: manualRate.label || `Tax (${manualRate.pct}%)`,\n ...(claim.stripeKey\n ? { headers: { 'Idempotency-Key': `${claim.stripeKey}-tax-rate` } }\n : {}),\n })) ?? ''\n if (!draftTaxRateId) {\n // A VISIBLE refusal rather than an untaxed payment link — the silent\n // under-collection is the defect. The order doc is rolled back the\n // same way the session failure below rolls it back.\n await orderRef.delete().catch(() => undefined)\n await releaseDiscountSlot()\n await claim.release()\n return res.status(502).json({ error: 'Payment link creation failed' })\n }\n }\n const params = new URLSearchParams({\n mode: 'payment',\n // 3-D Secure, from the one seam every card payment shares (AGL-3360).\n ...checkoutSessionCardAuthenticationParams('payment'),\n ...(useStripeTax ? { 'automatic_tax[enabled]': 'true' } : {}),\n ...(draftTaxRateId ? { 'line_items[0][tax_rates][0]': draftTaxRateId } : {}),\n 'line_items[0][quantity]': String(chargedQuantity),\n 'line_items[0][price_data][currency]': 'usd',\n 'line_items[0][price_data][unit_amount]': String(chargedUnitAmountCents),\n 'line_items[0][price_data][product_data][name]': chargedLineName,\n // THE CONNECT SPLIT (AGL-1956). A Stripe Tax payment link computes its\n // tax against AGLYN's registrations and Aglyn remits it (AGL-1904), so\n // the merchant gets a FIXED `transfer_data[amount]` and no\n // `application_fee_amount`: the fee form transfers `amount_total − fee`,\n // and `amount_total` includes that tax. A manual-rate or untaxed link is\n // unchanged. See `commerce-connect-transfer.ts`.\n ...CommerceModel.destinationChargeParams({\n accountId: String(accountId),\n feeCents,\n taxOwner: useStripeTax ? 'platform' : 'merchant',\n // The DISCOUNTED goods (AGL-305): this is what the merchant is\n // transferred, and paying out the list price on a discounted link\n // would move money Stripe never collected.\n merchantGoodsCents: chargedItemsCents,\n shippingFloorCents: CommerceModel.shippingFloorCents(shippingOptions),\n }),\n ...(email ? { customer_email: email } : {}),\n success_url: `${consoleProductsUrl}?draft=paid`,\n cancel_url: `${consoleProductsUrl}?draft=canceled`,\n 'metadata[type]': 'commerce-draft',\n 'metadata[hostId]': hostId,\n 'metadata[orderId]': orderRef.id,\n 'metadata[productId]': productId,\n ...(variantId ? { 'metadata[variantId]': variantId } : {}),\n 'metadata[feeCents]': String(feeCents),\n ...(shippingPlan.quotedPostalCode\n ? { 'metadata[shippingQuotePostalCode]': shippingPlan.quotedPostalCode }\n : {}),\n // WHAT THE WEBHOOK SETTLES (AGL-305), in the field names the\n // `checkout.session.completed` branch already reads for the cart. The\n // hold key's ABSENCE is what tells it to fall back to the unconditional\n // increment, which is right for an uncapped promotion.\n ...(appliedDiscountId\n ? { 'metadata[discountId]': appliedDiscountId }\n : {}),\n ...(discountHoldKey ? { 'metadata[discountHoldKey]': discountHoldKey } : {}),\n // See the AGL-1956 note in `checkout.ts` — recorded only on the shape\n // where the merchant's payout is no longer the whole charge.\n ...(useStripeTax\n ? {\n 'metadata[transferCents]': String(\n CommerceModel.platformLiableTransferCents({\n feeCents,\n merchantGoodsCents: chargedItemsCents,\n shippingFloorCents:\n CommerceModel.shippingFloorCents(shippingOptions),\n }),\n ),\n 'metadata[transferShippingCents]': String(\n CommerceModel.shippingFloorCents(shippingOptions),\n ),\n }\n : {}),\n })\n // Both keys or neither (AGL-1720's rule, inherited). Stripe will not apply\n // a shipping rate without an address to ship to, and a merchant who\n // configured nothing plans to no options and so gets neither — a payment\n // link byte-identical to the one this handler built before shipping\n // existed on it.\n if (shippingOptions.length > 0) {\n CommerceModel.appendShippingAddressCollectionParams(\n params,\n shippingPlan.countries,\n )\n CommerceModel.appendCheckoutShippingParams(params, shippingOptions)\n }\n // The payment methods the merchant switched off reach the payment link\n // too (AGL-3629). A hosted Stripe page shows Apple Pay and Google Pay by\n // device and cannot hide them per merchant; everything else follows.\n CommerceModel.appendStorefrontPaymentMethodParams(\n params,\n CommerceModel.resolveStorefrontPaymentMethodControls(\n CommerceModel.normalizeStorefrontPaymentMethodSettings(\n storeSettings.get('paymentMethods'),\n ),\n ),\n )\n const response = await fetch(\n 'https://api.stripe.com/v1/checkout/sessions',\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n // The half that is a live payment link (AGL-1697). Stripe replays\n // the existing session for a repeated key instead of opening a\n // second one on the merchant's account, which covers the window\n // where our claim is written but the response never arrives.\n ...(claim.stripeKey ? { 'Idempotency-Key': claim.stripeKey } : {}),\n },\n body: params.toString(),\n },\n )\n const session = (await response.json()) as {\n url?: string\n id?: string\n error?: any\n }\n if (!response.ok || !session.url) {\n console.error('Stripe draft-order error', session.error)\n await orderRef.delete().catch(() => undefined)\n // The draft did not happen — hand the key back rather than locking this\n // dialog out over one flaky moment. The retry re-derives the SAME\n // digest, so if Stripe did create the session it replays it.\n await releaseDiscountSlot()\n await claim.release()\n return res.status(502).json({ error: 'Payment link creation failed' })\n }\n await orderRef.set(\n { checkoutSessionId: session.id, paymentLinkUrl: session.url },\n { merge: true },\n )\n // Spread on the same terms as the register's: present only when there is\n // something to say, so a caller can test the key rather than a length.\n const payload = {\n orderId: orderRef.id,\n url: session.url,\n ...(stockWarnings.length > 0 ? { stockWarnings } : {}),\n }\n await claim.record(200, payload)\n return res.status(200).json(payload)\n } catch (error) {\n console.error(error)\n // Release on the way out so a transient failure does not strand the key\n // (AGL-1691's rule). The order write may or may not have landed; the\n // merchant reconciles from the orders list, the same position they were\n // in before this endpoint had a claim at all. The redemption slot goes\n // back for the same reason — a throw is the one path that would otherwise\n // leave it standing against the cap until its TTL lapsed.\n await releaseDiscountSlot()\n await claim?.release()\n return res.status(500).json({ error: 'Draft order failed' })\n }\n}\n"],"names":["holdsCommerceSiteKey","commerceSitePermissionRefusal","Aglyn","CommerceModel","firebaseAdmin","getOrgForHost","merchantAccountIsReady","checkoutSessionCardAuthenticationParams","planCheckoutShippingWithCarriers","resolveManualTaxRateId","holdPromotionSlot","promotionHoldKey","buildRoute","claimAttempt","Route","draftOrderHandler","req","res","body","method","status","json","error","process","env","STRIPE_SECRET_KEY","authorization","String","headers","idToken","startsWith","slice","length","undefined","JSON","parse","hostId","productId","variantId","quantity","Math","max","min","round","Number","email","trim","couponCode","idempotencyKey","claim","discountSlot","discountHoldKey","releaseDiscountSlot","slot","release","storeSettings","taxSettings","product","variant","ownerOrg","decoded","app","auth","verifyIdToken","firestore","hostRef","collection","doc","hostSnapshot","get","exists","uid","productSnapshot","raw","data","deletedAt","liftLegacyProduct","variants","find","item","id","variantHasPrice","name","checkEntitlement","org","ownerId","ownerUid","ownerProfile","accountId","chargesEnabled","accountLivemode","subject","consoleSubdomain","consoleOrgSlug","slug","origin","host","consoleProductsUrl","HOST_PLUGIN","orgSlug","pluginSlug","shortfall","stockShortfall","stockWarnings","requested","available","unitAmountCents","priceUsd","lineItems","sku","productType","type","itemsCents","draftDiscountLines","amountCents","hubDiscounts","limit","resolvedDiscount","resolveDiscount","docs","map","docSnapshot","$id","code","subtotalCents","productIds","lines","codeProblem","appliedDiscountId","benefit","kind","discountId","discountCents","chargedItemsCents","appliedDiscountCents","shippingSettings","taxDecision","storefrontTaxDecision","settings","taxExempt","STOREFRONT_TAX_UNDECIDED_MESSAGE","taxMisconfigured","storefrontTaxMisconfiguration","useStripeTax","manualRate","pricesIncludeTax","resolveTaxRate","taxCents","computeTaxCents","pct","shippingPlan","cart","totalGrams","weightGrams","parcel","productParcelDimensions","destination","country","shippingCountry","postalCode","shippingPostalCode","countries","CHECKOUT_SHIPPING_COUNTRIES","options","needsPostalCode","needsShippingCountry","needsShippingPostalCode","shippingCountries","refusal","declared","normalizeCheckoutShippingCountry","CHECKOUT_SHIPPING_COUNTRY_NAMES","CART_UNPRICEABLE_SHIPPING_MESSAGE","shippingOptions","freeShipping","option","shippingCeilingCents","reduce","most","feeCents","resolveTransactionFeeCents","totals","computeOrderTotals","evenUnitAmountCents","floor","remainderCents","chargedQuantity","chargedUnitAmountCents","chargedLineName","claimed","scopeId","orgId","key","busyMessage","replay","ref","holdKey","stripeKey","label","ok","discount","PROMOTION_EXHAUSTED_MESSAGE","PROMOTION_UNAVAILABLE_MESSAGE","orderRef","counterRef","runTransaction","transaction","counter","number","set","next","merge","withOrderListFields","channel","taxMode","storefrontTaxModeForDecision","customerEmail","timeline","atMs","Date","now","event","detail","createdAtMs","createdAt","FieldValue","serverTimestamp","draftTaxRateId","taxPct","taxLabel","delete","catch","params","URLSearchParams","mode","destinationChargeParams","taxOwner","merchantGoodsCents","shippingFloorCents","customer_email","success_url","cancel_url","quotedPostalCode","platformLiableTransferCents","appendShippingAddressCollectionParams","appendCheckoutShippingParams","appendStorefrontPaymentMethodParams","resolveStorefrontPaymentMethodControls","normalizeStorefrontPaymentMethodSettings","response","fetch","Authorization","toString","session","url","console","checkoutSessionId","paymentLinkUrl","payload","orderId","record"],"mappings":"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"}
@@ -14,7 +14,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import * as CommerceModel from "../model/index.js";
17
+ */ import { holdsCommerceSiteKey } from "./commerce-site-keys.js";
18
+ import { commerceSitePermissionRefusal } from "../model/plugin-permissions.js";
19
+ import * as CommerceModel from "../model/index.js";
18
20
  import { announcePluginShipment } from "@aglyn/aglyn/plugin-manager/plugin-shipment-records";
19
21
  import { createResourceUid } from "@aglyn/aglyn/server";
20
22
  import { firebaseAdmin } from "@aglyn/tenant-data-admin";
@@ -518,7 +520,6 @@ function orderRefFor(hostId, orderId) {
518
520
  error: shippingCostProblem
519
521
  });
520
522
  try {
521
- var _hostSnapshot_get;
522
523
  const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
523
524
  const firestore = firebaseAdmin.app().firestore();
524
525
  const hostRef = firestore.collection('hosts').doc(hostId);
@@ -528,10 +529,11 @@ function orderRefFor(hostId, orderId) {
528
529
  error: 'Unknown site'
529
530
  });
530
531
  }
531
- const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
532
- if (memberRole !== 'admin' && memberRole !== 'editor') {
532
+ // "Manage orders" (`commerce.orders`, AGL-4047), whose preset is the site's Admin
533
+ // and Editor this asked for, read as the rules would read it.
534
+ if (!holdsCommerceSiteKey(hostSnapshot, decoded.uid, 'commerce.orders')) {
533
535
  return res.status(403).json({
534
- error: 'Not permitted'
536
+ error: commerceSitePermissionRefusal('commerce.orders')
535
537
  });
536
538
  }
537
539
  if (action) {