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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/README.md +1 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/cart.js +25 -12
  4. package/src/lib/components/cart.js.map +1 -1
  5. package/src/lib/components/commerce-console-page.js +70 -13
  6. package/src/lib/components/commerce-console-page.js.map +1 -1
  7. package/src/lib/components/console/order-detail-dialog.component.js +2 -1
  8. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  9. package/src/lib/components/console/order-webhooks-card.component.js +1 -1
  10. package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
  11. package/src/lib/components/console/payment-methods-card.component.js +1 -1
  12. package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
  13. package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +5 -0
  14. package/src/lib/components/console/pos/pos-cart-panel.component.js +18 -12
  15. package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -1
  16. package/src/lib/components/console/pos/pos-hardware-api.d.ts +76 -5
  17. package/src/lib/components/console/pos/pos-hardware-api.js +53 -11
  18. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -1
  19. package/src/lib/components/console/pos/pos-hardware-buyer.component.d.ts +18 -0
  20. package/src/lib/components/console/pos/pos-hardware-buyer.component.js +629 -0
  21. package/src/lib/components/console/pos/pos-hardware-buyer.component.js.map +1 -0
  22. package/src/lib/components/console/pos/pos-hardware-sale-api.d.ts +162 -0
  23. package/src/lib/components/console/pos/pos-hardware-sale-api.js +122 -0
  24. package/src/lib/components/console/pos/pos-hardware-sale-api.js.map +1 -0
  25. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.d.ts +39 -0
  26. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js +251 -0
  27. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js.map +1 -0
  28. package/src/lib/components/console/pos/use-pos-display.js +50 -0
  29. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  30. package/src/lib/components/console/pos-cellular-control.component.d.ts +41 -0
  31. package/src/lib/components/console/pos-cellular-control.component.js +211 -0
  32. package/src/lib/components/console/pos-cellular-control.component.js.map +1 -0
  33. package/src/lib/components/console/pos-devices-card.component.d.ts +9 -1
  34. package/src/lib/components/console/pos-devices-card.component.js +47 -15
  35. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  36. package/src/lib/components/console/pos-display/pos-display-api.d.ts +6 -0
  37. package/src/lib/components/console/pos-display/pos-display-api.js +8 -1
  38. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  39. package/src/lib/components/console/pos-display-page.component.d.ts +13 -0
  40. package/src/lib/components/console/pos-display-page.component.js +44 -2
  41. package/src/lib/components/console/pos-display-page.component.js.map +1 -1
  42. package/src/lib/components/console/pos-hardware-card.component.d.ts +12 -4
  43. package/src/lib/components/console/pos-hardware-card.component.js +157 -36
  44. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -1
  45. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +360 -80
  46. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -1
  47. package/src/lib/components/console/pos-hardware-terms-page.component.d.ts +34 -0
  48. package/src/lib/components/console/pos-hardware-terms-page.component.js +257 -0
  49. package/src/lib/components/console/pos-hardware-terms-page.component.js.map +1 -0
  50. package/src/lib/components/console/pos-hardware-terms-sign-page.component.d.ts +27 -0
  51. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js +104 -0
  52. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js.map +1 -0
  53. package/src/lib/components/console/pos-hardware-waiting-card.component.d.ts +24 -0
  54. package/src/lib/components/console/pos-hardware-waiting-card.component.js +205 -0
  55. package/src/lib/components/console/pos-hardware-waiting-card.component.js.map +1 -0
  56. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +297 -201
  57. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -1
  58. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +3 -0
  59. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +260 -92
  60. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  61. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +9 -0
  62. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +15 -6
  63. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  64. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.d.ts +25 -0
  65. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js +136 -0
  66. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js.map +1 -0
  67. package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +6 -0
  68. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +48 -31
  69. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -1
  70. package/src/lib/components/console/pos-page.component.d.ts +1 -1
  71. package/src/lib/components/console/pos-page.component.js +63 -12
  72. package/src/lib/components/console/pos-page.component.js.map +1 -1
  73. package/src/lib/components/console/pos-reader-splash-setting.component.js +1 -1
  74. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -1
  75. package/src/lib/components/console/product-check-in-fields.component.d.ts +4 -2
  76. package/src/lib/components/console/product-check-in-fields.component.js +60 -14
  77. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -1
  78. package/src/lib/components/console/product-editor-dialog.component.js +44 -1
  79. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  80. package/src/lib/components/console/product-hardware-sku-field.component.d.ts +19 -0
  81. package/src/lib/components/console/product-hardware-sku-field.component.js +92 -0
  82. package/src/lib/components/console/product-hardware-sku-field.component.js.map +1 -0
  83. package/src/lib/components/console/product-kiosk-offer-field.component.d.ts +22 -0
  84. package/src/lib/components/console/product-kiosk-offer-field.component.js +94 -0
  85. package/src/lib/components/console/product-kiosk-offer-field.component.js.map +1 -0
  86. package/src/lib/components/console/product-register-fields.component.d.ts +4 -0
  87. package/src/lib/components/console/product-register-fields.component.js +31 -2
  88. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  89. package/src/lib/components/console/products-hub-card.component.js +11 -2
  90. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  91. package/src/lib/components/console/register-hardware-card.component.d.ts +11 -0
  92. package/src/lib/components/console/register-hardware-card.component.js +56 -27
  93. package/src/lib/components/console/register-hardware-card.component.js.map +1 -1
  94. package/src/lib/components/console/reservation-guests-dialog.component.d.ts +28 -0
  95. package/src/lib/components/console/reservation-guests-dialog.component.js +189 -0
  96. package/src/lib/components/console/reservation-guests-dialog.component.js.map +1 -0
  97. package/src/lib/components/console/reservations-card.component.d.ts +9 -0
  98. package/src/lib/components/console/reservations-card.component.js +158 -23
  99. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  100. package/src/lib/components/console/shippingeasy-card.component.js +1 -1
  101. package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
  102. package/src/lib/components/console/shipstation-card.component.d.ts +1 -1
  103. package/src/lib/components/console/shipstation-card.component.js +3 -3
  104. package/src/lib/components/console/shipstation-card.component.js.map +1 -1
  105. package/src/lib/components/product-detail.d.ts +4 -0
  106. package/src/lib/components/product-detail.js +40 -5
  107. package/src/lib/components/product-detail.js.map +1 -1
  108. package/src/lib/components/product-reviews.js +28 -11
  109. package/src/lib/components/product-reviews.js.map +1 -1
  110. package/src/lib/components/reservation-widget.js +28 -5
  111. package/src/lib/components/reservation-widget.js.map +1 -1
  112. package/src/lib/components/use-day-picker.d.ts +25 -0
  113. package/src/lib/components/use-day-picker.js +57 -0
  114. package/src/lib/components/use-day-picker.js.map +1 -0
  115. package/src/lib/constants/operator-alerts.d.ts +55 -0
  116. package/src/lib/constants/operator-alerts.js +156 -1
  117. package/src/lib/constants/operator-alerts.js.map +1 -1
  118. package/src/lib/declarations.console-server.js +54 -1
  119. package/src/lib/declarations.console-server.js.map +1 -1
  120. package/src/lib/declarations.d.ts +4 -2
  121. package/src/lib/declarations.js +15 -4
  122. package/src/lib/declarations.js.map +1 -1
  123. package/src/lib/declarations.server.js +9 -0
  124. package/src/lib/declarations.server.js.map +1 -1
  125. package/src/lib/model/commerce-cart.d.ts +9 -1
  126. package/src/lib/model/commerce-cart.js +4 -3
  127. package/src/lib/model/commerce-cart.js.map +1 -1
  128. package/src/lib/model/commerce-check-in-product.d.ts +26 -0
  129. package/src/lib/model/commerce-check-in-product.js +64 -2
  130. package/src/lib/model/commerce-check-in-product.js.map +1 -1
  131. package/src/lib/model/commerce-check-in-record.d.ts +127 -0
  132. package/src/lib/model/commerce-check-in-record.js +36 -0
  133. package/src/lib/model/commerce-check-in-record.js.map +1 -0
  134. package/src/lib/model/commerce-check-in.d.ts +91 -87
  135. package/src/lib/model/commerce-check-in.js +186 -62
  136. package/src/lib/model/commerce-check-in.js.map +1 -1
  137. package/src/lib/model/commerce-kiosk-offer.d.ts +31 -0
  138. package/src/lib/model/commerce-kiosk-offer.js +35 -0
  139. package/src/lib/model/commerce-kiosk-offer.js.map +1 -0
  140. package/src/lib/model/commerce-orders.d.ts +76 -0
  141. package/src/lib/model/commerce-orders.js +6 -0
  142. package/src/lib/model/commerce-orders.js.map +1 -1
  143. package/src/lib/model/commerce-pos-day-report.d.ts +54 -1
  144. package/src/lib/model/commerce-pos-day-report.js +39 -2
  145. package/src/lib/model/commerce-pos-day-report.js.map +1 -1
  146. package/src/lib/model/commerce-pos-offline.d.ts +1 -1
  147. package/src/lib/model/commerce-pos-offline.js +6 -1
  148. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  149. package/src/lib/model/commerce-pos-ops.js +3 -2
  150. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  151. package/src/lib/model/commerce-pos.d.ts +36 -3
  152. package/src/lib/model/commerce-pos.js +14 -6
  153. package/src/lib/model/commerce-pos.js.map +1 -1
  154. package/src/lib/model/commerce-receipt.js +3 -2
  155. package/src/lib/model/commerce-receipt.js.map +1 -1
  156. package/src/lib/model/commerce-reservations.d.ts +69 -4
  157. package/src/lib/model/commerce-reservations.js +57 -5
  158. package/src/lib/model/commerce-reservations.js.map +1 -1
  159. package/src/lib/model/commerce.d.ts +66 -0
  160. package/src/lib/model/commerce.js +15 -0
  161. package/src/lib/model/commerce.js.map +1 -1
  162. package/src/lib/model/order-list-fields.d.ts +11 -0
  163. package/src/lib/model/order-list-fields.js +17 -1
  164. package/src/lib/model/order-list-fields.js.map +1 -1
  165. package/src/lib/model/own-store-tax-summary.d.ts +152 -0
  166. package/src/lib/model/own-store-tax-summary.js +288 -0
  167. package/src/lib/model/own-store-tax-summary.js.map +1 -0
  168. package/src/lib/model/plugin-permissions.d.ts +74 -0
  169. package/src/lib/model/plugin-permissions.js +224 -7
  170. package/src/lib/model/plugin-permissions.js.map +1 -1
  171. package/src/lib/model/pos-cellular.d.ts +223 -0
  172. package/src/lib/model/pos-cellular.js +293 -0
  173. package/src/lib/model/pos-cellular.js.map +1 -0
  174. package/src/lib/model/pos-hardware-in-person.d.ts +98 -0
  175. package/src/lib/model/pos-hardware-in-person.js +224 -0
  176. package/src/lib/model/pos-hardware-in-person.js.map +1 -0
  177. package/src/lib/model/pos-hardware-tax-summary.d.ts +166 -0
  178. package/src/lib/model/pos-hardware-tax-summary.js +225 -0
  179. package/src/lib/model/pos-hardware-tax-summary.js.map +1 -0
  180. package/src/lib/model/pos-hardware-terms.d.ts +74 -0
  181. package/src/lib/model/pos-hardware-terms.js +115 -0
  182. package/src/lib/model/pos-hardware-terms.js.map +1 -0
  183. package/src/lib/model/pos-hardware.d.ts +349 -7
  184. package/src/lib/model/pos-hardware.js +291 -20
  185. package/src/lib/model/pos-hardware.js.map +1 -1
  186. package/src/lib/model/product-write.d.ts +10 -0
  187. package/src/lib/model/product-write.js +4 -2
  188. package/src/lib/model/product-write.js.map +1 -1
  189. package/src/lib/model/workspace-site-permissions.d.ts +26 -0
  190. package/src/lib/model/workspace-site-permissions.js +81 -0
  191. package/src/lib/model/workspace-site-permissions.js.map +1 -0
  192. package/src/lib/plugin.js +16 -0
  193. package/src/lib/plugin.js.map +1 -1
  194. package/src/lib/server/billing-webhook.js +25 -1
  195. package/src/lib/server/billing-webhook.js.map +1 -1
  196. package/src/lib/server/cancel-order.js +7 -5
  197. package/src/lib/server/cancel-order.js.map +1 -1
  198. package/src/lib/server/cart-checkout.js +20 -8
  199. package/src/lib/server/cart-checkout.js.map +1 -1
  200. package/src/lib/server/cart.js +29 -0
  201. package/src/lib/server/cart.js.map +1 -1
  202. package/src/lib/server/checkout.js +42 -13
  203. package/src/lib/server/checkout.js.map +1 -1
  204. package/src/lib/server/collection-membership.d.ts +4 -0
  205. package/src/lib/server/collection-membership.js +5 -10
  206. package/src/lib/server/collection-membership.js.map +1 -1
  207. package/src/lib/server/commerce-site-keys.d.ts +48 -0
  208. package/src/lib/server/commerce-site-keys.js +61 -0
  209. package/src/lib/server/commerce-site-keys.js.map +1 -0
  210. package/src/lib/server/draft-order.js +8 -5
  211. package/src/lib/server/draft-order.js.map +1 -1
  212. package/src/lib/server/fulfill-order.js +7 -5
  213. package/src/lib/server/fulfill-order.js.map +1 -1
  214. package/src/lib/server/gift-cards.js +8 -5
  215. package/src/lib/server/gift-cards.js.map +1 -1
  216. package/src/lib/server/local-fulfillment-status.js +9 -6
  217. package/src/lib/server/local-fulfillment-status.js.map +1 -1
  218. package/src/lib/server/member-post.js +6 -5
  219. package/src/lib/server/member-post.js.map +1 -1
  220. package/src/lib/server/membership-admin-password.d.ts +4 -2
  221. package/src/lib/server/membership-admin-password.js +10 -8
  222. package/src/lib/server/membership-admin-password.js.map +1 -1
  223. package/src/lib/server/membership-admin-remove.d.ts +7 -5
  224. package/src/lib/server/membership-admin-remove.js +9 -9
  225. package/src/lib/server/membership-admin-remove.js.map +1 -1
  226. package/src/lib/server/order-notifications.js +5 -2
  227. package/src/lib/server/order-notifications.js.map +1 -1
  228. package/src/lib/server/order-print.js +7 -5
  229. package/src/lib/server/order-print.js.map +1 -1
  230. package/src/lib/server/order-receipt-send.js +8 -5
  231. package/src/lib/server/order-receipt-send.js.map +1 -1
  232. package/src/lib/server/order-webhooks.js +8 -8
  233. package/src/lib/server/order-webhooks.js.map +1 -1
  234. package/src/lib/server/orders-shipping-prepare.js +9 -6
  235. package/src/lib/server/orders-shipping-prepare.js.map +1 -1
  236. package/src/lib/server/own-store-tax-return-source.d.ts +47 -0
  237. package/src/lib/server/own-store-tax-return-source.js +312 -0
  238. package/src/lib/server/own-store-tax-return-source.js.map +1 -0
  239. package/src/lib/server/payment-methods.js +13 -9
  240. package/src/lib/server/payment-methods.js.map +1 -1
  241. package/src/lib/server/pos-auth.d.ts +37 -6
  242. package/src/lib/server/pos-auth.js +40 -15
  243. package/src/lib/server/pos-auth.js.map +1 -1
  244. package/src/lib/server/pos-cellular-billing.d.ts +81 -0
  245. package/src/lib/server/pos-cellular-billing.js +711 -0
  246. package/src/lib/server/pos-cellular-billing.js.map +1 -0
  247. package/src/lib/server/pos-cellular-notify.d.ts +47 -0
  248. package/src/lib/server/pos-cellular-notify.js +150 -0
  249. package/src/lib/server/pos-cellular-notify.js.map +1 -0
  250. package/src/lib/server/pos-cellular.d.ts +88 -0
  251. package/src/lib/server/pos-cellular.js +339 -0
  252. package/src/lib/server/pos-cellular.js.map +1 -0
  253. package/src/lib/server/pos-check-in.d.ts +5 -1
  254. package/src/lib/server/pos-check-in.js +296 -28
  255. package/src/lib/server/pos-check-in.js.map +1 -1
  256. package/src/lib/server/pos-day-report.js +29 -3
  257. package/src/lib/server/pos-day-report.js.map +1 -1
  258. package/src/lib/server/pos-display.d.ts +16 -0
  259. package/src/lib/server/pos-display.js +129 -2
  260. package/src/lib/server/pos-display.js.map +1 -1
  261. package/src/lib/server/pos-hardware-admin.d.ts +10 -1
  262. package/src/lib/server/pos-hardware-admin.js +250 -84
  263. package/src/lib/server/pos-hardware-admin.js.map +1 -1
  264. package/src/lib/server/pos-hardware-attach.d.ts +105 -0
  265. package/src/lib/server/pos-hardware-attach.js +443 -0
  266. package/src/lib/server/pos-hardware-attach.js.map +1 -0
  267. package/src/lib/server/pos-hardware-buyer-lookup.d.ts +58 -0
  268. package/src/lib/server/pos-hardware-buyer-lookup.js +180 -0
  269. package/src/lib/server/pos-hardware-buyer-lookup.js.map +1 -0
  270. package/src/lib/server/pos-hardware-buyer-record.d.ts +29 -0
  271. package/src/lib/server/pos-hardware-buyer-record.js +86 -0
  272. package/src/lib/server/pos-hardware-buyer-record.js.map +1 -0
  273. package/src/lib/server/pos-hardware-cancel.d.ts +58 -0
  274. package/src/lib/server/pos-hardware-cancel.js +147 -0
  275. package/src/lib/server/pos-hardware-cancel.js.map +1 -0
  276. package/src/lib/server/pos-hardware-closure-notices.d.ts +27 -0
  277. package/src/lib/server/pos-hardware-closure-notices.js +85 -0
  278. package/src/lib/server/pos-hardware-closure-notices.js.map +1 -0
  279. package/src/lib/server/pos-hardware-closure.d.ts +60 -0
  280. package/src/lib/server/pos-hardware-closure.js +153 -0
  281. package/src/lib/server/pos-hardware-closure.js.map +1 -0
  282. package/src/lib/server/pos-hardware-in-person.d.ts +61 -0
  283. package/src/lib/server/pos-hardware-in-person.js +236 -0
  284. package/src/lib/server/pos-hardware-in-person.js.map +1 -0
  285. package/src/lib/server/pos-hardware-notify.d.ts +40 -2
  286. package/src/lib/server/pos-hardware-notify.js +195 -14
  287. package/src/lib/server/pos-hardware-notify.js.map +1 -1
  288. package/src/lib/server/pos-hardware-readers.d.ts +115 -0
  289. package/src/lib/server/pos-hardware-readers.js +430 -0
  290. package/src/lib/server/pos-hardware-readers.js.map +1 -0
  291. package/src/lib/server/pos-hardware-returns.d.ts +116 -0
  292. package/src/lib/server/pos-hardware-returns.js +502 -0
  293. package/src/lib/server/pos-hardware-returns.js.map +1 -0
  294. package/src/lib/server/pos-hardware-sale-fields.d.ts +28 -0
  295. package/src/lib/server/pos-hardware-sale-fields.js +44 -0
  296. package/src/lib/server/pos-hardware-sale-fields.js.map +1 -0
  297. package/src/lib/server/pos-hardware-sale.d.ts +54 -0
  298. package/src/lib/server/pos-hardware-sale.js +369 -0
  299. package/src/lib/server/pos-hardware-sale.js.map +1 -0
  300. package/src/lib/server/pos-hardware-tax-return-source.d.ts +99 -0
  301. package/src/lib/server/pos-hardware-tax-return-source.js +462 -0
  302. package/src/lib/server/pos-hardware-tax-return-source.js.map +1 -0
  303. package/src/lib/server/pos-hardware-terms.d.ts +64 -0
  304. package/src/lib/server/pos-hardware-terms.js +154 -0
  305. package/src/lib/server/pos-hardware-terms.js.map +1 -0
  306. package/src/lib/server/pos-hardware-user-eraser.d.ts +3 -0
  307. package/src/lib/server/pos-hardware-user-eraser.js +31 -7
  308. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -1
  309. package/src/lib/server/pos-hardware-webhook.d.ts +5 -0
  310. package/src/lib/server/pos-hardware-webhook.js +51 -6
  311. package/src/lib/server/pos-hardware-webhook.js.map +1 -1
  312. package/src/lib/server/pos-hardware.d.ts +74 -1
  313. package/src/lib/server/pos-hardware.js +175 -10
  314. package/src/lib/server/pos-hardware.js.map +1 -1
  315. package/src/lib/server/pos-kiosk.d.ts +2 -0
  316. package/src/lib/server/pos-kiosk.js +17 -12
  317. package/src/lib/server/pos-kiosk.js.map +1 -1
  318. package/src/lib/server/pos-offline-card-order.d.ts +64 -0
  319. package/src/lib/server/pos-offline-card-order.js +102 -0
  320. package/src/lib/server/pos-offline-card-order.js.map +1 -0
  321. package/src/lib/server/pos-offline-card-reconcile.d.ts +67 -1
  322. package/src/lib/server/pos-offline-card-reconcile.js +215 -18
  323. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -1
  324. package/src/lib/server/pos-offline-sync.d.ts +39 -0
  325. package/src/lib/server/pos-offline-sync.js +190 -55
  326. package/src/lib/server/pos-offline-sync.js.map +1 -1
  327. package/src/lib/server/pos-ops-gate.d.ts +28 -9
  328. package/src/lib/server/pos-ops-gate.js +50 -19
  329. package/src/lib/server/pos-ops-gate.js.map +1 -1
  330. package/src/lib/server/pos-ops-routes.js +24 -0
  331. package/src/lib/server/pos-ops-routes.js.map +1 -1
  332. package/src/lib/server/pos-order-refund-ids.d.ts +28 -0
  333. package/src/lib/server/pos-order-refund-ids.js +31 -0
  334. package/src/lib/server/pos-order-refund-ids.js.map +1 -0
  335. package/src/lib/server/pos-order.js +52 -49
  336. package/src/lib/server/pos-order.js.map +1 -1
  337. package/src/lib/server/pos-payment.js +1 -1
  338. package/src/lib/server/pos-payment.js.map +1 -1
  339. package/src/lib/server/pos-reader-splash-admin.js +30 -32
  340. package/src/lib/server/pos-reader-splash-admin.js.map +1 -1
  341. package/src/lib/server/pos-reader-splash-images-route.d.ts +17 -0
  342. package/src/lib/server/pos-reader-splash-images-route.js +113 -0
  343. package/src/lib/server/pos-reader-splash-images-route.js.map +1 -0
  344. package/src/lib/server/pos-reader-splash-images.d.ts +51 -0
  345. package/src/lib/server/pos-reader-splash-images.js +123 -0
  346. package/src/lib/server/pos-reader-splash-images.js.map +1 -0
  347. package/src/lib/server/pos-reader-splash.d.ts +68 -1
  348. package/src/lib/server/pos-reader-splash.js +182 -11
  349. package/src/lib/server/pos-reader-splash.js.map +1 -1
  350. package/src/lib/server/pos-readers.d.ts +29 -0
  351. package/src/lib/server/pos-readers.js +134 -35
  352. package/src/lib/server/pos-readers.js.map +1 -1
  353. package/src/lib/server/pos-refund-authority.d.ts +3 -1
  354. package/src/lib/server/pos-refund-authority.js +3 -2
  355. package/src/lib/server/pos-refund-authority.js.map +1 -1
  356. package/src/lib/server/pos-return.d.ts +10 -0
  357. package/src/lib/server/pos-return.js +29 -7
  358. package/src/lib/server/pos-return.js.map +1 -1
  359. package/src/lib/server/pos-sale.js +6 -1
  360. package/src/lib/server/pos-sale.js.map +1 -1
  361. package/src/lib/server/pos-shift.js +11 -0
  362. package/src/lib/server/pos-shift.js.map +1 -1
  363. package/src/lib/server/pos-staff-pin.js +29 -24
  364. package/src/lib/server/pos-staff-pin.js.map +1 -1
  365. package/src/lib/server/pos-terminal.d.ts +14 -4
  366. package/src/lib/server/pos-terminal.js +34 -38
  367. package/src/lib/server/pos-terminal.js.map +1 -1
  368. package/src/lib/server/printers.d.ts +7 -3
  369. package/src/lib/server/printers.js +18 -12
  370. package/src/lib/server/printers.js.map +1 -1
  371. package/src/lib/server/product-drafts.d.ts +11 -1
  372. package/src/lib/server/product-drafts.js +12 -8
  373. package/src/lib/server/product-drafts.js.map +1 -1
  374. package/src/lib/server/product.d.ts +15 -1
  375. package/src/lib/server/product.js +16 -3
  376. package/src/lib/server/product.js.map +1 -1
  377. package/src/lib/server/products-write.d.ts +26 -2
  378. package/src/lib/server/products-write.js +19 -7
  379. package/src/lib/server/products-write.js.map +1 -1
  380. package/src/lib/server/refund.js +55 -42
  381. package/src/lib/server/refund.js.map +1 -1
  382. package/src/lib/server/reservation-availability-query.d.ts +8 -0
  383. package/src/lib/server/reservation-availability-query.js +11 -1
  384. package/src/lib/server/reservation-availability-query.js.map +1 -1
  385. package/src/lib/server/reservation-availability.d.ts +1 -1
  386. package/src/lib/server/reservation-availability.js +7 -2
  387. package/src/lib/server/reservation-availability.js.map +1 -1
  388. package/src/lib/server/reservation-guests.d.ts +32 -0
  389. package/src/lib/server/reservation-guests.js +285 -0
  390. package/src/lib/server/reservation-guests.js.map +1 -0
  391. package/src/lib/server/reservation-walk-in.d.ts +11 -1
  392. package/src/lib/server/reservation-walk-in.js +56 -12
  393. package/src/lib/server/reservation-walk-in.js.map +1 -1
  394. package/src/lib/server/reserve.js +18 -5
  395. package/src/lib/server/reserve.js.map +1 -1
  396. package/src/lib/server/returns.js +9 -6
  397. package/src/lib/server/returns.js.map +1 -1
  398. package/src/lib/server/reviews.d.ts +22 -2
  399. package/src/lib/server/reviews.js +88 -20
  400. package/src/lib/server/reviews.js.map +1 -1
  401. package/src/lib/server/shipping-connectors.js +10 -11
  402. package/src/lib/server/shipping-connectors.js.map +1 -1
  403. package/src/lib/server/site-page-resolver.js +20 -3
  404. package/src/lib/server/site-page-resolver.js.map +1 -1
  405. package/src/lib/server/store-day.d.ts +33 -0
  406. package/src/lib/server/store-day.js +46 -0
  407. package/src/lib/server/store-day.js.map +1 -0
  408. package/src/lib/server/store-setup-ai-writer.d.ts +2 -1
  409. package/src/lib/server/store-setup-ai-writer.js +5 -6
  410. package/src/lib/server/store-setup-ai-writer.js.map +1 -1
  411. package/src/lib/server/storefront-tax.d.ts +13 -0
  412. package/src/lib/server/storefront-tax.js +8 -1
  413. package/src/lib/server/storefront-tax.js.map +1 -1
  414. package/src/lib/server/tax-return-source.d.ts +32 -3
  415. package/src/lib/server/tax-return-source.js +99 -20
  416. package/src/lib/server/tax-return-source.js.map +1 -1
  417. package/src/lib/server.js +100 -2
  418. package/src/lib/server.js.map +1 -1
  419. package/src/lib/testing/fake-firestore.js +58 -16
  420. package/src/lib/testing/fake-firestore.js.map +1 -1
  421. package/src/lib/testing/pos-ops-memory-firestore.js +18 -4
  422. package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -1
  423. package/src/lib/testing/site-register-permissions.d.ts +29 -0
  424. package/src/lib/testing/site-register-permissions.js +38 -0
  425. package/src/lib/testing/site-register-permissions.js.map +1 -0
  426. package/src/lib/transfer/tracking.server.js +8 -5
  427. package/src/lib/transfer/tracking.server.js.map +1 -1
  428. package/src/lib/utils/add-to-cart.d.ts +2 -0
  429. package/src/lib/utils/add-to-cart.js +6 -3
  430. package/src/lib/utils/add-to-cart.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/pos-hardware.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\n/*==========================================\n * THE POS HARDWARE SHOP (AGL-4038), PHASE 1: THE MODEL.\n *\n * A merchant buys Stripe Terminal readers from the platform, the platform\n * places the order in Stripe's Terminal hardware shop and Stripe ships it\n * straight to the merchant. The platform is the seller of record, because the\n * register's card payments are destination charges on the PLATFORM account\n * and a reader must be registered there to take them.\n *\n * THE CATALOG IS CODE, THE PRICES ARE CONFIG. Which readers the register\n * works with is a fact about this codebase; what Stripe charges for them, the\n * markup on top and what shipping costs are business decisions that change\n * without a release. Every figure comes from the console's environment, read\n * by {@link parsePosHardwareConfig}, and a missing or malformed figure keeps\n * the shop HIDDEN: it is a gate only the platform can lift, so no workspace is\n * shown a control it cannot use.\n *\n * Everything here is pure, so the price math, the metadata a Checkout Session\n * carries and the status rules are specced without Firestore or Stripe.\n *=========================================*/\n\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\n\n/** The register's currency, and so the shop's: US dollars. */\nexport const POS_HARDWARE_CURRENCY = 'usd'\n\n/**\n * Where the shop ships. Stripe's Terminal hardware shop ships to many\n * countries, but the register sells in US dollars only and the readers a\n * merchant buys here are registered to a US Location, so the shop is US-only\n * (Stripe itself excludes the US Virgin Islands, military addresses and\n * P.O. boxes).\n */\nexport const POS_HARDWARE_SHIP_COUNTRIES: readonly string[] = ['US']\n\n/** The most of one item, and the most units, a single order may hold. */\nexport const POS_HARDWARE_MAX_PER_LINE = 10\nexport const POS_HARDWARE_MAX_UNITS = 20\n\n/** The env names the shop reads, named once for the reader and the docs. */\nexport const POS_HARDWARE_ENV = {\n /** `true` releases the shop. Absent or anything else keeps it hidden. */\n enabled: 'POS_HARDWARE_SHOP_ENABLED',\n /** JSON `{ \"<sku>\": <Stripe's price in cents> }`. */\n prices: 'POS_HARDWARE_PRICES',\n /**\n * The markup on Stripe's price, an integer in basis points (100 = 1%).\n * Absent, {@link POS_HARDWARE_DEFAULT_MARKUP_BPS} applies.\n */\n markupBps: 'POS_HARDWARE_MARKUP_BPS',\n /** JSON `{ \"standard\" | \"express\" | \"priority\": <cents> }`. */\n shipping: 'POS_HARDWARE_SHIPPING',\n /** `true` turns Stripe automatic tax on for hardware checkouts. */\n automaticTax: 'POS_HARDWARE_AUTOMATIC_TAX',\n} as const\n\n/**\n * The markup when the deployment sets none: 3000 basis points on Stripe's\n * price. The business sets it; this is the one place the default lives, and\n * `POS_HARDWARE_MARKUP_BPS` overrides it.\n */\nexport const POS_HARDWARE_DEFAULT_MARKUP_BPS = 3000\n\nexport type PosHardwareSkuId =\n | 'stripe-reader-s710'\n | 'stripe-reader-s700'\n | 'stripe-reader-m2'\n | 'bbpos-wisepos-e'\n | 'stripe-reader-s700-dock'\n | 'bbpos-wisepos-e-dock'\n | 'stripe-reader-m2-mount'\n\nexport interface PosHardwareSku {\n id: PosHardwareSkuId\n kind: 'reader' | 'accessory'\n /** What Stripe's hardware shop calls it, for the staff member ordering it. */\n stripeProductName: string\n /** What the merchant sees. */\n displayName: string\n /** One line on what it is for. */\n use: string\n}\n\n/**\n * Every item the shop can sell. An item is offered only once its Stripe\n * price is configured, so one Stripe stops selling disappears by deleting its\n * price, not by a release.\n */\nexport const POS_HARDWARE_SKUS: readonly PosHardwareSku[] = [\n {\n id: 'stripe-reader-s710',\n kind: 'reader',\n stripeProductName: 'Stripe Reader S710',\n displayName: 'Stripe Reader S710',\n use: 'Handheld with its own 4G connection, for events and parking lots',\n },\n {\n id: 'stripe-reader-s700',\n kind: 'reader',\n stripeProductName: 'Stripe Reader S700',\n displayName: 'Stripe Reader S700',\n use: 'Handheld or countertop reader on Wi-Fi',\n },\n {\n id: 'bbpos-wisepos-e',\n kind: 'reader',\n stripeProductName: 'BBPOS WisePOS E',\n displayName: 'BBPOS WisePOS E',\n use: 'Countertop reader on Wi-Fi',\n },\n {\n id: 'stripe-reader-m2',\n kind: 'reader',\n stripeProductName: 'Stripe Reader M2',\n displayName: 'Stripe Reader M2',\n use: `Pairs with the ${PLATFORM_BRAND_NAME} POS app on your phone or tablet by Bluetooth`,\n },\n {\n id: 'stripe-reader-s700-dock',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader S700 Dock',\n displayName: 'Charging dock for the S700 and S710',\n use: 'Keeps the reader charged at the counter and adds an Ethernet port',\n },\n {\n id: 'bbpos-wisepos-e-dock',\n kind: 'accessory',\n stripeProductName: 'BBPOS WisePOS E Dock',\n displayName: 'Charging dock for the WisePOS E',\n use: 'Keeps the reader charged at the counter and adds an Ethernet port',\n },\n {\n id: 'stripe-reader-m2-mount',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader M2 Mount',\n displayName: 'Mount for the M2',\n use: 'Holds the M2 in place at the counter',\n },\n]\n\nexport function posHardwareSku(id: string): PosHardwareSku | null {\n return POS_HARDWARE_SKUS.find((sku) => sku.id === id) ?? null\n}\n\nexport type PosHardwareShippingMethod = 'standard' | 'express' | 'priority'\n\nexport const POS_HARDWARE_SHIPPING_LABELS: Record<PosHardwareShippingMethod, string> = {\n standard: 'Standard shipping',\n express: 'Express shipping',\n priority: 'Priority shipping',\n}\n\nconst SHIPPING_METHODS = Object.keys(POS_HARDWARE_SHIPPING_LABELS) as PosHardwareShippingMethod[]\n\nexport function isPosHardwareShippingMethod(value: unknown): value is PosHardwareShippingMethod {\n return typeof value === 'string' && (SHIPPING_METHODS as string[]).includes(value)\n}\n\n/** The shop's configuration, once every figure it needs is present and sane. */\nexport interface PosHardwareConfig {\n /** Stripe's price for each configured item, in cents. */\n costs: Partial<Record<PosHardwareSkuId, number>>\n markupBps: number\n shipping: Array<{ method: PosHardwareShippingMethod; cents: number }>\n automaticTax: boolean\n}\n\n/** The highest single figure the parser accepts, in cents: $10,000. */\nconst MAX_FIGURE_CENTS = 1_000_000\n/** The highest markup the parser accepts: 1000%. */\nconst MAX_MARKUP_BPS = 100_000\n\nconst isCents = (value: unknown, min: number): value is number =>\n typeof value === 'number' &&\n Number.isSafeInteger(value) &&\n value >= min &&\n value <= MAX_FIGURE_CENTS\n\nfunction parseJsonObject(raw: string | undefined): Record<string, unknown> | null {\n const text = String(raw ?? '').trim()\n if (!text) return null\n try {\n const parsed = JSON.parse(text) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null\n } catch {\n return null\n }\n}\n\n/**\n * The shop's configuration from the environment, or `null` while the shop is\n * hidden: not released, no reader priced, a malformed markup, or no shipping\n * figure. An ABSENT markup is the default\n * ({@link POS_HARDWARE_DEFAULT_MARKUP_BPS}), so a deployment can never sell\n * at cost by forgetting a variable; `0` is a valid, explicit choice to sell\n * at Stripe's price. A markup that is set but unreadable hides the shop\n * rather than guessing.\n *\n * Unknown item ids and malformed figures are dropped one by one rather than\n * failing the whole shop, so a typo in one price hides that one item.\n */\nexport function parsePosHardwareConfig(\n env: Record<string, string | undefined>,\n): PosHardwareConfig | null {\n if (String(env[POS_HARDWARE_ENV.enabled] ?? '').trim().toLowerCase() !== 'true') return null\n\n const markupText = String(env[POS_HARDWARE_ENV.markupBps] ?? '').trim()\n if (markupText && !/^\\d+$/.test(markupText)) return null\n const markupBps = markupText ? Number(markupText) : POS_HARDWARE_DEFAULT_MARKUP_BPS\n if (!Number.isSafeInteger(markupBps) || markupBps > MAX_MARKUP_BPS) return null\n\n const prices = parseJsonObject(env[POS_HARDWARE_ENV.prices])\n if (!prices) return null\n const costs: Partial<Record<PosHardwareSkuId, number>> = {}\n for (const sku of POS_HARDWARE_SKUS) {\n const cost = prices[sku.id]\n if (isCents(cost, 1)) costs[sku.id] = cost\n }\n const anyReader = POS_HARDWARE_SKUS.some((sku) => sku.kind === 'reader' && costs[sku.id] !== undefined)\n if (!anyReader) return null\n\n const shippingRaw = parseJsonObject(env[POS_HARDWARE_ENV.shipping])\n if (!shippingRaw) return null\n const shipping = SHIPPING_METHODS.flatMap((method) => {\n const cents = shippingRaw[method]\n return isCents(cents, 0) ? [{ method, cents }] : []\n })\n if (!shipping.length) return null\n\n return {\n costs,\n markupBps,\n shipping,\n automaticTax:\n String(env[POS_HARDWARE_ENV.automaticTax] ?? '').trim().toLowerCase() === 'true',\n }\n}\n\n/**\n * What the merchant pays for one item: Stripe's price plus the markup,\n * rounded half up to the cent, in integer math throughout.\n *\n * `cost + round(cost × bps / 10 000)`. The markup is basis points ON COST, so\n * a 50% markup is `5000` and a price at which half of it is profit (a 50%\n * margin) is `10000`. Whichever the business means, it is this one number.\n */\nexport function posHardwareUnitCents(costCents: number, markupBps: number): number {\n if (!Number.isSafeInteger(costCents) || costCents <= 0) return 0\n if (!Number.isSafeInteger(markupBps) || markupBps < 0) return costCents\n return costCents + Math.floor((costCents * markupBps + 5_000) / 10_000)\n}\n\n/** An item as the shop offers it: the merchant's price, never Stripe's. */\nexport interface PosHardwareOffer {\n sku: PosHardwareSkuId\n kind: PosHardwareSku['kind']\n name: string\n use: string\n unitCents: number\n}\n\n/** The items on offer, readers first, in catalog order. */\nexport function posHardwareOffers(config: PosHardwareConfig): PosHardwareOffer[] {\n return POS_HARDWARE_SKUS.flatMap((sku) => {\n const cost = config.costs[sku.id]\n if (cost === undefined) return []\n return [\n {\n sku: sku.id,\n kind: sku.kind,\n name: sku.displayName,\n use: sku.use,\n unitCents: posHardwareUnitCents(cost, config.markupBps),\n },\n ]\n })\n}\n\nexport interface PosHardwareLine {\n sku: PosHardwareSkuId\n name: string\n quantity: number\n unitCents: number\n lineCents: number\n}\n\nexport type PosHardwareCartResult =\n | { ok: true; lines: PosHardwareLine[]; subtotalCents: number }\n | { ok: false; error: string }\n\n/**\n * A merchant's basket priced on the SERVER: every item must be on offer, each\n * quantity a whole number from 1 to {@link POS_HARDWARE_MAX_PER_LINE}, and\n * the order at most {@link POS_HARDWARE_MAX_UNITS} units. A repeated item is\n * folded into one line. Prices come from the config, never the request.\n */\nexport function pricePosHardwareCart(\n config: PosHardwareConfig,\n requested: unknown,\n): PosHardwareCartResult {\n if (!Array.isArray(requested) || requested.length === 0) {\n return { ok: false, error: 'Choose at least one item.' }\n }\n const offers = new Map(posHardwareOffers(config).map((offer) => [offer.sku as string, offer]))\n const quantities = new Map<string, number>()\n for (const entry of requested) {\n const sku = String((entry as { sku?: unknown })?.sku ?? '')\n const quantity = Number((entry as { quantity?: unknown })?.quantity)\n if (!offers.has(sku)) return { ok: false, error: 'That item is not available.' }\n if (!Number.isInteger(quantity) || quantity < 1) {\n return { ok: false, error: 'Each quantity must be a whole number of at least 1.' }\n }\n quantities.set(sku, (quantities.get(sku) ?? 0) + quantity)\n }\n let units = 0\n const lines: PosHardwareLine[] = []\n for (const [sku, quantity] of quantities) {\n if (quantity > POS_HARDWARE_MAX_PER_LINE) {\n return { ok: false, error: `Order at most ${POS_HARDWARE_MAX_PER_LINE} of each item.` }\n }\n units += quantity\n const offer = offers.get(sku) as PosHardwareOffer\n lines.push({\n sku: offer.sku,\n name: offer.name,\n quantity,\n unitCents: offer.unitCents,\n lineCents: offer.unitCents * quantity,\n })\n }\n if (units > POS_HARDWARE_MAX_UNITS) {\n return { ok: false, error: `One order holds at most ${POS_HARDWARE_MAX_UNITS} items.` }\n }\n // Accessories alone are allowed: a merchant may want a second dock.\n return { ok: true, lines, subtotalCents: lines.reduce((sum, line) => sum + line.lineCents, 0) }\n}\n\n/*==========================================\n * THE CHECKOUT SESSION'S METADATA.\n *\n * The webhook builds the order from the session Stripe hands back, so the\n * lines ride in its metadata in a compact form (`sku*qty*unit;…`, well inside\n * Stripe's 500-character value limit for the at most seven distinct items),\n * and the webhook re-reads every one against the catalog rather than trusting\n * a name from the wire.\n *=========================================*/\n\nexport const POS_HARDWARE_METADATA_TYPE = 'pos-hardware-order'\n\nexport function encodePosHardwareLines(lines: readonly PosHardwareLine[]): string {\n return lines.map((line) => `${line.sku}*${line.quantity}*${line.unitCents}`).join(';')\n}\n\nexport function decodePosHardwareLines(encoded: unknown): PosHardwareLine[] | null {\n const text = String(encoded ?? '').trim()\n if (!text) return null\n const lines: PosHardwareLine[] = []\n for (const part of text.split(';')) {\n const [skuId, quantityText, unitText] = part.split('*')\n const sku = posHardwareSku(String(skuId ?? ''))\n const quantity = Number(quantityText)\n const unitCents = Number(unitText)\n if (!sku) return null\n if (!Number.isInteger(quantity) || quantity < 1 || quantity > POS_HARDWARE_MAX_PER_LINE) return null\n if (!Number.isSafeInteger(unitCents) || unitCents < 1) return null\n lines.push({ sku: sku.id, name: sku.displayName, quantity, unitCents, lineCents: quantity * unitCents })\n }\n return lines\n}\n\n/*==========================================\n * THE ORDER AND ITS STATUSES.\n *=========================================*/\n\nexport type PosHardwareOrderStatus =\n | 'paid'\n | 'ordered'\n | 'shipped'\n | 'delivered'\n | 'registered'\n | 'canceled'\n | 'refunded'\n\nexport const POS_HARDWARE_ORDER_STATUSES: readonly PosHardwareOrderStatus[] = [\n 'paid',\n 'ordered',\n 'shipped',\n 'delivered',\n 'registered',\n 'canceled',\n 'refunded',\n]\n\nexport function isPosHardwareOrderStatus(value: unknown): value is PosHardwareOrderStatus {\n return typeof value === 'string' && (POS_HARDWARE_ORDER_STATUSES as readonly string[]).includes(value)\n}\n\n/** What the merchant reads on the Hardware card. */\nexport const POS_HARDWARE_STATUS_LABELS: Record<PosHardwareOrderStatus, string> = {\n paid: 'Order received',\n ordered: 'Being prepared',\n shipped: 'Shipped',\n delivered: 'Delivered',\n registered: 'Set up',\n canceled: 'Canceled',\n refunded: 'Refunded',\n}\n\n/** What staff read on the orders page: where the order is in fulfillment. */\nexport const POS_HARDWARE_STAFF_STATUS_LABELS: Record<PosHardwareOrderStatus, string> = {\n paid: 'Paid',\n ordered: 'Ordered from Stripe',\n shipped: 'Shipped',\n delivered: 'Delivered',\n registered: 'Registered',\n canceled: 'Canceled',\n refunded: 'Refunded',\n}\n\n/**\n * The forward steps staff may move an order through. Canceling and refunding\n * are separate acts with their own rule ({@link posHardwareRefundable}),\n * because each returns money.\n */\nconst NEXT_STATUS: Partial<Record<PosHardwareOrderStatus, PosHardwareOrderStatus>> = {\n paid: 'ordered',\n ordered: 'shipped',\n shipped: 'delivered',\n delivered: 'registered',\n}\n\nexport function nextPosHardwareStatus(\n status: PosHardwareOrderStatus,\n): PosHardwareOrderStatus | null {\n return NEXT_STATUS[status] ?? null\n}\n\n/**\n * Whether the order may still be canceled or refunded in full: only before it\n * ships. Once a parcel is on its way the money comes back through a return of\n * the hardware, which is a separate process this phase does not automate.\n */\nexport function posHardwareRefundable(status: PosHardwareOrderStatus): boolean {\n return status === 'paid' || status === 'ordered'\n}\n\nexport interface PosHardwareAddress {\n line1: string\n line2?: string\n city: string\n state?: string\n postalCode: string\n country: string\n}\n\n/**\n * What sales tax is sourced by: where the order shipped, without the street\n * or the person. An erasure keeps this much of the address (see\n * {@link posHardwareBuyerErasedPatch}) and the retained sale record holds\n * nothing finer ({@link PosHardwareSaleRecord}).\n */\nexport interface PosHardwareTaxRegion {\n country: string\n state: string | null\n postalCode: string | null\n}\n\nexport interface PosHardwareTracking {\n carrier: string\n number: string\n url: string | null\n}\n\n/** `orgs/{orgId}/hardwareOrders/{orderId}`, written only by the server. */\nexport interface PosHardwareOrder {\n orgId: string\n /** The site whose POS settings the order was placed from. */\n hostId: string\n /** The member who started the checkout; `null` once their account is erased. */\n createdBy: string | null\n /** Where the order emails go: the address entered at checkout. */\n contactEmail: string | null\n lines: PosHardwareLine[]\n subtotalCents: number\n shipping: {\n method: PosHardwareShippingMethod\n label: string\n cents: number\n name: string | null\n phone: string | null\n address: PosHardwareAddress | null\n /**\n * Set when the buyer's account is erased, in place of `address`: the part\n * of it sales tax is sourced by.\n */\n taxRegion?: PosHardwareTaxRegion | null\n }\n taxCents: number\n totalCents: number\n currency: string\n stripeCheckoutSessionId: string\n stripePaymentIntentId: string | null\n /** Stripe's hardware order id, recorded by staff (phase 2 sets it itself). */\n stripeHardwareOrderId: string | null\n tracking: PosHardwareTracking | null\n status: PosHardwareOrderStatus\n livemode: boolean\n createdAtMs: number\n updatedAtMs: number\n paidAtMs: number\n orderedAtMs?: number\n shippedAtMs?: number\n deliveredAtMs?: number\n registeredAtMs?: number\n canceledAtMs?: number\n refundedAtMs?: number\n refundId?: string | null\n /** The cents Stripe reported refunding. */\n refundedCents?: number\n /** When the buyer's account was erased and their details taken off the order. */\n buyerErasedAtMs?: number\n}\n\n/** The timestamp field each status stamps when it is reached. */\nexport const POS_HARDWARE_STATUS_STAMP: Record<PosHardwareOrderStatus, keyof PosHardwareOrder> = {\n paid: 'paidAtMs',\n ordered: 'orderedAtMs',\n shipped: 'shippedAtMs',\n delivered: 'deliveredAtMs',\n registered: 'registeredAtMs',\n canceled: 'canceledAtMs',\n refunded: 'refundedAtMs',\n}\n\n/** The Hardware card's anchor: Checkout returns to it and the emails link at it. */\nexport const POS_HARDWARE_ANCHOR = 'pos-hardware'\n\n/** The collection under `orgs/{orgId}`, named once. */\nexport const POS_HARDWARE_ORDERS_COLLECTION = 'hardwareOrders'\n\n/*==========================================\n * THE SALE RECORD THE PLATFORM KEEPS (AGL-4038).\n *\n * A hardware order is the PLATFORM's own sale: the platform is the seller of\n * record and owes the sales tax on it. So beside every order the server keeps\n * `posHardwareSales/{orderId}`, a TOP-LEVEL record of what tax and accounting\n * need — the lines, the amounts, the tax, Stripe's ids, the status dates and\n * the region it shipped to — and nothing about the person who bought it.\n *\n * It is the `platformRevenue` / `storefrontTaxCollected` shape on purpose\n * (AGL-1811, AGL-1904): keyed to the workspace by FIELD, outside\n * `orgs/{orgId}`, so the workspace erasure's `recursiveDelete(orgs/{orgId})`\n * cannot see it, and never in any org-keyed sweep. GDPR Art. 17(3)(b) exempts\n * records kept to comply with a legal obligation. The order under the\n * workspace, with the buyer's name, street, phone, email and tracking, is\n * erased with the workspace as before.\n *\n * Written in the same transaction as every write to the order, from the\n * order as it stands after that write, so the two never disagree and a\n * missing record heals on the next write.\n *=========================================*/\n\n/** The top-level collection of retained hardware sale records. */\nexport const POS_HARDWARE_SALES_COLLECTION = 'posHardwareSales'\n\n/** `posHardwareSales/{orderId}`: an order with every personal detail left out. */\nexport interface PosHardwareSaleRecord {\n orderId: string\n /** The workspace that bought it, by id; the record outlives the workspace. */\n orgId: string\n lines: Array<Pick<PosHardwareLine, 'sku' | 'name' | 'quantity' | 'unitCents' | 'lineCents'>>\n subtotalCents: number\n shippingMethod: PosHardwareShippingMethod\n shippingCents: number\n taxCents: number\n totalCents: number\n currency: string\n /** The sales tax sourcing input: country, state and postal code, never the street. */\n shipTo: PosHardwareTaxRegion | null\n stripeCheckoutSessionId: string | null\n stripePaymentIntentId: string | null\n stripeHardwareOrderId: string | null\n refundId: string | null\n refundedCents: number | null\n status: PosHardwareOrderStatus\n livemode: boolean\n createdAtMs: number\n updatedAtMs: number\n paidAtMs: number | null\n orderedAtMs: number | null\n shippedAtMs: number | null\n deliveredAtMs: number | null\n registeredAtMs: number | null\n canceledAtMs: number | null\n refundedAtMs: number | null\n}\n\nconst stampOrNull = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\nconst textOrNull = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\n\n/** The tax region of an order: its address cut down, or what an erasure left. */\nexport function posHardwareTaxRegion(order: Pick<PosHardwareOrder, 'shipping'>): PosHardwareTaxRegion | null {\n const address = order.shipping?.address\n if (address?.country) {\n return {\n country: address.country,\n state: textOrNull(address.state),\n postalCode: textOrNull(address.postalCode),\n }\n }\n const kept = order.shipping?.taxRegion\n return kept?.country\n ? { country: kept.country, state: textOrNull(kept.state), postalCode: textOrNull(kept.postalCode) }\n : null\n}\n\n/**\n * The retained sale record of an order, as it stands. Every field is picked\n * by name, so a field added to the order later reaches the record only when\n * someone decides it should.\n */\nexport function posHardwareSaleRecord(orderId: string, order: PosHardwareOrder): PosHardwareSaleRecord {\n return {\n orderId,\n orgId: order.orgId,\n lines: (order.lines ?? []).map((line) => ({\n sku: line.sku,\n name: line.name,\n quantity: line.quantity,\n unitCents: line.unitCents,\n lineCents: line.lineCents,\n })),\n subtotalCents: order.subtotalCents ?? 0,\n shippingMethod: order.shipping?.method ?? 'standard',\n shippingCents: order.shipping?.cents ?? 0,\n taxCents: order.taxCents ?? 0,\n totalCents: order.totalCents ?? 0,\n currency: order.currency ?? POS_HARDWARE_CURRENCY,\n shipTo: posHardwareTaxRegion(order),\n stripeCheckoutSessionId: textOrNull(order.stripeCheckoutSessionId),\n stripePaymentIntentId: textOrNull(order.stripePaymentIntentId),\n stripeHardwareOrderId: textOrNull(order.stripeHardwareOrderId),\n refundId: textOrNull(order.refundId),\n refundedCents: stampOrNull(order.refundedCents),\n status: order.status,\n livemode: order.livemode === true,\n createdAtMs: order.createdAtMs ?? 0,\n updatedAtMs: order.updatedAtMs ?? order.createdAtMs ?? 0,\n paidAtMs: stampOrNull(order.paidAtMs),\n orderedAtMs: stampOrNull(order.orderedAtMs),\n shippedAtMs: stampOrNull(order.shippedAtMs),\n deliveredAtMs: stampOrNull(order.deliveredAtMs),\n registeredAtMs: stampOrNull(order.registeredAtMs),\n canceledAtMs: stampOrNull(order.canceledAtMs),\n refundedAtMs: stampOrNull(order.refundedAtMs),\n }\n}\n\n/**\n * What an account erasure does to an order the person placed (AGL-4038): the\n * workspace keeps the order and every amount on it; the buyer comes off it —\n * who started it, the email, the name, the phone, the street and the\n * tracking, which names the parcel to a carrier who would say where it went.\n * The country, state and postal code stay, because sales tax is sourced by\n * them. Deterministic but for the stamp, and an order already erased no\n * longer answers to the person's uid, so a second run finds nothing.\n */\nexport function posHardwareBuyerErasedPatch(\n order: Pick<PosHardwareOrder, 'shipping'>,\n now: number,\n): Record<string, unknown> {\n // The whole map, read in the same transaction: a dotted-path update would\n // leave a field added to `shipping` later standing with the person in it.\n const shipping = order.shipping\n return {\n createdBy: null,\n contactEmail: null,\n shipping: {\n method: shipping?.method ?? 'standard',\n label: shipping?.label ?? POS_HARDWARE_SHIPPING_LABELS[shipping?.method ?? 'standard'],\n cents: shipping?.cents ?? 0,\n name: null,\n phone: null,\n address: null,\n taxRegion: posHardwareTaxRegion(order),\n },\n tracking: null,\n buyerErasedAtMs: now,\n updatedAtMs: now,\n }\n}\n\n/** An order id: `hw_` and twenty hex characters. */\nexport const POS_HARDWARE_ORDER_ID = /^hw_[a-f0-9]{20}$/\n\n/** Dollars and cents for the card and the emails. */\nexport function formatPosHardwareCents(cents: number): string {\n const value = Number.isFinite(cents) ? cents : 0\n return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value / 100)\n}\n"],"names":["PLATFORM_BRAND_NAME","POS_HARDWARE_CURRENCY","POS_HARDWARE_SHIP_COUNTRIES","POS_HARDWARE_MAX_PER_LINE","POS_HARDWARE_MAX_UNITS","POS_HARDWARE_ENV","enabled","prices","markupBps","shipping","automaticTax","POS_HARDWARE_DEFAULT_MARKUP_BPS","POS_HARDWARE_SKUS","id","kind","stripeProductName","displayName","use","posHardwareSku","find","sku","POS_HARDWARE_SHIPPING_LABELS","standard","express","priority","SHIPPING_METHODS","Object","keys","isPosHardwareShippingMethod","value","includes","MAX_FIGURE_CENTS","MAX_MARKUP_BPS","isCents","min","Number","isSafeInteger","parseJsonObject","raw","text","String","trim","parsed","JSON","parse","Array","isArray","parsePosHardwareConfig","env","toLowerCase","markupText","test","costs","cost","anyReader","some","undefined","shippingRaw","flatMap","method","cents","length","posHardwareUnitCents","costCents","Math","floor","posHardwareOffers","config","name","unitCents","pricePosHardwareCart","requested","ok","error","offers","Map","map","offer","quantities","entry","quantity","has","isInteger","set","get","units","lines","push","lineCents","subtotalCents","reduce","sum","line","POS_HARDWARE_METADATA_TYPE","encodePosHardwareLines","join","decodePosHardwareLines","encoded","part","split","skuId","quantityText","unitText","POS_HARDWARE_ORDER_STATUSES","isPosHardwareOrderStatus","POS_HARDWARE_STATUS_LABELS","paid","ordered","shipped","delivered","registered","canceled","refunded","POS_HARDWARE_STAFF_STATUS_LABELS","NEXT_STATUS","nextPosHardwareStatus","status","posHardwareRefundable","POS_HARDWARE_STATUS_STAMP","POS_HARDWARE_ANCHOR","POS_HARDWARE_ORDERS_COLLECTION","POS_HARDWARE_SALES_COLLECTION","stampOrNull","isFinite","textOrNull","posHardwareTaxRegion","order","address","country","state","postalCode","kept","taxRegion","posHardwareSaleRecord","orderId","orgId","shippingMethod","shippingCents","taxCents","totalCents","currency","shipTo","stripeCheckoutSessionId","stripePaymentIntentId","stripeHardwareOrderId","refundId","refundedCents","livemode","createdAtMs","updatedAtMs","paidAtMs","orderedAtMs","shippedAtMs","deliveredAtMs","registeredAtMs","canceledAtMs","refundedAtMs","posHardwareBuyerErasedPatch","now","createdBy","contactEmail","label","phone","tracking","buyerErasedAtMs","POS_HARDWARE_ORDER_ID","formatPosHardwareCents","Intl","NumberFormat","style","format"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/pos-hardware.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\n/*==========================================\n * THE POS HARDWARE SHOP (AGL-4038), PHASE 1: THE MODEL.\n *\n * A merchant buys Stripe Terminal readers from the platform, the platform\n * places the order in Stripe's Terminal hardware shop and Stripe ships it\n * straight to the merchant. The platform is the seller of record, because the\n * register's card payments are destination charges on the PLATFORM account\n * and a reader must be registered there to take them.\n *\n * THE CATALOG IS CODE, THE PRICES ARE CONFIG. Which readers the register\n * works with is a fact about this codebase; what Stripe charges for them, the\n * markup on top and what shipping costs are business decisions that change\n * without a release. Every figure comes from the console's environment, read\n * by {@link parsePosHardwareConfig}, and a missing or malformed figure keeps\n * the shop HIDDEN: it is a gate only the platform can lift, so no workspace is\n * shown a control it cannot use.\n *\n * Everything here is pure, so the price math, the metadata a Checkout Session\n * carries and the status rules are specced without Firestore or Stripe.\n *=========================================*/\n\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport type { PosHardwareTermsAcceptance } from './pos-hardware-terms'\n\n/** The register's currency, and so the shop's: US dollars. */\nexport const POS_HARDWARE_CURRENCY = 'usd'\n\n/**\n * Where the shop ships. Stripe's Terminal hardware shop ships to many\n * countries, but the register sells in US dollars only and the readers a\n * merchant buys here are registered to a US Location, so the shop is US-only\n * (Stripe itself excludes the US Virgin Islands, military addresses and\n * P.O. boxes).\n */\nexport const POS_HARDWARE_SHIP_COUNTRIES: readonly string[] = ['US']\n\n/** The most of one item, and the most units, a single order may hold. */\nexport const POS_HARDWARE_MAX_PER_LINE = 10\nexport const POS_HARDWARE_MAX_UNITS = 20\n\n/** The env names the shop reads, named once for the reader and the docs. */\nexport const POS_HARDWARE_ENV = {\n /** `true` releases the shop. Absent or anything else keeps it hidden. */\n enabled: 'POS_HARDWARE_SHOP_ENABLED',\n /** JSON `{ \"<sku>\": <Stripe's price in cents> }`. */\n prices: 'POS_HARDWARE_PRICES',\n /**\n * The markup on Stripe's price, an integer in basis points (100 = 1%).\n * Absent, {@link POS_HARDWARE_DEFAULT_MARKUP_BPS} applies.\n */\n markupBps: 'POS_HARDWARE_MARKUP_BPS',\n /** JSON `{ \"standard\" | \"express\" | \"priority\": <cents> }`. */\n shipping: 'POS_HARDWARE_SHIPPING',\n /** `true` turns Stripe automatic tax on for hardware checkouts. */\n automaticTax: 'POS_HARDWARE_AUTOMATIC_TAX',\n /**\n * `true` lets the platform's own store sell hardware IN PERSON at its\n * register (AGL-4038): a reader handed over at an event, attached to the\n * buyer's workspace. Absent or anything else keeps it off.\n */\n inPersonEnabled: 'POS_HARDWARE_IN_PERSON_ENABLED',\n /** The site id of the platform's own store whose register sells hardware in person. */\n sellerHostId: 'POS_HARDWARE_SELLER_HOST_ID',\n /** The workspace that owns that store. */\n sellerOrgId: 'POS_HARDWARE_SELLER_ORG_ID',\n} as const\n\n/**\n * The markup when the deployment sets none: 3000 basis points on Stripe's\n * price. The business sets it; this is the one place the default lives, and\n * `POS_HARDWARE_MARKUP_BPS` overrides it.\n */\nexport const POS_HARDWARE_DEFAULT_MARKUP_BPS = 3000\n\nexport type PosHardwareSkuId =\n | 'stripe-reader-s710'\n | 'stripe-reader-s700'\n | 'stripe-reader-t600'\n | 'stripe-reader-m2'\n | 'bbpos-wisepos-e'\n | 'stripe-reader-s700-dock'\n | 'stripe-reader-hub'\n | 'stripe-reader-s700-case'\n | 'stripe-reader-s700-cable'\n | 'stripe-reader-charger'\n | 'stripe-reader-m2-dock'\n | 'stripe-reader-m2-mount'\n | 'bbpos-wisepos-e-dock'\n | 'bbpos-wisepos-e-power-cable'\n\nexport interface PosHardwareSku {\n id: PosHardwareSkuId\n kind: 'reader' | 'accessory'\n /** What Stripe's hardware shop calls it, for the staff member ordering it. */\n stripeProductName: string\n /** What the merchant sees. */\n displayName: string\n /** One line on what it is for. */\n use: string\n}\n\n/**\n * Every item the shop can sell. An item is offered only once its Stripe\n * price is configured, so one Stripe stops selling disappears by deleting its\n * price, not by a release.\n */\nexport const POS_HARDWARE_SKUS: readonly PosHardwareSku[] = [\n {\n id: 'stripe-reader-s710',\n kind: 'reader',\n stripeProductName: 'Stripe Reader S710',\n displayName: 'Stripe Reader S710',\n // 4G needs Stripe's cellular service, billed monthly per reader, which a\n // store turns on for its S710s where it is offered (AGL-4038): the reader\n // alone does not bring a connection.\n use: 'Handheld for events and parking lots; 4G needs Stripe’s cellular service, billed monthly',\n },\n {\n id: 'stripe-reader-s700',\n kind: 'reader',\n stripeProductName: 'Stripe Reader S700',\n displayName: 'Stripe Reader S700',\n use: 'Handheld or countertop reader on Wi-Fi',\n },\n {\n id: 'stripe-reader-t600',\n kind: 'reader',\n stripeProductName: 'Stripe Reader T600',\n displayName: 'Stripe Reader T600',\n use: 'Countertop reader on Wi-Fi, or on Ethernet with the hub',\n },\n {\n id: 'bbpos-wisepos-e',\n kind: 'reader',\n stripeProductName: 'BBPOS WisePOS E',\n displayName: 'BBPOS WisePOS E',\n use: 'Countertop reader on Wi-Fi',\n },\n {\n id: 'stripe-reader-m2',\n kind: 'reader',\n stripeProductName: 'Stripe Reader M2',\n displayName: 'Stripe Reader M2',\n use: `Pairs with the ${PLATFORM_BRAND_NAME} POS app on your phone or tablet by Bluetooth`,\n },\n {\n id: 'stripe-reader-s700-dock',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader S700/S710 Dock',\n displayName: 'Charging dock for the S700 and S710',\n use: 'Keeps the reader charged at the counter and adds an Ethernet port',\n },\n {\n id: 'stripe-reader-hub',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader Hub',\n displayName: 'Hub for the S700, S710 and T600',\n use: 'Connects the reader to Ethernet and power',\n },\n {\n id: 'stripe-reader-s700-case',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader S700/S710 Case',\n displayName: 'Case for the S700 and S710',\n use: 'Protects a handheld reader on the move',\n },\n {\n id: 'stripe-reader-s700-cable',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader S700/S710 Cable',\n displayName: 'Cable for the S700 and S710',\n use: 'A spare cable for the reader',\n },\n {\n id: 'stripe-reader-charger',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader Charger',\n displayName: 'Charger for Stripe readers',\n use: 'A spare wall charger',\n },\n {\n id: 'stripe-reader-m2-dock',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader M2 Dock',\n displayName: 'Charging dock for the M2',\n use: 'Keeps the M2 charged at the counter',\n },\n {\n id: 'stripe-reader-m2-mount',\n kind: 'accessory',\n stripeProductName: 'Stripe Reader M2 Mount',\n displayName: 'Mount for the M2',\n use: 'Holds the M2 in place at the counter',\n },\n {\n id: 'bbpos-wisepos-e-dock',\n kind: 'accessory',\n stripeProductName: 'BBPOS WisePOS E Dock',\n displayName: 'Charging dock for the WisePOS E',\n use: 'Keeps the reader charged at the counter and adds an Ethernet port',\n },\n {\n id: 'bbpos-wisepos-e-power-cable',\n kind: 'accessory',\n stripeProductName: 'BBPOS WisePOS E 5ft Power Cable',\n displayName: '5 ft power cable for the WisePOS E',\n use: 'A spare power cable for the reader',\n },\n]\n\nexport function posHardwareSku(id: string): PosHardwareSku | null {\n return POS_HARDWARE_SKUS.find((sku) => sku.id === id) ?? null\n}\n\nexport type PosHardwareShippingMethod = 'standard' | 'express' | 'priority'\n\nexport const POS_HARDWARE_SHIPPING_LABELS: Record<PosHardwareShippingMethod, string> = {\n standard: 'Standard shipping',\n express: 'Express shipping',\n priority: 'Priority shipping',\n}\n\nconst SHIPPING_METHODS = Object.keys(POS_HARDWARE_SHIPPING_LABELS) as PosHardwareShippingMethod[]\n\nexport function isPosHardwareShippingMethod(value: unknown): value is PosHardwareShippingMethod {\n return typeof value === 'string' && (SHIPPING_METHODS as string[]).includes(value)\n}\n\n/**\n * How an order reached the buyer: one of the shop's shipping speeds, or\n * `in_person` — handed over at the platform's own register (AGL-4038), with\n * no parcel, address or carrier. Never offered at checkout.\n */\nexport type PosHardwareOrderShippingMethod = PosHardwareShippingMethod | 'in_person'\n\nexport const POS_HARDWARE_IN_PERSON_SHIPPING_LABEL = 'Handed over in person'\n\n/** The label of any order's method, the in-person one included. */\nexport function posHardwareShippingLabel(method: PosHardwareOrderShippingMethod | null | undefined): string {\n if (method === 'in_person') return POS_HARDWARE_IN_PERSON_SHIPPING_LABEL\n return POS_HARDWARE_SHIPPING_LABELS[isPosHardwareShippingMethod(method) ? method : 'standard']\n}\n\n/** The shop's configuration, once every figure it needs is present and sane. */\nexport interface PosHardwareConfig {\n /** Stripe's price for each configured item, in cents. */\n costs: Partial<Record<PosHardwareSkuId, number>>\n markupBps: number\n shipping: Array<{ method: PosHardwareShippingMethod; cents: number }>\n automaticTax: boolean\n}\n\n/** The highest single figure the parser accepts, in cents: $10,000. */\nconst MAX_FIGURE_CENTS = 1_000_000\n/** The highest markup the parser accepts: 1000%. */\nconst MAX_MARKUP_BPS = 100_000\n\nconst isCents = (value: unknown, min: number): value is number =>\n typeof value === 'number' &&\n Number.isSafeInteger(value) &&\n value >= min &&\n value <= MAX_FIGURE_CENTS\n\nfunction parseJsonObject(raw: string | undefined): Record<string, unknown> | null {\n const text = String(raw ?? '').trim()\n if (!text) return null\n try {\n const parsed = JSON.parse(text) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null\n } catch {\n return null\n }\n}\n\n/**\n * The shop's configuration from the environment, or `null` while the shop is\n * hidden: not released, no reader priced, a malformed markup, or no shipping\n * figure. An ABSENT markup is the default\n * ({@link POS_HARDWARE_DEFAULT_MARKUP_BPS}), so a deployment can never sell\n * at cost by forgetting a variable; `0` is a valid, explicit choice to sell\n * at Stripe's price. A markup that is set but unreadable hides the shop\n * rather than guessing.\n *\n * Unknown item ids and malformed figures are dropped one by one rather than\n * failing the whole shop, so a typo in one price hides that one item.\n */\nexport function parsePosHardwareConfig(\n env: Record<string, string | undefined>,\n): PosHardwareConfig | null {\n if (String(env[POS_HARDWARE_ENV.enabled] ?? '').trim().toLowerCase() !== 'true') return null\n\n const markupText = String(env[POS_HARDWARE_ENV.markupBps] ?? '').trim()\n if (markupText && !/^\\d+$/.test(markupText)) return null\n const markupBps = markupText ? Number(markupText) : POS_HARDWARE_DEFAULT_MARKUP_BPS\n if (!Number.isSafeInteger(markupBps) || markupBps > MAX_MARKUP_BPS) return null\n\n const prices = parseJsonObject(env[POS_HARDWARE_ENV.prices])\n if (!prices) return null\n const costs: Partial<Record<PosHardwareSkuId, number>> = {}\n for (const sku of POS_HARDWARE_SKUS) {\n const cost = prices[sku.id]\n if (isCents(cost, 1)) costs[sku.id] = cost\n }\n const anyReader = POS_HARDWARE_SKUS.some((sku) => sku.kind === 'reader' && costs[sku.id] !== undefined)\n if (!anyReader) return null\n\n const shippingRaw = parseJsonObject(env[POS_HARDWARE_ENV.shipping])\n if (!shippingRaw) return null\n const shipping = SHIPPING_METHODS.flatMap((method) => {\n const cents = shippingRaw[method]\n return isCents(cents, 0) ? [{ method, cents }] : []\n })\n if (!shipping.length) return null\n\n return {\n costs,\n markupBps,\n shipping,\n automaticTax:\n String(env[POS_HARDWARE_ENV.automaticTax] ?? '').trim().toLowerCase() === 'true',\n }\n}\n\n/*==========================================\n * SELLING HARDWARE IN PERSON (AGL-4038).\n *\n * The platform also sells readers face to face, at an event, from its OWN\n * store's register: the same products a store sells, rung up like any other\n * sale, priced and taxed by that store, paid into that store's account. The\n * one difference is what follows the sale — a hardware order for the buyer's\n * workspace, so the reader shows up there and can be set up, returned or\n * refunded like one bought online.\n *\n * Which store that is, is configuration: the site and its workspace, named in\n * the environment beside the shop's other figures. A product on that store\n * names the hardware item it is ({@link isPosHardwareSkuId} on the product's\n * `hardwareSku`), and no other store's product ever does, whatever it says.\n *=========================================*/\n\n/** The platform's own store, once the in-person sale is turned on and named. */\nexport interface PosHardwareInPersonConfig {\n sellerHostId: string\n sellerOrgId: string\n}\n\nconst CONFIG_ID = /^[A-Za-z0-9_-]{1,128}$/\n\n/**\n * The in-person sale's configuration from the environment, or `null` while\n * it is off: not turned on, or the store or its workspace not named, or\n * named with something that cannot be an id. It needs nothing of the online\n * shop's: the store's own products carry the prices.\n */\nexport function parsePosHardwareInPersonConfig(\n env: Record<string, string | undefined>,\n): PosHardwareInPersonConfig | null {\n if (String(env[POS_HARDWARE_ENV.inPersonEnabled] ?? '').trim().toLowerCase() !== 'true') return null\n const sellerHostId = String(env[POS_HARDWARE_ENV.sellerHostId] ?? '').trim()\n const sellerOrgId = String(env[POS_HARDWARE_ENV.sellerOrgId] ?? '').trim()\n if (!CONFIG_ID.test(sellerHostId) || !CONFIG_ID.test(sellerOrgId)) return null\n return { sellerHostId, sellerOrgId }\n}\n\nexport function isPosHardwareSkuId(value: unknown): value is PosHardwareSkuId {\n return typeof value === 'string' && posHardwareSku(value) !== null\n}\n\n/**\n * A reader's serial number as staff scanned or typed it, in the one form\n * every comparison uses: upper case, letters, digits and dashes only, at\n * most 64 characters. Empty when nothing usable was given.\n */\nexport function normalizePosHardwareSerial(value: unknown): string {\n return String(value ?? '')\n .toUpperCase()\n .replace(/[^A-Z0-9-]/g, '')\n .slice(0, 64)\n}\n\n/**\n * What the merchant pays for one item: Stripe's price plus the markup,\n * rounded half up to the cent, in integer math throughout.\n *\n * `cost + round(cost × bps / 10 000)`. The markup is basis points ON COST, so\n * a 50% markup is `5000` and a price at which half of it is profit (a 50%\n * margin) is `10000`. Whichever the business means, it is this one number.\n */\nexport function posHardwareUnitCents(costCents: number, markupBps: number): number {\n if (!Number.isSafeInteger(costCents) || costCents <= 0) return 0\n if (!Number.isSafeInteger(markupBps) || markupBps < 0) return costCents\n return costCents + Math.floor((costCents * markupBps + 5_000) / 10_000)\n}\n\n/** An item as the shop offers it: the merchant's price, never Stripe's. */\nexport interface PosHardwareOffer {\n sku: PosHardwareSkuId\n kind: PosHardwareSku['kind']\n name: string\n use: string\n unitCents: number\n}\n\n/** The items on offer, readers first, in catalog order. */\nexport function posHardwareOffers(config: PosHardwareConfig): PosHardwareOffer[] {\n return POS_HARDWARE_SKUS.flatMap((sku) => {\n const cost = config.costs[sku.id]\n if (cost === undefined) return []\n return [\n {\n sku: sku.id,\n kind: sku.kind,\n name: sku.displayName,\n use: sku.use,\n unitCents: posHardwareUnitCents(cost, config.markupBps),\n },\n ]\n })\n}\n\nexport interface PosHardwareLine {\n sku: PosHardwareSkuId\n name: string\n quantity: number\n unitCents: number\n lineCents: number\n}\n\nexport type PosHardwareCartResult =\n | { ok: true; lines: PosHardwareLine[]; subtotalCents: number }\n | { ok: false; error: string }\n\n/**\n * A merchant's basket priced on the SERVER: every item must be on offer, each\n * quantity a whole number from 1 to {@link POS_HARDWARE_MAX_PER_LINE}, and\n * the order at most {@link POS_HARDWARE_MAX_UNITS} units. A repeated item is\n * folded into one line. Prices come from the config, never the request.\n */\nexport function pricePosHardwareCart(\n config: PosHardwareConfig,\n requested: unknown,\n): PosHardwareCartResult {\n if (!Array.isArray(requested) || requested.length === 0) {\n return { ok: false, error: 'Choose at least one item.' }\n }\n const offers = new Map(posHardwareOffers(config).map((offer) => [offer.sku as string, offer]))\n const quantities = new Map<string, number>()\n for (const entry of requested) {\n const sku = String((entry as { sku?: unknown })?.sku ?? '')\n const quantity = Number((entry as { quantity?: unknown })?.quantity)\n if (!offers.has(sku)) return { ok: false, error: 'That item is not available.' }\n if (!Number.isInteger(quantity) || quantity < 1) {\n return { ok: false, error: 'Each quantity must be a whole number of at least 1.' }\n }\n quantities.set(sku, (quantities.get(sku) ?? 0) + quantity)\n }\n let units = 0\n const lines: PosHardwareLine[] = []\n for (const [sku, quantity] of quantities) {\n if (quantity > POS_HARDWARE_MAX_PER_LINE) {\n return { ok: false, error: `Order at most ${POS_HARDWARE_MAX_PER_LINE} of each item.` }\n }\n units += quantity\n const offer = offers.get(sku) as PosHardwareOffer\n lines.push({\n sku: offer.sku,\n name: offer.name,\n quantity,\n unitCents: offer.unitCents,\n lineCents: offer.unitCents * quantity,\n })\n }\n if (units > POS_HARDWARE_MAX_UNITS) {\n return { ok: false, error: `One order holds at most ${POS_HARDWARE_MAX_UNITS} items.` }\n }\n // Accessories alone are allowed: a merchant may want a second dock.\n return { ok: true, lines, subtotalCents: lines.reduce((sum, line) => sum + line.lineCents, 0) }\n}\n\n/*==========================================\n * THE CHECKOUT SESSION'S METADATA.\n *\n * The webhook builds the order from the session Stripe hands back, so the\n * lines ride in its metadata in a compact form (`sku*qty*unit;…`, inside\n * Stripe's 500-character value limit even with every item in the catalog in\n * one basket at the highest price the config accepts, which a spec holds),\n * and the webhook re-reads every one against the catalog rather than trusting\n * a name from the wire.\n *=========================================*/\n\nexport const POS_HARDWARE_METADATA_TYPE = 'pos-hardware-order'\n\nexport function encodePosHardwareLines(lines: readonly PosHardwareLine[]): string {\n return lines.map((line) => `${line.sku}*${line.quantity}*${line.unitCents}`).join(';')\n}\n\nexport function decodePosHardwareLines(encoded: unknown): PosHardwareLine[] | null {\n const text = String(encoded ?? '').trim()\n if (!text) return null\n const lines: PosHardwareLine[] = []\n for (const part of text.split(';')) {\n const [skuId, quantityText, unitText] = part.split('*')\n const sku = posHardwareSku(String(skuId ?? ''))\n const quantity = Number(quantityText)\n const unitCents = Number(unitText)\n if (!sku) return null\n if (!Number.isInteger(quantity) || quantity < 1 || quantity > POS_HARDWARE_MAX_PER_LINE) return null\n if (!Number.isSafeInteger(unitCents) || unitCents < 1) return null\n lines.push({ sku: sku.id, name: sku.displayName, quantity, unitCents, lineCents: quantity * unitCents })\n }\n return lines\n}\n\n/*==========================================\n * THE ORDER AND ITS STATUSES.\n *=========================================*/\n\nexport type PosHardwareOrderStatus =\n | 'paid'\n | 'ordered'\n | 'shipped'\n | 'delivered'\n | 'registered'\n | 'canceled'\n | 'refunded'\n\nexport const POS_HARDWARE_ORDER_STATUSES: readonly PosHardwareOrderStatus[] = [\n 'paid',\n 'ordered',\n 'shipped',\n 'delivered',\n 'registered',\n 'canceled',\n 'refunded',\n]\n\nexport function isPosHardwareOrderStatus(value: unknown): value is PosHardwareOrderStatus {\n return typeof value === 'string' && (POS_HARDWARE_ORDER_STATUSES as readonly string[]).includes(value)\n}\n\n/** What the merchant reads on the Hardware card. */\nexport const POS_HARDWARE_STATUS_LABELS: Record<PosHardwareOrderStatus, string> = {\n paid: 'Order received',\n ordered: 'Being prepared',\n shipped: 'Shipped',\n delivered: 'Delivered',\n registered: 'Set up',\n canceled: 'Canceled',\n refunded: 'Refunded',\n}\n\n/** What staff read on the orders page: where the order is in fulfillment. */\nexport const POS_HARDWARE_STAFF_STATUS_LABELS: Record<PosHardwareOrderStatus, string> = {\n paid: 'Paid',\n ordered: 'Ordered from Stripe',\n shipped: 'Shipped',\n delivered: 'Delivered',\n registered: 'Registered',\n canceled: 'Canceled',\n refunded: 'Refunded',\n}\n\n/**\n * The forward steps staff may move an order through. Canceling and refunding\n * are separate acts with their own rule ({@link posHardwareRefundable}),\n * because each returns money.\n */\nconst NEXT_STATUS: Partial<Record<PosHardwareOrderStatus, PosHardwareOrderStatus>> = {\n paid: 'ordered',\n ordered: 'shipped',\n shipped: 'delivered',\n delivered: 'registered',\n}\n\nexport function nextPosHardwareStatus(\n status: PosHardwareOrderStatus,\n): PosHardwareOrderStatus | null {\n return NEXT_STATUS[status] ?? null\n}\n\n/**\n * Whether the order may still be canceled or refunded in full: only before it\n * ships. Once a parcel is on its way the money comes back through a return of\n * the hardware, which is a separate process this phase does not automate.\n */\nexport function posHardwareRefundable(status: PosHardwareOrderStatus): boolean {\n return status === 'paid' || status === 'ordered'\n}\n\nexport interface PosHardwareAddress {\n line1: string\n line2?: string\n city: string\n state?: string\n postalCode: string\n country: string\n}\n\n/**\n * What sales tax is sourced by: where the order shipped, without the street\n * or the person. An erasure keeps this much of the address (see\n * {@link posHardwareBuyerErasedPatch}) and the retained sale record holds\n * nothing finer ({@link PosHardwareSaleRecord}).\n */\nexport interface PosHardwareTaxRegion {\n country: string\n state: string | null\n postalCode: string | null\n}\n\nexport interface PosHardwareTracking {\n carrier: string\n number: string\n url: string | null\n}\n\n/**\n * Why an order was canceled and refunded: by staff on the Hardware orders\n * page, or because the workspace that bought it was deleted or the account\n * that placed it was closed before it shipped (AGL-4038 closure).\n */\nexport type PosHardwareCancelReason = 'staff' | 'workspace-closed' | 'account-closed'\n\n/**\n * One entry of the tax Stripe computed on a hardware checkout, as the\n * storefront's tax rows store it (`StorefrontTaxLine`): the amount, the base\n * it was applied to (`taxable_amount`, null when Stripe did not state one)\n * and the rate, for the working papers of the platform's own return.\n */\nexport interface PosHardwareTaxLine {\n amountCents: number\n taxabilityReason: string | null\n taxRateId: string | null\n taxableAmountCents: number | null\n jurisdiction: string | null\n rateState: string | null\n percentage: number | null\n}\n\n/**\n * Where an order was sold (AGL-4038): the Hardware card's checkout, or the\n * platform's own register, in person. Absent on every order sold before the\n * in-person sale existed, which were all online; read it through\n * {@link posHardwareOrderSource}.\n */\nexport type PosHardwareOrderSource = 'online' | 'in_person'\n\nexport function posHardwareOrderSource(\n order: { source?: unknown } | null | undefined,\n): PosHardwareOrderSource {\n return order?.source === 'in_person' ? 'in_person' : 'online'\n}\n\n/**\n * The register sale an in-person order came from: the platform's own store,\n * the register order and its number, and how it was rung.\n */\nexport interface PosHardwareRegisterSale {\n hostId: string\n orgId: string\n orderId: string\n /** The register order's number, as its receipt shows it. */\n number: number | null\n registerId: string | null\n /** A customer built it at the self-service kiosk. */\n kiosk: boolean\n /** It was rung while the register was offline, and synced later. */\n offline: boolean\n /**\n * The Checkout Session of the register's payment link, when the sale was\n * paid by one: the storefront's tax record of the sale is filed under it.\n */\n paymentLinkSessionId: string | null\n}\n\n/**\n * Where an in-person sale's money went, for the books to reconcile it\n * against: the selling store's own takings — cash in its drawer, cards into\n * its connected Stripe account, exactly as every sale on its register. The\n * hardware order records it because the platform's online sales land in the\n * platform's own account instead, and the two must never be summed as one.\n */\nexport interface PosHardwarePaidTo {\n account: 'seller_store'\n hostId: string\n orgId: string\n /** The register sale's tenders, as its payments name them (`cash`, `card_present`, …). */\n tenders: string[]\n /** The store's connected Stripe account, when its owner has one. */\n stripeAccountId: string | null\n}\n\n/**\n * Who an in-person order belongs to (AGL-4038). `attached` once it is in the\n * buyer's workspace; `pending` while staff have still to say whose it is —\n * a kiosk or offline sale, a buyer with no account yet (`email`, invited to\n * sign up), or one with an account and no workspace yet (`uid`). A pending\n * order is kept in the selling workspace's own `hardwareOrders`, on its \"To\n * attach\" list, and moves to the buyer's workspace when it is attached.\n */\nexport interface PosHardwareAttachment {\n state: 'attached' | 'pending'\n /** The buyer's address: the account's, or the one typed for a buyer with no account. */\n email: string | null\n uid: string | null\n /** The buyer's workspace, once there is one. */\n orgId: string | null\n /** The store the buyer chose for the reader, when they chose one at the sale. */\n hostId: string | null\n /** The register user who attached it; null for the buyer's own sign-up. */\n attachedBy: string | null\n attachedAtMs: number | null\n /** When the sign-up invitation went to `email`, for a buyer with no account. */\n invitedAtMs?: number | null\n /**\n * The buyer's name, for the platform's reseller records (AGL-4038): the\n * account's name, or the one typed at the sale. An online order keeps it\n * in `shipping.name` instead. Taken off with the buyer's account.\n */\n name?: string | null\n /**\n * The buyer's business address, for the same records: typed at the sale,\n * or the address of the store the readers are set up on. Never the tax\n * region, which stays the selling store's. Taken off with the account.\n */\n address?: PosHardwareAddress | null\n}\n\n/**\n * One reader of an in-person order, by its serial number when staff scanned\n * it at the sale (AGL-4038). The order moves to Set up when every reader on\n * it is registered to the buyer's store.\n */\nexport interface PosHardwareUnit {\n sku: PosHardwareSkuId\n /** Normalized ({@link normalizePosHardwareSerial}); null when none was taken. */\n serialNumber: string | null\n /** The Stripe Terminal reader it was registered as, and on which store. */\n readerId: string | null\n hostId: string | null\n registeredAtMs: number | null\n}\n\n/**\n * One refund of an in-person order (AGL-4038), once by its key: a return at\n * the selling store's register (`register:{returnId}`), or a refund from the\n * staff Hardware orders page (`staff:{n}`), both paid back from the selling\n * store's own takings the way the sale was paid.\n */\nexport interface PosHardwareRefund {\n key: string\n via: 'register' | 'staff'\n cents: number\n /** Hardware items it took back; 0 when the refund named none. */\n quantity: number\n atMs: number\n stripeRefundId: string | null\n}\n\n/**\n * The buyer's request to return an in-person order, from their own\n * purchase history (AGL-4038). Staff refund it (`refunded`) or decline it.\n * Returns follow Stripe's hardware terms.\n */\nexport interface PosHardwareReturnRequest {\n state: 'requested' | 'declined' | 'refunded'\n /** The buyer's words; taken off with the buyer when their account is erased. */\n reason: string | null\n requestedBy: string | null\n requestedAtMs: number\n decidedBy: string | null\n decidedAtMs: number | null\n /** What staff told the buyer when declining. */\n note: string | null\n}\n\nexport const POS_HARDWARE_RETURN_REASON_MAX = 500\n\n/** `orgs/{orgId}/hardwareOrders/{orderId}`, written only by the server. */\nexport interface PosHardwareOrder {\n orgId: string\n /**\n * The site whose POS settings the order was placed from; for an in-person\n * order, the buyer's store once known, else the selling store.\n */\n hostId: string\n /** The member who started the checkout; `null` once their account is erased. */\n createdBy: string | null\n /** Where the order emails go: the address entered at checkout. */\n contactEmail: string | null\n lines: PosHardwareLine[]\n subtotalCents: number\n shipping: {\n method: PosHardwareOrderShippingMethod\n label: string\n cents: number\n name: string | null\n phone: string | null\n address: PosHardwareAddress | null\n /**\n * Set when the buyer's account is erased, in place of `address`: the part\n * of it sales tax is sourced by.\n */\n taxRegion?: PosHardwareTaxRegion | null\n }\n taxCents: number\n /**\n * The register sale's discounts that fell on this order's lines, for an\n * in-person order (AGL-4038): `totalCents` is the lines less this, plus\n * the tax. Absent, and so nothing, on every online order.\n */\n discountCents?: number\n totalCents: number\n currency: string\n /** Whether Stripe Tax computed the tax on the checkout. Absent on orders before it was recorded. */\n automaticTax?: boolean\n /** Stripe's tax breakdown for the checkout, when Stripe Tax computed it. */\n taxLines?: PosHardwareTaxLine[]\n /** The Hardware card's checkout; null for an in-person sale, which had none. */\n stripeCheckoutSessionId: string | null\n stripePaymentIntentId: string | null\n /** Stripe's hardware order id, recorded by staff (phase 2 sets it itself). */\n stripeHardwareOrderId: string | null\n tracking: PosHardwareTracking | null\n status: PosHardwareOrderStatus\n livemode: boolean\n createdAtMs: number\n updatedAtMs: number\n paidAtMs: number\n orderedAtMs?: number\n shippedAtMs?: number\n deliveredAtMs?: number\n registeredAtMs?: number\n canceledAtMs?: number\n refundedAtMs?: number\n refundId?: string | null\n /** The cents Stripe reported refunding. */\n refundedCents?: number\n /** Why it was canceled, when it was. */\n cancelReason?: PosHardwareCancelReason\n /** When the buyer's account was erased and their details taken off the order. */\n buyerErasedAtMs?: number\n /** Where it was sold; absent reads as online ({@link posHardwareOrderSource}). */\n source?: PosHardwareOrderSource\n /** The register sale an in-person order came from. */\n posSale?: PosHardwareRegisterSale\n /** Where an in-person sale's money went. */\n paidTo?: PosHardwarePaidTo\n /** Whose an in-person order is, or that staff have still to say. */\n attachment?: PosHardwareAttachment\n /**\n * How the buyer of an in-person order agreed to the terms of sale\n * (AGL-4038): copied from the register sale, or the receipt-and-sign\n * notice when they did not accept electronically.\n */\n termsAcceptance?: PosHardwareTermsAcceptance\n /** Each reader of an in-person order, with its serial when one was taken. */\n units?: PosHardwareUnit[]\n /** The units' serial numbers, for finding the order when a reader is registered. */\n serialNumbers?: string[]\n /** An in-person order's refunds, each once by its key. */\n refunds?: PosHardwareRefund[]\n /** The buyer's request to return it, and what staff decided. */\n returnRequest?: PosHardwareReturnRequest\n /**\n * The emails that are sent once (AGL-4038), by key: `canceled`, or\n * `refunded:{refund key}`. Claimed before the send, so the staff page and a\n * closure never both send one.\n */\n buyerEmails?: Record<string, { state: 'sending' | 'sent' | 'skipped' | 'failed'; atMs: number }>\n}\n\n/** The timestamp field each status stamps when it is reached. */\nexport const POS_HARDWARE_STATUS_STAMP: Record<PosHardwareOrderStatus, keyof PosHardwareOrder> = {\n paid: 'paidAtMs',\n ordered: 'orderedAtMs',\n shipped: 'shippedAtMs',\n delivered: 'deliveredAtMs',\n registered: 'registeredAtMs',\n canceled: 'canceledAtMs',\n refunded: 'refundedAtMs',\n}\n\n/** The Hardware card's anchor: Checkout returns to it and the emails link at it. */\nexport const POS_HARDWARE_ANCHOR = 'pos-hardware'\n\n/** The collection under `orgs/{orgId}`, named once. */\nexport const POS_HARDWARE_ORDERS_COLLECTION = 'hardwareOrders'\n\n/*==========================================\n * THE SALE RECORD THE PLATFORM KEEPS (AGL-4038).\n *\n * A hardware order is the PLATFORM's own sale: the platform is the seller of\n * record and owes the sales tax on it. So beside every order the server keeps\n * `posHardwareSales/{orderId}`, a TOP-LEVEL record of what tax and accounting\n * need — the lines, the amounts, the tax, Stripe's ids, the status dates and\n * the region it shipped to — and, of the person who bought it, only their\n * NAME and ADDRESS ({@link PosHardwareSaleBuyer}).\n *\n * The name and address are the reseller's records Stripe's Terminal Reseller\n * Terms §10 ask for: the identity and address of each buyer, open to Stripe's\n * inspection. §10 states no period, so they are kept for the record's own\n * seven years. They SURVIVE the buyer's account erasure, which still takes\n * the email, phone, tracking and the name and street off the ORDER: every\n * write sets the record through {@link posHardwareSaleSetOptions}, which\n * leaves the stored name and address alone when the order no longer has\n * them because its buyer was erased.\n *\n * It is the `platformRevenue` / `storefrontTaxCollected` shape on purpose\n * (AGL-1811, AGL-1904): keyed to the workspace by FIELD, outside\n * `orgs/{orgId}`, so the workspace erasure's `recursiveDelete(orgs/{orgId})`\n * cannot see it, and never in any org-keyed sweep. GDPR Art. 17(3)(b) exempts\n * records kept to comply with a legal obligation. The order under the\n * workspace, with the buyer's name, street, phone, email and tracking, is\n * erased with the workspace as before.\n *\n * Written in the same transaction as every write to the order, from the\n * order as it stands after that write, so the two never disagree and a\n * missing record heals on the next write.\n *\n * KEPT SEVEN YEARS FROM THE SALE, THEN DELETED. Every write stamps\n * `expiresAt` ({@link posHardwareSaleExpiry}) and the Firestore TTL policy on\n * `posHardwareSales.expiresAt` deletes the record once it passes.\n *=========================================*/\n\n/** The top-level collection of retained hardware sale records. */\nexport const POS_HARDWARE_SALES_COLLECTION = 'posHardwareSales'\n\n/**\n * A street address as the sale record keeps it: every key present, so a\n * later write of a different address replaces each one.\n */\nexport interface PosHardwareSaleAddress {\n line1: string\n line2: string | null\n city: string\n state: string | null\n postalCode: string | null\n country: string\n}\n\n/**\n * Who bought it, for the reseller's records (AGL-4038): the buyer's name and\n * address and nothing else of theirs. An online order's are the shipping\n * name and address; an in-person order's are on its attachment.\n */\nexport interface PosHardwareSaleBuyer {\n name: string | null\n address: PosHardwareSaleAddress | null\n}\n\n/**\n * `posHardwareSales/{orderId}`: an order with every personal detail left out\n * but the buyer's name and address.\n */\nexport interface PosHardwareSaleRecord {\n orderId: string\n /** The workspace that bought it, by id; the record outlives the workspace. */\n orgId: string\n lines: Array<Pick<PosHardwareLine, 'sku' | 'name' | 'quantity' | 'unitCents' | 'lineCents'>>\n subtotalCents: number\n shippingMethod: PosHardwareOrderShippingMethod\n shippingCents: number\n taxCents: number\n /** The register discount on an in-person order's lines; 0 for every online one. */\n discountCents: number\n totalCents: number\n currency: string\n /** The sales tax sourcing input: country, state and postal code, never the street. */\n shipTo: PosHardwareTaxRegion | null\n /** Whether Stripe Tax computed the tax; null on orders before it was recorded. */\n automaticTax: boolean | null\n /** Stripe's tax breakdown: the base each rate was applied to, for the return. */\n taxLines: PosHardwareTaxLine[]\n stripeCheckoutSessionId: string | null\n stripePaymentIntentId: string | null\n stripeHardwareOrderId: string | null\n refundId: string | null\n refundedCents: number | null\n status: PosHardwareOrderStatus\n livemode: boolean\n createdAtMs: number\n updatedAtMs: number\n paidAtMs: number | null\n orderedAtMs: number | null\n shippedAtMs: number | null\n deliveredAtMs: number | null\n registeredAtMs: number | null\n canceledAtMs: number | null\n refundedAtMs: number | null\n /** Where it was sold; `online` for every order before the in-person sale. */\n source: PosHardwareOrderSource\n /** The register sale an in-person order came from: the selling store and its order. */\n posHostId: string | null\n posOrderId: string | null\n /**\n * Where the money went: `platform` for the Hardware card's checkout, on\n * the platform's own Stripe account; `seller_store` for an in-person sale,\n * the selling store's own takings ({@link PosHardwarePaidTo}).\n */\n paidTo: { account: 'platform' } | PosHardwarePaidTo\n /**\n * The `storefrontTaxCollected` row the register sale ALSO filed, when it\n * was paid by a payment link. That row is the whole register sale; the\n * storefront section leaves this order's part of it out, so the sale is on\n * the return once, in the hardware section.\n */\n storefrontTaxRowId: string | null\n /**\n * The buyer's name and address, for the reseller's records; null when the\n * order names none (a sale not attached to anyone yet). Absent from a write\n * whose order no longer has them because the buyer's account was erased,\n * so the stored ones stay ({@link posHardwareSaleSetOptions}).\n */\n buyer?: PosHardwareSaleBuyer | null\n /**\n * When the record is deleted: {@link POS_HARDWARE_SALE_RETENTION_YEARS}\n * after the sale, by the Firestore TTL policy on `posHardwareSales.expiresAt`.\n * Null when the record states no sale date, which keeps it rather than\n * guessing a date to delete it by.\n */\n expiresAt: Date | null\n}\n\n/**\n * How long the platform keeps a hardware sale record (AGL-4038): seven years\n * from the sale, then the record is deleted. Longer than the four years Texas\n * requires for sales tax records, a margin for an audit of an older period.\n */\nexport const POS_HARDWARE_SALE_RETENTION_YEARS = 7\n\n/**\n * When a sale record expires: the same calendar instant, seven years after\n * the sale was paid (the order's creation where no payment time is stated).\n * A sale on 29 February expires on 1 March, a day late rather than early.\n * Null when there is no sale time, so the record is kept, never reaped at\n * the epoch.\n */\nexport function posHardwareSaleExpiry(\n order: Pick<PosHardwareOrder, 'paidAtMs' | 'createdAtMs'>,\n): Date | null {\n const saleAtMs = [order.paidAtMs, order.createdAtMs].find(\n (value) => typeof value === 'number' && Number.isFinite(value) && value > 0,\n )\n if (saleAtMs === undefined) return null\n const expires = new Date(saleAtMs)\n expires.setUTCFullYear(expires.getUTCFullYear() + POS_HARDWARE_SALE_RETENTION_YEARS)\n return expires\n}\n\n/** A tax line as the record keeps it: every field picked, every figure an integer. */\nfunction saleTaxLine(line: Partial<PosHardwareTaxLine> | null | undefined): PosHardwareTaxLine {\n const integerOrNull = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? Math.round(value) : null\n const numberOrNull = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n const textOf = (value: unknown): string | null =>\n typeof value === 'string' && value ? value : null\n return {\n amountCents: integerOrNull(line?.amountCents) ?? 0,\n taxabilityReason: textOf(line?.taxabilityReason),\n taxRateId: textOf(line?.taxRateId),\n taxableAmountCents: integerOrNull(line?.taxableAmountCents),\n jurisdiction: textOf(line?.jurisdiction),\n rateState: textOf(line?.rateState),\n percentage: numberOrNull(line?.percentage),\n }\n}\n\nconst stampOrNull = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\nconst textOrNull = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\n\n/** The tax region of an order: its address cut down, or what an erasure left. */\nexport function posHardwareTaxRegion(order: Pick<PosHardwareOrder, 'shipping'>): PosHardwareTaxRegion | null {\n const address = order.shipping?.address\n if (address?.country) {\n return {\n country: address.country,\n state: textOrNull(address.state),\n postalCode: textOrNull(address.postalCode),\n }\n }\n const kept = order.shipping?.taxRegion\n return kept?.country\n ? { country: kept.country, state: textOrNull(kept.state), postalCode: textOrNull(kept.postalCode) }\n : null\n}\n\n/** An address with every key present, or null when it names no place. */\nfunction saleAddress(address: Partial<PosHardwareAddress> | null | undefined): PosHardwareSaleAddress | null {\n const line1 = textOrNull(address?.line1)\n const city = textOrNull(address?.city)\n const country = textOrNull(address?.country)\n if (!line1 || !city || !country) return null\n return {\n line1: line1.slice(0, 200),\n line2: textOrNull(address?.line2)?.slice(0, 200) ?? null,\n city: city.slice(0, 100),\n state: textOrNull(address?.state)?.slice(0, 100) ?? null,\n postalCode: textOrNull(address?.postalCode)?.slice(0, 20) ?? null,\n country: country.toUpperCase().slice(0, 2),\n }\n}\n\n/**\n * A buyer's business address as an in-person order keeps it (AGL-4038):\n * trimmed and cut to length, or null when it lacks a street, city, postal\n * code or two-letter country.\n */\nexport function posHardwareBuyerAddress(value: unknown): PosHardwareAddress | null {\n const address = (value && typeof value === 'object' ? value : {}) as Partial<Record<keyof PosHardwareAddress, unknown>>\n const line1 = textOrNull(address.line1)?.slice(0, 200)\n const city = textOrNull(address.city)?.slice(0, 100)\n const postalCode = textOrNull(address.postalCode)?.slice(0, 20)\n const country = textOrNull(address.country)?.toUpperCase()\n if (!line1 || !city || !postalCode || !country || !/^[A-Z]{2}$/.test(country)) return null\n const line2 = textOrNull(address.line2)?.slice(0, 200)\n const state = textOrNull(address.state)?.slice(0, 100)\n return { line1, ...(line2 ? { line2 } : {}), city, ...(state ? { state } : {}), postalCode, country }\n}\n\n/**\n * The buyer's name and address as the order holds them now (AGL-4038): the\n * shipping name and address of an online order, the attachment's of an\n * in-person one. `undefined` when the order has none BECAUSE its buyer's\n * account was erased, which tells the write to keep what the record has;\n * null when it simply names no one yet.\n */\nexport function posHardwareSaleBuyer(\n order: Pick<PosHardwareOrder, 'shipping' | 'attachment' | 'source' | 'buyerErasedAtMs'>,\n): PosHardwareSaleBuyer | null | undefined {\n const inPerson = posHardwareOrderSource(order) === 'in_person'\n const name = textOrNull(inPerson ? order.attachment?.name : order.shipping?.name)?.slice(0, 200) ?? null\n const address = saleAddress(inPerson ? order.attachment?.address : order.shipping?.address)\n if (name || address) return { name, address }\n return typeof order.buyerErasedAtMs === 'number' ? undefined : null\n}\n\n/**\n * The retained sale record of an order, as it stands. Every field is picked\n * by name, so a field added to the order later reaches the record only when\n * someone decides it should.\n */\nexport function posHardwareSaleRecord(orderId: string, order: PosHardwareOrder): PosHardwareSaleRecord {\n const buyer = posHardwareSaleBuyer(order)\n return {\n orderId,\n orgId: order.orgId,\n lines: (order.lines ?? []).map((line) => ({\n sku: line.sku,\n name: line.name,\n quantity: line.quantity,\n unitCents: line.unitCents,\n lineCents: line.lineCents,\n })),\n subtotalCents: order.subtotalCents ?? 0,\n shippingMethod: order.shipping?.method ?? 'standard',\n shippingCents: order.shipping?.cents ?? 0,\n taxCents: order.taxCents ?? 0,\n discountCents: stampOrNull(order.discountCents) ?? 0,\n totalCents: order.totalCents ?? 0,\n currency: order.currency ?? POS_HARDWARE_CURRENCY,\n shipTo: posHardwareTaxRegion(order),\n automaticTax: typeof order.automaticTax === 'boolean' ? order.automaticTax : null,\n taxLines: (Array.isArray(order.taxLines) ? order.taxLines : []).map(saleTaxLine),\n stripeCheckoutSessionId: textOrNull(order.stripeCheckoutSessionId),\n stripePaymentIntentId: textOrNull(order.stripePaymentIntentId),\n stripeHardwareOrderId: textOrNull(order.stripeHardwareOrderId),\n refundId: textOrNull(order.refundId),\n refundedCents: stampOrNull(order.refundedCents),\n status: order.status,\n livemode: order.livemode === true,\n createdAtMs: order.createdAtMs ?? 0,\n updatedAtMs: order.updatedAtMs ?? order.createdAtMs ?? 0,\n paidAtMs: stampOrNull(order.paidAtMs),\n orderedAtMs: stampOrNull(order.orderedAtMs),\n shippedAtMs: stampOrNull(order.shippedAtMs),\n deliveredAtMs: stampOrNull(order.deliveredAtMs),\n registeredAtMs: stampOrNull(order.registeredAtMs),\n canceledAtMs: stampOrNull(order.canceledAtMs),\n refundedAtMs: stampOrNull(order.refundedAtMs),\n source: posHardwareOrderSource(order),\n posHostId: textOrNull(order.posSale?.hostId),\n posOrderId: textOrNull(order.posSale?.orderId),\n paidTo: salePaidTo(order),\n storefrontTaxRowId: textOrNull(order.posSale?.paymentLinkSessionId),\n // Left out, never written as null, once the buyer was erased: the\n // stored name and address are the reseller's records and stay.\n ...(buyer === undefined ? {} : { buyer }),\n // The TTL policy's field: the record outlives the workspace, not the law.\n expiresAt: posHardwareSaleExpiry(order),\n }\n}\n\n/**\n * How every write of a sale record is made (AGL-4038): `set` with exactly\n * the record's own fields as `mergeFields`, so each one is replaced whole —\n * a map included, never merged key by key — and a field the record leaves\n * out is left as stored. The record leaves out only `buyer`, and only once\n * the buyer's account was erased ({@link posHardwareSaleBuyer}), so the\n * buyer's name and address outlive the erasure and every write after it.\n */\nexport function posHardwareSaleSetOptions(order: PosHardwareOrder): { mergeFields: string[] } {\n return { mergeFields: Object.keys(posHardwareSaleRecord('', order)) }\n}\n\n/** Where a sale's money went, picked by name: never a buyer detail. */\nfunction salePaidTo(order: Pick<PosHardwareOrder, 'source' | 'paidTo'>): PosHardwareSaleRecord['paidTo'] {\n const paidTo = order.paidTo\n if (posHardwareOrderSource(order) !== 'in_person' || paidTo?.account !== 'seller_store') {\n return { account: 'platform' }\n }\n return {\n account: 'seller_store',\n hostId: String(paidTo.hostId ?? ''),\n orgId: String(paidTo.orgId ?? ''),\n tenders: (Array.isArray(paidTo.tenders) ? paidTo.tenders : []).map(String).slice(0, 10),\n stripeAccountId: textOrNull(paidTo.stripeAccountId),\n }\n}\n\n/**\n * What an account erasure does to an order the person placed (AGL-4038): the\n * workspace keeps the order and every amount on it; the buyer comes off it —\n * who started it, the email, the name, the phone, the street and the\n * tracking, which names the parcel to a carrier who would say where it went.\n * The country, state and postal code stay, because sales tax is sourced by\n * them. Deterministic but for the stamp, and an order already erased no\n * longer answers to the person's uid, so a second run finds nothing.\n *\n * The buyer's NAME and ADDRESS stay on the platform's sale record, not on\n * the order: `buyerErasedAtMs` is what tells the record's next write to keep\n * them ({@link posHardwareSaleBuyer}, {@link posHardwareSaleSetOptions}).\n */\nexport function posHardwareBuyerErasedPatch(\n order: Pick<PosHardwareOrder, 'shipping' | 'attachment' | 'returnRequest'>,\n now: number,\n): Record<string, unknown> {\n // The whole map, read in the same transaction: a dotted-path update would\n // leave a field added to `shipping` later standing with the person in it.\n const shipping = order.shipping\n const attachment = order.attachment\n return {\n createdBy: null,\n contactEmail: null,\n shipping: {\n method: shipping?.method ?? 'standard',\n label: shipping?.label ?? posHardwareShippingLabel(shipping?.method),\n cents: shipping?.cents ?? 0,\n name: null,\n phone: null,\n address: null,\n taxRegion: posHardwareTaxRegion(order),\n },\n tracking: null,\n // An in-person order's attachment names the buyer too (AGL-4038): their\n // email, account, name and address come off it (the map is rebuilt with\n // none of them); the workspace and store it is in stay.\n ...(attachment\n ? {\n attachment: {\n state: attachment.state === 'attached' ? 'attached' : 'pending',\n email: null,\n uid: null,\n orgId: attachment.orgId ?? null,\n hostId: attachment.hostId ?? null,\n attachedBy: attachment.attachedBy ?? null,\n attachedAtMs: attachment.attachedAtMs ?? null,\n },\n }\n : {}),\n // Their return request keeps its outcome; their words and account go.\n ...(order.returnRequest\n ? { returnRequest: { ...order.returnRequest, reason: null, requestedBy: null } }\n : {}),\n buyerErasedAtMs: now,\n updatedAtMs: now,\n }\n}\n\n/** An order id: `hw_` and twenty hex characters. */\nexport const POS_HARDWARE_ORDER_ID = /^hw_[a-f0-9]{20}$/\n\n/** Dollars and cents for the card and the emails. */\nexport function formatPosHardwareCents(cents: number): string {\n const value = Number.isFinite(cents) ? cents : 0\n return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value / 100)\n}\n"],"names":["PLATFORM_BRAND_NAME","POS_HARDWARE_CURRENCY","POS_HARDWARE_SHIP_COUNTRIES","POS_HARDWARE_MAX_PER_LINE","POS_HARDWARE_MAX_UNITS","POS_HARDWARE_ENV","enabled","prices","markupBps","shipping","automaticTax","inPersonEnabled","sellerHostId","sellerOrgId","POS_HARDWARE_DEFAULT_MARKUP_BPS","POS_HARDWARE_SKUS","id","kind","stripeProductName","displayName","use","posHardwareSku","find","sku","POS_HARDWARE_SHIPPING_LABELS","standard","express","priority","SHIPPING_METHODS","Object","keys","isPosHardwareShippingMethod","value","includes","POS_HARDWARE_IN_PERSON_SHIPPING_LABEL","posHardwareShippingLabel","method","MAX_FIGURE_CENTS","MAX_MARKUP_BPS","isCents","min","Number","isSafeInteger","parseJsonObject","raw","text","String","trim","parsed","JSON","parse","Array","isArray","parsePosHardwareConfig","env","toLowerCase","markupText","test","costs","cost","anyReader","some","undefined","shippingRaw","flatMap","cents","length","CONFIG_ID","parsePosHardwareInPersonConfig","isPosHardwareSkuId","normalizePosHardwareSerial","toUpperCase","replace","slice","posHardwareUnitCents","costCents","Math","floor","posHardwareOffers","config","name","unitCents","pricePosHardwareCart","requested","ok","error","offers","Map","map","offer","quantities","entry","quantity","has","isInteger","set","get","units","lines","push","lineCents","subtotalCents","reduce","sum","line","POS_HARDWARE_METADATA_TYPE","encodePosHardwareLines","join","decodePosHardwareLines","encoded","part","split","skuId","quantityText","unitText","POS_HARDWARE_ORDER_STATUSES","isPosHardwareOrderStatus","POS_HARDWARE_STATUS_LABELS","paid","ordered","shipped","delivered","registered","canceled","refunded","POS_HARDWARE_STAFF_STATUS_LABELS","NEXT_STATUS","nextPosHardwareStatus","status","posHardwareRefundable","posHardwareOrderSource","order","source","POS_HARDWARE_RETURN_REASON_MAX","POS_HARDWARE_STATUS_STAMP","POS_HARDWARE_ANCHOR","POS_HARDWARE_ORDERS_COLLECTION","POS_HARDWARE_SALES_COLLECTION","POS_HARDWARE_SALE_RETENTION_YEARS","posHardwareSaleExpiry","saleAtMs","paidAtMs","createdAtMs","isFinite","expires","Date","setUTCFullYear","getUTCFullYear","saleTaxLine","integerOrNull","round","numberOrNull","textOf","amountCents","taxabilityReason","taxRateId","taxableAmountCents","jurisdiction","rateState","percentage","stampOrNull","textOrNull","posHardwareTaxRegion","address","country","state","postalCode","kept","taxRegion","saleAddress","line1","city","line2","posHardwareBuyerAddress","posHardwareSaleBuyer","inPerson","attachment","buyerErasedAtMs","posHardwareSaleRecord","orderId","buyer","orgId","shippingMethod","shippingCents","taxCents","discountCents","totalCents","currency","shipTo","taxLines","stripeCheckoutSessionId","stripePaymentIntentId","stripeHardwareOrderId","refundId","refundedCents","livemode","updatedAtMs","orderedAtMs","shippedAtMs","deliveredAtMs","registeredAtMs","canceledAtMs","refundedAtMs","posHostId","posSale","hostId","posOrderId","paidTo","salePaidTo","storefrontTaxRowId","paymentLinkSessionId","expiresAt","posHardwareSaleSetOptions","mergeFields","account","tenders","stripeAccountId","posHardwareBuyerErasedPatch","now","createdBy","contactEmail","label","phone","tracking","email","uid","attachedBy","attachedAtMs","returnRequest","reason","requestedBy","POS_HARDWARE_ORDER_ID","formatPosHardwareCents","Intl","NumberFormat","style","format"],"mappings":"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"}
@@ -89,6 +89,9 @@ export declare function productSaveFields(current: EditedProduct, nowMs: number)
89
89
  modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
90
90
  posQuickKey?: boolean;
91
91
  sellOnline?: boolean;
92
+ inStoreReviews?: "show" | "hide";
93
+ offerOnKiosk?: boolean;
94
+ hardwareSku?: string;
92
95
  checkIn?: import("./commerce-check-in-product").ProductCheckIn;
93
96
  createdAtMs?: number;
94
97
  deletedAt?: number | null;
@@ -166,6 +169,9 @@ export declare function productEditWrite(fields: ReturnType<typeof productSaveFi
166
169
  modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
167
170
  posQuickKey?: boolean;
168
171
  sellOnline?: boolean;
172
+ inStoreReviews?: "show" | "hide";
173
+ offerOnKiosk?: boolean;
174
+ hardwareSku?: string;
169
175
  checkIn?: import("./commerce-check-in-product").ProductCheckIn;
170
176
  createdAtMs?: number;
171
177
  };
@@ -198,6 +204,7 @@ export declare function productDuplicateFields(product: HostProduct & {
198
204
  skus?: string[];
199
205
  barcodes?: string[];
200
206
  priceFromCents?: number;
207
+ sample?: boolean;
201
208
  description?: string;
202
209
  type: import("./commerce").ProductType;
203
210
  mediaUrls?: string[];
@@ -237,6 +244,9 @@ export declare function productDuplicateFields(product: HostProduct & {
237
244
  modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
238
245
  posQuickKey?: boolean;
239
246
  sellOnline?: boolean;
247
+ inStoreReviews?: "show" | "hide";
248
+ offerOnKiosk?: boolean;
249
+ hardwareSku?: string;
240
250
  checkIn?: import("./commerce-check-in-product").ProductCheckIn;
241
251
  deletedAt?: number | null;
242
252
  priceUsd?: number;
@@ -31,10 +31,12 @@ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_wi
31
31
  */ export function productSaveFields(current, nowMs) {
32
32
  var _ref, _ref1;
33
33
  var _current_mediaUrls;
34
- const { $id: _syntheticId, skus: _seededSkus, barcodes: _seededBarcodes } = current, currentFields = _object_without_properties_loose(current, [
34
+ const { $id: _syntheticId, skus: _seededSkus, barcodes: _seededBarcodes, // A sample the merchant saves is theirs now (AGL-4065).
35
+ sample: _sample } = current, currentFields = _object_without_properties_loose(current, [
35
36
  "$id",
36
37
  "skus",
37
- "barcodes"
38
+ "barcodes",
39
+ "sample"
38
40
  ]);
39
41
  const primaryVariant = current.variants[0];
40
42
  return _extends({}, currentFields, productSearchFields({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/product-write.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n adjustVariantInventory,\n commerceSlug,\n type HostProduct,\n type InventoryAdjustment,\n type InventoryAdjustmentReason,\n productSearchFields,\n productStockFields,\n type SmartCollectionRules,\n variantHasPrice,\n} from './commerce'\n\n/*\n * WHAT A PRODUCT SAVE AND A STOCK ADJUSTMENT WRITE, worked out once.\n *\n * The console's product editor and its Adjust stock dialog compute these\n * fields in the browser before their Firestore writes; the native apps save\n * through `commerce/products/save` and `commerce/products/stock`, which run\n * this same code on the server. One computation, so a product saved from a\n * phone carries exactly the search keys, stock verdict, price, image and\n * smart-collection membership the console would have written.\n */\n\n/** A product as the editor holds it: the stored fields plus keys the listener or a seed may carry. */\nexport type EditedProduct = HostProduct & {\n $id?: string\n skus?: string[]\n barcodes?: string[]\n stockHolds?: Record<string, unknown>\n}\n\n/**\n * The fields a product save writes, before its collection membership and its\n * timestamps: the product as edited, with\n *\n * - `$id` dropped (AGL-1374): the listener's synthetic key, never a field;\n * - the seed's `skus` / `barcodes` dropped (AGL-3321): `productSearchFields`\n * omits each when no variant has one, so a seeded copy would outlive the\n * last SKU;\n * - the trimmed name with the search keys derived from it, together, so the\n * two cannot drift (AGL-2501);\n * - a slug, the primary variant's price, the stock fields, the first image\n * and `updatedAtMs`.\n */\nexport function productSaveFields(current: EditedProduct, nowMs: number) {\n const {\n $id: _syntheticId,\n skus: _seededSkus,\n barcodes: _seededBarcodes,\n ...currentFields\n } = current\n const primaryVariant = current.variants[0]\n return {\n ...currentFields,\n ...productSearchFields({\n name: current.name.trim().slice(0, 120),\n variants: current.variants,\n }),\n slug: current.slug || commerceSlug(current.name),\n // The legacy flat price, only where there is one (AGL-3676): an owner\n // may save a product before pricing it, and an unpriced product is not\n // a free one. `null` rather than absent, so an edit's full replace and\n // the native save write the same field.\n priceUsd: variantHasPrice(primaryVariant) ? primaryVariant.priceUsd : null,\n ...productStockFields({\n variants: current.variants,\n oversellPolicy: current.oversellPolicy,\n }),\n imageUrl: current.mediaUrls?.[0] ?? current.imageUrl ?? null,\n updatedAtMs: nowMs,\n }\n}\n\n/** What the smart-collection rules are matched against: the saved name with the product's own facts. */\nexport function productMembershipInput(\n fields: { name: string },\n current: Pick<HostProduct, 'type' | 'tags' | 'categoryIds' | 'variants'>,\n) {\n return {\n name: fields.name,\n type: current.type,\n tags: current.tags,\n categoryIds: current.categoryIds,\n variants: current.variants,\n }\n}\n\n/**\n * An EDIT's replacing write, without its membership and server timestamp:\n * live stock holds are not the editor's to write (AGL-2356), so the seed's\n * are dropped and the stored ones carried; the product stays live\n * (`deletedAt: null`, AGL-3321).\n */\nexport function productEditWrite(\n fields: ReturnType<typeof productSaveFields>,\n liveHolds: Record<string, unknown> | null | undefined,\n) {\n const { stockHolds: _seededHolds, ...withoutHolds } = fields as typeof fields & {\n stockHolds?: Record<string, unknown>\n }\n return {\n ...withoutHolds,\n ...(liveHolds ? { stockHolds: liveHolds } : {}),\n deletedAt: null,\n }\n}\n\n/** A duplicate's name: the source's, marked as a copy. */\nexport function productDuplicateName(name: string): string {\n return `${name} (copy)`\n}\n\n/** The slug a duplicate starts from, before the store is asked which are taken. */\nexport function productDuplicateSlugBase(slug: string | undefined, name: string): string {\n return commerceSlug(`${slug || commerceSlug(name)}-copy`)\n}\n\n/**\n * What Duplicate writes (the products list's Duplicate, AGL-3321): the source\n * as a draft under the copy's name, with the COPY's search keys (spreading the\n * source's would sort and find it by the source's name) and stock fields, and\n * not the source's SKU and barcode keys, which are derived again from its\n * variants. The caller adds the claimed `slug`'s smart-collection membership.\n */\nexport function productDuplicateFields(\n product: HostProduct & { $id?: string; skus?: string[]; barcodes?: string[] },\n slug: string,\n nowMs: number,\n) {\n const { $id: _sourceId, skus: _skus, barcodes: _barcodes, ...copy } = product\n return {\n ...copy,\n ...productSearchFields({ name: productDuplicateName(product.name), variants: product.variants }),\n ...productStockFields(product),\n slug,\n status: 'draft' as const,\n createdAtMs: nowMs,\n updatedAtMs: nowMs,\n }\n}\n\n/** A smart collection as `hosts/{hostId}/collections` stores it, as the membership rules read it. */\nexport function smartCollectionRulesOf(id: string, data: Record<string, unknown>): SmartCollectionRules {\n return {\n id,\n rules: (data['rules'] as SmartCollectionRules['rules']) ?? [],\n matchAll: data['matchAll'] as boolean | undefined,\n }\n}\n\n/** One stock adjustment, as the Adjust stock dialog asks for it. */\nexport interface StockAdjustment {\n variantId: string\n delta: number\n reason: InventoryAdjustmentReason\n locationId?: string\n}\n\n/**\n * The product update and the ledger row one stock adjustment writes: the\n * variants with the count moved, the stock fields that move with it\n * (AGL-3321), and the same `inventoryAdjustments` row the sale webhook writes\n * (AGL-281). A zero or non-numeric delta writes nothing.\n */\nexport function stockAdjustmentWrite(\n product: HostProduct,\n productId: string,\n adjustment: StockAdjustment,\n nowMs: number,\n): { update: Record<string, unknown>; ledger: InventoryAdjustment } | null {\n const delta = Math.round(Number(adjustment.delta))\n if (!delta) return null\n const variants = adjustVariantInventory(product, adjustment.variantId, delta, adjustment.locationId || undefined)\n return {\n update: {\n variants,\n ...productStockFields({ ...product, variants }),\n updatedAtMs: nowMs,\n },\n ledger: {\n productId,\n variantId: adjustment.variantId,\n delta,\n reason: adjustment.reason,\n ...(adjustment.locationId ? { locationId: adjustment.locationId } : {}),\n atMs: nowMs,\n },\n }\n}\n"],"names":["adjustVariantInventory","commerceSlug","productSearchFields","productStockFields","variantHasPrice","productSaveFields","current","nowMs","$id","_syntheticId","skus","_seededSkus","barcodes","_seededBarcodes","currentFields","primaryVariant","variants","name","trim","slice","slug","priceUsd","oversellPolicy","imageUrl","mediaUrls","updatedAtMs","productMembershipInput","fields","type","tags","categoryIds","productEditWrite","liveHolds","stockHolds","_seededHolds","withoutHolds","deletedAt","productDuplicateName","productDuplicateSlugBase","productDuplicateFields","product","_sourceId","_skus","_barcodes","copy","status","createdAtMs","smartCollectionRulesOf","id","data","rules","matchAll","stockAdjustmentWrite","productId","adjustment","delta","Math","round","Number","variantId","locationId","undefined","update","ledger","reason","atMs"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/product-write.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n adjustVariantInventory,\n commerceSlug,\n type HostProduct,\n type InventoryAdjustment,\n type InventoryAdjustmentReason,\n productSearchFields,\n productStockFields,\n type SmartCollectionRules,\n variantHasPrice,\n} from './commerce'\n\n/*\n * WHAT A PRODUCT SAVE AND A STOCK ADJUSTMENT WRITE, worked out once.\n *\n * The console's product editor and its Adjust stock dialog compute these\n * fields in the browser before their Firestore writes; the native apps save\n * through `commerce/products/save` and `commerce/products/stock`, which run\n * this same code on the server. One computation, so a product saved from a\n * phone carries exactly the search keys, stock verdict, price, image and\n * smart-collection membership the console would have written.\n */\n\n/** A product as the editor holds it: the stored fields plus keys the listener or a seed may carry. */\nexport type EditedProduct = HostProduct & {\n $id?: string\n skus?: string[]\n barcodes?: string[]\n stockHolds?: Record<string, unknown>\n}\n\n/**\n * The fields a product save writes, before its collection membership and its\n * timestamps: the product as edited, with\n *\n * - `$id` dropped (AGL-1374): the listener's synthetic key, never a field;\n * - the seed's `skus` / `barcodes` dropped (AGL-3321): `productSearchFields`\n * omits each when no variant has one, so a seeded copy would outlive the\n * last SKU;\n * - the trimmed name with the search keys derived from it, together, so the\n * two cannot drift (AGL-2501);\n * - a slug, the primary variant's price, the stock fields, the first image\n * and `updatedAtMs`.\n */\nexport function productSaveFields(current: EditedProduct, nowMs: number) {\n const {\n $id: _syntheticId,\n skus: _seededSkus,\n barcodes: _seededBarcodes,\n // A sample the merchant saves is theirs now (AGL-4065).\n sample: _sample,\n ...currentFields\n } = current\n const primaryVariant = current.variants[0]\n return {\n ...currentFields,\n ...productSearchFields({\n name: current.name.trim().slice(0, 120),\n variants: current.variants,\n }),\n slug: current.slug || commerceSlug(current.name),\n // The legacy flat price, only where there is one (AGL-3676): an owner\n // may save a product before pricing it, and an unpriced product is not\n // a free one. `null` rather than absent, so an edit's full replace and\n // the native save write the same field.\n priceUsd: variantHasPrice(primaryVariant) ? primaryVariant.priceUsd : null,\n ...productStockFields({\n variants: current.variants,\n oversellPolicy: current.oversellPolicy,\n }),\n imageUrl: current.mediaUrls?.[0] ?? current.imageUrl ?? null,\n updatedAtMs: nowMs,\n }\n}\n\n/** What the smart-collection rules are matched against: the saved name with the product's own facts. */\nexport function productMembershipInput(\n fields: { name: string },\n current: Pick<HostProduct, 'type' | 'tags' | 'categoryIds' | 'variants'>,\n) {\n return {\n name: fields.name,\n type: current.type,\n tags: current.tags,\n categoryIds: current.categoryIds,\n variants: current.variants,\n }\n}\n\n/**\n * An EDIT's replacing write, without its membership and server timestamp:\n * live stock holds are not the editor's to write (AGL-2356), so the seed's\n * are dropped and the stored ones carried; the product stays live\n * (`deletedAt: null`, AGL-3321).\n */\nexport function productEditWrite(\n fields: ReturnType<typeof productSaveFields>,\n liveHolds: Record<string, unknown> | null | undefined,\n) {\n const { stockHolds: _seededHolds, ...withoutHolds } = fields as typeof fields & {\n stockHolds?: Record<string, unknown>\n }\n return {\n ...withoutHolds,\n ...(liveHolds ? { stockHolds: liveHolds } : {}),\n deletedAt: null,\n }\n}\n\n/** A duplicate's name: the source's, marked as a copy. */\nexport function productDuplicateName(name: string): string {\n return `${name} (copy)`\n}\n\n/** The slug a duplicate starts from, before the store is asked which are taken. */\nexport function productDuplicateSlugBase(slug: string | undefined, name: string): string {\n return commerceSlug(`${slug || commerceSlug(name)}-copy`)\n}\n\n/**\n * What Duplicate writes (the products list's Duplicate, AGL-3321): the source\n * as a draft under the copy's name, with the COPY's search keys (spreading the\n * source's would sort and find it by the source's name) and stock fields, and\n * not the source's SKU and barcode keys, which are derived again from its\n * variants. The caller adds the claimed `slug`'s smart-collection membership.\n */\nexport function productDuplicateFields(\n product: HostProduct & { $id?: string; skus?: string[]; barcodes?: string[] },\n slug: string,\n nowMs: number,\n) {\n const { $id: _sourceId, skus: _skus, barcodes: _barcodes, ...copy } = product\n return {\n ...copy,\n ...productSearchFields({ name: productDuplicateName(product.name), variants: product.variants }),\n ...productStockFields(product),\n slug,\n status: 'draft' as const,\n createdAtMs: nowMs,\n updatedAtMs: nowMs,\n }\n}\n\n/** A smart collection as `hosts/{hostId}/collections` stores it, as the membership rules read it. */\nexport function smartCollectionRulesOf(id: string, data: Record<string, unknown>): SmartCollectionRules {\n return {\n id,\n rules: (data['rules'] as SmartCollectionRules['rules']) ?? [],\n matchAll: data['matchAll'] as boolean | undefined,\n }\n}\n\n/** One stock adjustment, as the Adjust stock dialog asks for it. */\nexport interface StockAdjustment {\n variantId: string\n delta: number\n reason: InventoryAdjustmentReason\n locationId?: string\n}\n\n/**\n * The product update and the ledger row one stock adjustment writes: the\n * variants with the count moved, the stock fields that move with it\n * (AGL-3321), and the same `inventoryAdjustments` row the sale webhook writes\n * (AGL-281). A zero or non-numeric delta writes nothing.\n */\nexport function stockAdjustmentWrite(\n product: HostProduct,\n productId: string,\n adjustment: StockAdjustment,\n nowMs: number,\n): { update: Record<string, unknown>; ledger: InventoryAdjustment } | null {\n const delta = Math.round(Number(adjustment.delta))\n if (!delta) return null\n const variants = adjustVariantInventory(product, adjustment.variantId, delta, adjustment.locationId || undefined)\n return {\n update: {\n variants,\n ...productStockFields({ ...product, variants }),\n updatedAtMs: nowMs,\n },\n ledger: {\n productId,\n variantId: adjustment.variantId,\n delta,\n reason: adjustment.reason,\n ...(adjustment.locationId ? { locationId: adjustment.locationId } : {}),\n atMs: nowMs,\n },\n }\n}\n"],"names":["adjustVariantInventory","commerceSlug","productSearchFields","productStockFields","variantHasPrice","productSaveFields","current","nowMs","$id","_syntheticId","skus","_seededSkus","barcodes","_seededBarcodes","sample","_sample","currentFields","primaryVariant","variants","name","trim","slice","slug","priceUsd","oversellPolicy","imageUrl","mediaUrls","updatedAtMs","productMembershipInput","fields","type","tags","categoryIds","productEditWrite","liveHolds","stockHolds","_seededHolds","withoutHolds","deletedAt","productDuplicateName","productDuplicateSlugBase","productDuplicateFields","product","_sourceId","_skus","_barcodes","copy","status","createdAtMs","smartCollectionRulesOf","id","data","rules","matchAll","stockAdjustmentWrite","productId","adjustment","delta","Math","round","Number","variantId","locationId","undefined","update","ledger","reason","atMs"],"mappings":"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"}
@@ -0,0 +1,26 @@
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 { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types';
18
+ import type { PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements';
19
+ /** Commerce's keys whose site-role preset holds for members of the whole workspace only. */
20
+ export type CommerceWorkspaceSitePermission = 'commerce.refunds' | 'commerce.posApprovals' | 'commerce.webhooks';
21
+ /** The site Admin, read for members of the whole workspace only. */
22
+ export declare const WORKSPACE_ADMIN_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>>;
23
+ /** The keys as commerce declares them, after `COMMERCE_SITE_PERMISSIONS`. */
24
+ export declare const COMMERCE_WORKSPACE_SITE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[];
25
+ /** The refusal a door answers when one of these keys is not held: names the permission, never a role. */
26
+ export declare function commerceWorkspacePermissionRefusal(key: CommerceWorkspaceSitePermission): string;
@@ -0,0 +1,81 @@
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 { _ as _extends } from "@swc/helpers/_/_extends";
17
+ /** The site Admin, read for members of the whole workspace only. */ export const WORKSPACE_ADMIN_SITE_ROLE_PRESET = {
18
+ admin: true,
19
+ editor: false,
20
+ author: false,
21
+ viewer: false
22
+ };
23
+ /** Who holds each before anyone sets it, as its description ends. */ const HOLDS = 'A member of the whole workspace who is the site’s admin holds it unless a role or member sets it; someone added to just this site holds it only when the site grants it.';
24
+ /** The keys as commerce declares them, after `COMMERCE_SITE_PERMISSIONS`. */ export const COMMERCE_WORKSPACE_SITE_PERMISSIONS = [
25
+ {
26
+ key: 'commerce.refunds',
27
+ label: 'Refund orders',
28
+ description: `Refund any order, online or at the register, in full or in part, with no register limit. ${HOLDS}`,
29
+ roleDefaults: {
30
+ owner: true,
31
+ admin: true,
32
+ editor: false,
33
+ viewer: false
34
+ },
35
+ hostRoleDefaults: _extends({}, WORKSPACE_ADMIN_SITE_ROLE_PRESET),
36
+ group: 'commerce',
37
+ groupLabel: 'Commerce',
38
+ presetFromSiteRole: true,
39
+ workspaceMembersOnly: true
40
+ },
41
+ {
42
+ key: 'commerce.posApprovals',
43
+ label: 'Approve refunds over the register’s limit',
44
+ description: `Refund at the register above its refund limit, approve a cashier’s refund over it with a PIN, and set or clear other staff members’ PINs. ${HOLDS}`,
45
+ roleDefaults: {
46
+ owner: true,
47
+ admin: true,
48
+ editor: false,
49
+ viewer: false
50
+ },
51
+ hostRoleDefaults: _extends({}, WORKSPACE_ADMIN_SITE_ROLE_PRESET),
52
+ group: 'commerce',
53
+ groupLabel: 'Commerce',
54
+ presetFromSiteRole: true,
55
+ workspaceMembersOnly: true
56
+ },
57
+ {
58
+ key: 'commerce.webhooks',
59
+ label: 'Manage order webhooks',
60
+ description: `Add, change, test and remove the store’s order webhooks, roll their secrets and resend deliveries. ${HOLDS}`,
61
+ roleDefaults: {
62
+ owner: true,
63
+ admin: true,
64
+ editor: false,
65
+ viewer: false
66
+ },
67
+ hostRoleDefaults: _extends({}, WORKSPACE_ADMIN_SITE_ROLE_PRESET),
68
+ group: 'commerce',
69
+ groupLabel: 'Commerce',
70
+ presetFromSiteRole: true,
71
+ workspaceMembersOnly: true
72
+ }
73
+ ];
74
+ /** The refusal a door answers when one of these keys is not held: names the permission, never a role. */ export function commerceWorkspacePermissionRefusal(key) {
75
+ var _ref;
76
+ var _COMMERCE_WORKSPACE_SITE_PERMISSIONS_find;
77
+ const label = (_ref = (_COMMERCE_WORKSPACE_SITE_PERMISSIONS_find = COMMERCE_WORKSPACE_SITE_PERMISSIONS.find((entry)=>entry.key === key)) == null ? void 0 : _COMMERCE_WORKSPACE_SITE_PERMISSIONS_find.label) != null ? _ref : key;
78
+ return `Your role does not include "${label}" — ask an organization admin`;
79
+ }
80
+
81
+ //# sourceMappingURL=workspace-site-permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/workspace-site-permissions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport type { PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\n\n/*\n * REFUNDS, THE REGISTER'S APPROVALS AND ORDER WEBHOOKS (AGL-4047).\n *\n * Refunding an order, approving a refund over the register's limit (and\n * setting other staff members' PINs), and managing order webhooks asked for\n * an admin of the WHOLE workspace who is also the site's admin (`refund.ts`,\n * AGL-2372): a site's Members card can make a contractor the admin of one\n * site, which writes the same `'admin'` into `memberRoles` as an owner's, and\n * none of these were ever theirs. They are now keys whose preset is the site\n * role for members of the whole workspace only (`workspaceMembersOnly`): the\n * site's Admin among them, and nobody added to just that site unless the\n * site grants it. A custom role, a member's own permissions or the site's\n * toggle grants or refuses each one.\n *\n * Their own module, not `plugin-permissions.ts`: the storefront imports that\n * one, and only the declarations and the server doors read these.\n */\n\n/** Commerce's keys whose site-role preset holds for members of the whole workspace only. */\nexport type CommerceWorkspaceSitePermission = 'commerce.refunds' | 'commerce.posApprovals' | 'commerce.webhooks'\n\n/** The site Admin, read for members of the whole workspace only. */\nexport const WORKSPACE_ADMIN_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>> = {\n admin: true,\n editor: false,\n author: false,\n viewer: false,\n}\n\n/** Who holds each before anyone sets it, as its description ends. */\nconst HOLDS =\n 'A member of the whole workspace who is the site’s admin holds it unless a role or member sets it; someone added to just this site holds it only when the site grants it.'\n\n/** The keys as commerce declares them, after `COMMERCE_SITE_PERMISSIONS`. */\nexport const COMMERCE_WORKSPACE_SITE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[] = [\n {\n key: 'commerce.refunds',\n label: 'Refund orders',\n description: `Refund any order, online or at the register, in full or in part, with no register limit. ${HOLDS}`,\n roleDefaults: { owner: true, admin: true, editor: false, viewer: false },\n hostRoleDefaults: { ...WORKSPACE_ADMIN_SITE_ROLE_PRESET },\n group: 'commerce',\n groupLabel: 'Commerce',\n presetFromSiteRole: true,\n workspaceMembersOnly: true,\n },\n {\n key: 'commerce.posApprovals',\n label: 'Approve refunds over the register’s limit',\n description: `Refund at the register above its refund limit, approve a cashier’s refund over it with a PIN, and set or clear other staff members’ PINs. ${HOLDS}`,\n roleDefaults: { owner: true, admin: true, editor: false, viewer: false },\n hostRoleDefaults: { ...WORKSPACE_ADMIN_SITE_ROLE_PRESET },\n group: 'commerce',\n groupLabel: 'Commerce',\n presetFromSiteRole: true,\n workspaceMembersOnly: true,\n },\n {\n key: 'commerce.webhooks',\n label: 'Manage order webhooks',\n description: `Add, change, test and remove the store’s order webhooks, roll their secrets and resend deliveries. ${HOLDS}`,\n roleDefaults: { owner: true, admin: true, editor: false, viewer: false },\n hostRoleDefaults: { ...WORKSPACE_ADMIN_SITE_ROLE_PRESET },\n group: 'commerce',\n groupLabel: 'Commerce',\n presetFromSiteRole: true,\n workspaceMembersOnly: true,\n },\n]\n\n/** The refusal a door answers when one of these keys is not held: names the permission, never a role. */\nexport function commerceWorkspacePermissionRefusal(key: CommerceWorkspaceSitePermission): string {\n const label = COMMERCE_WORKSPACE_SITE_PERMISSIONS.find((entry) => entry.key === key)?.label ?? key\n return `Your role does not include \"${label}\" — ask an organization admin`\n}\n"],"names":["WORKSPACE_ADMIN_SITE_ROLE_PRESET","admin","editor","author","viewer","HOLDS","COMMERCE_WORKSPACE_SITE_PERMISSIONS","key","label","description","roleDefaults","owner","hostRoleDefaults","group","groupLabel","presetFromSiteRole","workspaceMembersOnly","commerceWorkspacePermissionRefusal","find","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AA0BD,kEAAkE,GAClE,OAAO,MAAMA,mCAA8E;IACzFC,OAAO;IACPC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV,EAAC;AAED,mEAAmE,GACnE,MAAMC,QACJ;AAEF,2EAA2E,GAC3E,OAAO,MAAMC,sCAAiF;IAC5F;QACEC,KAAK;QACLC,OAAO;QACPC,aAAa,CAAC,yFAAyF,EAAEJ,OAAO;QAChHK,cAAc;YAAEC,OAAO;YAAMV,OAAO;YAAMC,QAAQ;YAAOE,QAAQ;QAAM;QACvEQ,kBAAkB,aAAKZ;QACvBa,OAAO;QACPC,YAAY;QACZC,oBAAoB;QACpBC,sBAAsB;IACxB;IACA;QACET,KAAK;QACLC,OAAO;QACPC,aAAa,CAAC,0IAA0I,EAAEJ,OAAO;QACjKK,cAAc;YAAEC,OAAO;YAAMV,OAAO;YAAMC,QAAQ;YAAOE,QAAQ;QAAM;QACvEQ,kBAAkB,aAAKZ;QACvBa,OAAO;QACPC,YAAY;QACZC,oBAAoB;QACpBC,sBAAsB;IACxB;IACA;QACET,KAAK;QACLC,OAAO;QACPC,aAAa,CAAC,mGAAmG,EAAEJ,OAAO;QAC1HK,cAAc;YAAEC,OAAO;YAAMV,OAAO;YAAMC,QAAQ;YAAOE,QAAQ;QAAM;QACvEQ,kBAAkB,aAAKZ;QACvBa,OAAO;QACPC,YAAY;QACZC,oBAAoB;QACpBC,sBAAsB;IACxB;CACD,CAAA;AAED,uGAAuG,GACvG,OAAO,SAASC,mCAAmCV,GAAoC;;QACvED;IAAd,MAAME,iBAAQF,4CAAAA,oCAAoCY,IAAI,CAAC,CAACC,QAAUA,MAAMZ,GAAG,KAAKA,yBAAlED,0CAAwEE,KAAK,mBAAID;IAC/F,OAAO,CAAC,4BAA4B,EAAEC,MAAM,6BAA6B,CAAC;AAC5E"}
package/src/lib/plugin.js CHANGED
@@ -19,6 +19,8 @@ import { lazy } from "react";
19
19
  const PosConsolePage = lazy(()=>import("./components/console/pos-page.component.js"));
20
20
  /** The customer display (AGL-3608): loads only on the public page that shows it. */ const PosDisplayPage = lazy(()=>import("./components/console/pos-display-page.component.js"));
21
21
  const PosKioskPage = lazy(()=>import("./components/console/pos-kiosk-page.component.js"));
22
+ const PosHardwareTermsPage = lazy(()=>import("./components/console/pos-hardware-terms-page.component.js"));
23
+ const PosHardwareTermsSignPage = lazy(()=>import("./components/console/pos-hardware-terms-sign-page.component.js"));
22
24
  /** Staff → Hardware orders (AGL-4038): loads only on its staff page. */ const PosHardwareOrdersStaffPage = lazy(()=>import("./components/console/pos-hardware-orders-staff-page.component.js"));
23
25
  /** Staff → Reader start-up screen (AGL-4048): loads only on its staff page. */ const PosReaderSplashStaffPage = lazy(()=>import("./components/console/pos-reader-splash-staff-page.component.js"));
24
26
  const CommerceGlanceCard = lazy(()=>import("./components/console/commerce-glance-card.component.js"));
@@ -120,6 +122,20 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
120
122
  path: '/pos-kiosk',
121
123
  title: 'Self-service kiosk',
122
124
  Component: PosKioskPage
125
+ },
126
+ // The terms of sale for a card reader sold at the platform's register
127
+ // (AGL-4038): on its customer display, by the display's own token
128
+ // (`server/pos-display.ts`), and the printable booth sign, which holds
129
+ // only the published terms' address and summary.
130
+ {
131
+ path: '/hardware-terms',
132
+ title: 'Terms of sale',
133
+ Component: PosHardwareTermsPage
134
+ },
135
+ {
136
+ path: '/hardware-terms-sign',
137
+ title: 'Terms of sale sign',
138
+ Component: PosHardwareTermsSignPage
123
139
  }
124
140
  ],
125
141
  // Dashboard/analytics glance card (AGL-419): rendered through the