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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/README.md +1 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/cart.js +25 -12
  4. package/src/lib/components/cart.js.map +1 -1
  5. package/src/lib/components/commerce-console-page.js +70 -13
  6. package/src/lib/components/commerce-console-page.js.map +1 -1
  7. package/src/lib/components/console/order-detail-dialog.component.js +2 -1
  8. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  9. package/src/lib/components/console/order-webhooks-card.component.js +1 -1
  10. package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
  11. package/src/lib/components/console/payment-methods-card.component.js +1 -1
  12. package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
  13. package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +5 -0
  14. package/src/lib/components/console/pos/pos-cart-panel.component.js +18 -12
  15. package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -1
  16. package/src/lib/components/console/pos/pos-hardware-api.d.ts +76 -5
  17. package/src/lib/components/console/pos/pos-hardware-api.js +53 -11
  18. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -1
  19. package/src/lib/components/console/pos/pos-hardware-buyer.component.d.ts +18 -0
  20. package/src/lib/components/console/pos/pos-hardware-buyer.component.js +629 -0
  21. package/src/lib/components/console/pos/pos-hardware-buyer.component.js.map +1 -0
  22. package/src/lib/components/console/pos/pos-hardware-sale-api.d.ts +162 -0
  23. package/src/lib/components/console/pos/pos-hardware-sale-api.js +122 -0
  24. package/src/lib/components/console/pos/pos-hardware-sale-api.js.map +1 -0
  25. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.d.ts +39 -0
  26. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js +251 -0
  27. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js.map +1 -0
  28. package/src/lib/components/console/pos/use-pos-display.js +50 -0
  29. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  30. package/src/lib/components/console/pos-cellular-control.component.d.ts +41 -0
  31. package/src/lib/components/console/pos-cellular-control.component.js +211 -0
  32. package/src/lib/components/console/pos-cellular-control.component.js.map +1 -0
  33. package/src/lib/components/console/pos-devices-card.component.d.ts +9 -1
  34. package/src/lib/components/console/pos-devices-card.component.js +47 -15
  35. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  36. package/src/lib/components/console/pos-display/pos-display-api.d.ts +6 -0
  37. package/src/lib/components/console/pos-display/pos-display-api.js +8 -1
  38. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  39. package/src/lib/components/console/pos-display-page.component.d.ts +13 -0
  40. package/src/lib/components/console/pos-display-page.component.js +44 -2
  41. package/src/lib/components/console/pos-display-page.component.js.map +1 -1
  42. package/src/lib/components/console/pos-hardware-card.component.d.ts +12 -4
  43. package/src/lib/components/console/pos-hardware-card.component.js +157 -36
  44. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -1
  45. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +360 -80
  46. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -1
  47. package/src/lib/components/console/pos-hardware-terms-page.component.d.ts +34 -0
  48. package/src/lib/components/console/pos-hardware-terms-page.component.js +257 -0
  49. package/src/lib/components/console/pos-hardware-terms-page.component.js.map +1 -0
  50. package/src/lib/components/console/pos-hardware-terms-sign-page.component.d.ts +27 -0
  51. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js +104 -0
  52. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js.map +1 -0
  53. package/src/lib/components/console/pos-hardware-waiting-card.component.d.ts +24 -0
  54. package/src/lib/components/console/pos-hardware-waiting-card.component.js +205 -0
  55. package/src/lib/components/console/pos-hardware-waiting-card.component.js.map +1 -0
  56. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +297 -201
  57. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -1
  58. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +3 -0
  59. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +260 -92
  60. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  61. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +9 -0
  62. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +15 -6
  63. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  64. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.d.ts +25 -0
  65. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js +136 -0
  66. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js.map +1 -0
  67. package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +6 -0
  68. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +48 -31
  69. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -1
  70. package/src/lib/components/console/pos-page.component.d.ts +1 -1
  71. package/src/lib/components/console/pos-page.component.js +63 -12
  72. package/src/lib/components/console/pos-page.component.js.map +1 -1
  73. package/src/lib/components/console/pos-reader-splash-setting.component.js +1 -1
  74. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -1
  75. package/src/lib/components/console/product-check-in-fields.component.d.ts +4 -2
  76. package/src/lib/components/console/product-check-in-fields.component.js +60 -14
  77. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -1
  78. package/src/lib/components/console/product-editor-dialog.component.js +44 -1
  79. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  80. package/src/lib/components/console/product-hardware-sku-field.component.d.ts +19 -0
  81. package/src/lib/components/console/product-hardware-sku-field.component.js +92 -0
  82. package/src/lib/components/console/product-hardware-sku-field.component.js.map +1 -0
  83. package/src/lib/components/console/product-kiosk-offer-field.component.d.ts +22 -0
  84. package/src/lib/components/console/product-kiosk-offer-field.component.js +94 -0
  85. package/src/lib/components/console/product-kiosk-offer-field.component.js.map +1 -0
  86. package/src/lib/components/console/product-register-fields.component.d.ts +4 -0
  87. package/src/lib/components/console/product-register-fields.component.js +31 -2
  88. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  89. package/src/lib/components/console/products-hub-card.component.js +11 -2
  90. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  91. package/src/lib/components/console/register-hardware-card.component.d.ts +11 -0
  92. package/src/lib/components/console/register-hardware-card.component.js +56 -27
  93. package/src/lib/components/console/register-hardware-card.component.js.map +1 -1
  94. package/src/lib/components/console/reservation-guests-dialog.component.d.ts +28 -0
  95. package/src/lib/components/console/reservation-guests-dialog.component.js +189 -0
  96. package/src/lib/components/console/reservation-guests-dialog.component.js.map +1 -0
  97. package/src/lib/components/console/reservations-card.component.d.ts +9 -0
  98. package/src/lib/components/console/reservations-card.component.js +158 -23
  99. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  100. package/src/lib/components/console/shippingeasy-card.component.js +1 -1
  101. package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
  102. package/src/lib/components/console/shipstation-card.component.d.ts +1 -1
  103. package/src/lib/components/console/shipstation-card.component.js +3 -3
  104. package/src/lib/components/console/shipstation-card.component.js.map +1 -1
  105. package/src/lib/components/product-detail.d.ts +4 -0
  106. package/src/lib/components/product-detail.js +40 -5
  107. package/src/lib/components/product-detail.js.map +1 -1
  108. package/src/lib/components/product-reviews.js +28 -11
  109. package/src/lib/components/product-reviews.js.map +1 -1
  110. package/src/lib/components/reservation-widget.js +28 -5
  111. package/src/lib/components/reservation-widget.js.map +1 -1
  112. package/src/lib/components/use-day-picker.d.ts +25 -0
  113. package/src/lib/components/use-day-picker.js +57 -0
  114. package/src/lib/components/use-day-picker.js.map +1 -0
  115. package/src/lib/constants/operator-alerts.d.ts +55 -0
  116. package/src/lib/constants/operator-alerts.js +156 -1
  117. package/src/lib/constants/operator-alerts.js.map +1 -1
  118. package/src/lib/declarations.console-server.js +54 -1
  119. package/src/lib/declarations.console-server.js.map +1 -1
  120. package/src/lib/declarations.d.ts +4 -2
  121. package/src/lib/declarations.js +15 -4
  122. package/src/lib/declarations.js.map +1 -1
  123. package/src/lib/declarations.server.js +9 -0
  124. package/src/lib/declarations.server.js.map +1 -1
  125. package/src/lib/model/commerce-cart.d.ts +9 -1
  126. package/src/lib/model/commerce-cart.js +4 -3
  127. package/src/lib/model/commerce-cart.js.map +1 -1
  128. package/src/lib/model/commerce-check-in-product.d.ts +26 -0
  129. package/src/lib/model/commerce-check-in-product.js +64 -2
  130. package/src/lib/model/commerce-check-in-product.js.map +1 -1
  131. package/src/lib/model/commerce-check-in-record.d.ts +127 -0
  132. package/src/lib/model/commerce-check-in-record.js +36 -0
  133. package/src/lib/model/commerce-check-in-record.js.map +1 -0
  134. package/src/lib/model/commerce-check-in.d.ts +91 -87
  135. package/src/lib/model/commerce-check-in.js +186 -62
  136. package/src/lib/model/commerce-check-in.js.map +1 -1
  137. package/src/lib/model/commerce-kiosk-offer.d.ts +31 -0
  138. package/src/lib/model/commerce-kiosk-offer.js +35 -0
  139. package/src/lib/model/commerce-kiosk-offer.js.map +1 -0
  140. package/src/lib/model/commerce-orders.d.ts +76 -0
  141. package/src/lib/model/commerce-orders.js +6 -0
  142. package/src/lib/model/commerce-orders.js.map +1 -1
  143. package/src/lib/model/commerce-pos-day-report.d.ts +54 -1
  144. package/src/lib/model/commerce-pos-day-report.js +39 -2
  145. package/src/lib/model/commerce-pos-day-report.js.map +1 -1
  146. package/src/lib/model/commerce-pos-offline.d.ts +1 -1
  147. package/src/lib/model/commerce-pos-offline.js +6 -1
  148. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  149. package/src/lib/model/commerce-pos-ops.js +3 -2
  150. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  151. package/src/lib/model/commerce-pos.d.ts +36 -3
  152. package/src/lib/model/commerce-pos.js +14 -6
  153. package/src/lib/model/commerce-pos.js.map +1 -1
  154. package/src/lib/model/commerce-receipt.js +3 -2
  155. package/src/lib/model/commerce-receipt.js.map +1 -1
  156. package/src/lib/model/commerce-reservations.d.ts +69 -4
  157. package/src/lib/model/commerce-reservations.js +57 -5
  158. package/src/lib/model/commerce-reservations.js.map +1 -1
  159. package/src/lib/model/commerce.d.ts +66 -0
  160. package/src/lib/model/commerce.js +15 -0
  161. package/src/lib/model/commerce.js.map +1 -1
  162. package/src/lib/model/order-list-fields.d.ts +11 -0
  163. package/src/lib/model/order-list-fields.js +17 -1
  164. package/src/lib/model/order-list-fields.js.map +1 -1
  165. package/src/lib/model/own-store-tax-summary.d.ts +152 -0
  166. package/src/lib/model/own-store-tax-summary.js +288 -0
  167. package/src/lib/model/own-store-tax-summary.js.map +1 -0
  168. package/src/lib/model/plugin-permissions.d.ts +74 -0
  169. package/src/lib/model/plugin-permissions.js +224 -7
  170. package/src/lib/model/plugin-permissions.js.map +1 -1
  171. package/src/lib/model/pos-cellular.d.ts +223 -0
  172. package/src/lib/model/pos-cellular.js +293 -0
  173. package/src/lib/model/pos-cellular.js.map +1 -0
  174. package/src/lib/model/pos-hardware-in-person.d.ts +98 -0
  175. package/src/lib/model/pos-hardware-in-person.js +224 -0
  176. package/src/lib/model/pos-hardware-in-person.js.map +1 -0
  177. package/src/lib/model/pos-hardware-tax-summary.d.ts +166 -0
  178. package/src/lib/model/pos-hardware-tax-summary.js +225 -0
  179. package/src/lib/model/pos-hardware-tax-summary.js.map +1 -0
  180. package/src/lib/model/pos-hardware-terms.d.ts +74 -0
  181. package/src/lib/model/pos-hardware-terms.js +115 -0
  182. package/src/lib/model/pos-hardware-terms.js.map +1 -0
  183. package/src/lib/model/pos-hardware.d.ts +349 -7
  184. package/src/lib/model/pos-hardware.js +291 -20
  185. package/src/lib/model/pos-hardware.js.map +1 -1
  186. package/src/lib/model/product-write.d.ts +10 -0
  187. package/src/lib/model/product-write.js +4 -2
  188. package/src/lib/model/product-write.js.map +1 -1
  189. package/src/lib/model/workspace-site-permissions.d.ts +26 -0
  190. package/src/lib/model/workspace-site-permissions.js +81 -0
  191. package/src/lib/model/workspace-site-permissions.js.map +1 -0
  192. package/src/lib/plugin.js +16 -0
  193. package/src/lib/plugin.js.map +1 -1
  194. package/src/lib/server/billing-webhook.js +25 -1
  195. package/src/lib/server/billing-webhook.js.map +1 -1
  196. package/src/lib/server/cancel-order.js +7 -5
  197. package/src/lib/server/cancel-order.js.map +1 -1
  198. package/src/lib/server/cart-checkout.js +20 -8
  199. package/src/lib/server/cart-checkout.js.map +1 -1
  200. package/src/lib/server/cart.js +29 -0
  201. package/src/lib/server/cart.js.map +1 -1
  202. package/src/lib/server/checkout.js +42 -13
  203. package/src/lib/server/checkout.js.map +1 -1
  204. package/src/lib/server/collection-membership.d.ts +4 -0
  205. package/src/lib/server/collection-membership.js +5 -10
  206. package/src/lib/server/collection-membership.js.map +1 -1
  207. package/src/lib/server/commerce-site-keys.d.ts +48 -0
  208. package/src/lib/server/commerce-site-keys.js +61 -0
  209. package/src/lib/server/commerce-site-keys.js.map +1 -0
  210. package/src/lib/server/draft-order.js +8 -5
  211. package/src/lib/server/draft-order.js.map +1 -1
  212. package/src/lib/server/fulfill-order.js +7 -5
  213. package/src/lib/server/fulfill-order.js.map +1 -1
  214. package/src/lib/server/gift-cards.js +8 -5
  215. package/src/lib/server/gift-cards.js.map +1 -1
  216. package/src/lib/server/local-fulfillment-status.js +9 -6
  217. package/src/lib/server/local-fulfillment-status.js.map +1 -1
  218. package/src/lib/server/member-post.js +6 -5
  219. package/src/lib/server/member-post.js.map +1 -1
  220. package/src/lib/server/membership-admin-password.d.ts +4 -2
  221. package/src/lib/server/membership-admin-password.js +10 -8
  222. package/src/lib/server/membership-admin-password.js.map +1 -1
  223. package/src/lib/server/membership-admin-remove.d.ts +7 -5
  224. package/src/lib/server/membership-admin-remove.js +9 -9
  225. package/src/lib/server/membership-admin-remove.js.map +1 -1
  226. package/src/lib/server/order-notifications.js +5 -2
  227. package/src/lib/server/order-notifications.js.map +1 -1
  228. package/src/lib/server/order-print.js +7 -5
  229. package/src/lib/server/order-print.js.map +1 -1
  230. package/src/lib/server/order-receipt-send.js +8 -5
  231. package/src/lib/server/order-receipt-send.js.map +1 -1
  232. package/src/lib/server/order-webhooks.js +8 -8
  233. package/src/lib/server/order-webhooks.js.map +1 -1
  234. package/src/lib/server/orders-shipping-prepare.js +9 -6
  235. package/src/lib/server/orders-shipping-prepare.js.map +1 -1
  236. package/src/lib/server/own-store-tax-return-source.d.ts +47 -0
  237. package/src/lib/server/own-store-tax-return-source.js +312 -0
  238. package/src/lib/server/own-store-tax-return-source.js.map +1 -0
  239. package/src/lib/server/payment-methods.js +13 -9
  240. package/src/lib/server/payment-methods.js.map +1 -1
  241. package/src/lib/server/pos-auth.d.ts +37 -6
  242. package/src/lib/server/pos-auth.js +40 -15
  243. package/src/lib/server/pos-auth.js.map +1 -1
  244. package/src/lib/server/pos-cellular-billing.d.ts +81 -0
  245. package/src/lib/server/pos-cellular-billing.js +711 -0
  246. package/src/lib/server/pos-cellular-billing.js.map +1 -0
  247. package/src/lib/server/pos-cellular-notify.d.ts +47 -0
  248. package/src/lib/server/pos-cellular-notify.js +150 -0
  249. package/src/lib/server/pos-cellular-notify.js.map +1 -0
  250. package/src/lib/server/pos-cellular.d.ts +88 -0
  251. package/src/lib/server/pos-cellular.js +339 -0
  252. package/src/lib/server/pos-cellular.js.map +1 -0
  253. package/src/lib/server/pos-check-in.d.ts +5 -1
  254. package/src/lib/server/pos-check-in.js +296 -28
  255. package/src/lib/server/pos-check-in.js.map +1 -1
  256. package/src/lib/server/pos-day-report.js +29 -3
  257. package/src/lib/server/pos-day-report.js.map +1 -1
  258. package/src/lib/server/pos-display.d.ts +16 -0
  259. package/src/lib/server/pos-display.js +129 -2
  260. package/src/lib/server/pos-display.js.map +1 -1
  261. package/src/lib/server/pos-hardware-admin.d.ts +10 -1
  262. package/src/lib/server/pos-hardware-admin.js +250 -84
  263. package/src/lib/server/pos-hardware-admin.js.map +1 -1
  264. package/src/lib/server/pos-hardware-attach.d.ts +105 -0
  265. package/src/lib/server/pos-hardware-attach.js +443 -0
  266. package/src/lib/server/pos-hardware-attach.js.map +1 -0
  267. package/src/lib/server/pos-hardware-buyer-lookup.d.ts +58 -0
  268. package/src/lib/server/pos-hardware-buyer-lookup.js +180 -0
  269. package/src/lib/server/pos-hardware-buyer-lookup.js.map +1 -0
  270. package/src/lib/server/pos-hardware-buyer-record.d.ts +29 -0
  271. package/src/lib/server/pos-hardware-buyer-record.js +86 -0
  272. package/src/lib/server/pos-hardware-buyer-record.js.map +1 -0
  273. package/src/lib/server/pos-hardware-cancel.d.ts +58 -0
  274. package/src/lib/server/pos-hardware-cancel.js +147 -0
  275. package/src/lib/server/pos-hardware-cancel.js.map +1 -0
  276. package/src/lib/server/pos-hardware-closure-notices.d.ts +27 -0
  277. package/src/lib/server/pos-hardware-closure-notices.js +85 -0
  278. package/src/lib/server/pos-hardware-closure-notices.js.map +1 -0
  279. package/src/lib/server/pos-hardware-closure.d.ts +60 -0
  280. package/src/lib/server/pos-hardware-closure.js +153 -0
  281. package/src/lib/server/pos-hardware-closure.js.map +1 -0
  282. package/src/lib/server/pos-hardware-in-person.d.ts +61 -0
  283. package/src/lib/server/pos-hardware-in-person.js +236 -0
  284. package/src/lib/server/pos-hardware-in-person.js.map +1 -0
  285. package/src/lib/server/pos-hardware-notify.d.ts +40 -2
  286. package/src/lib/server/pos-hardware-notify.js +195 -14
  287. package/src/lib/server/pos-hardware-notify.js.map +1 -1
  288. package/src/lib/server/pos-hardware-readers.d.ts +115 -0
  289. package/src/lib/server/pos-hardware-readers.js +430 -0
  290. package/src/lib/server/pos-hardware-readers.js.map +1 -0
  291. package/src/lib/server/pos-hardware-returns.d.ts +116 -0
  292. package/src/lib/server/pos-hardware-returns.js +502 -0
  293. package/src/lib/server/pos-hardware-returns.js.map +1 -0
  294. package/src/lib/server/pos-hardware-sale-fields.d.ts +28 -0
  295. package/src/lib/server/pos-hardware-sale-fields.js +44 -0
  296. package/src/lib/server/pos-hardware-sale-fields.js.map +1 -0
  297. package/src/lib/server/pos-hardware-sale.d.ts +54 -0
  298. package/src/lib/server/pos-hardware-sale.js +369 -0
  299. package/src/lib/server/pos-hardware-sale.js.map +1 -0
  300. package/src/lib/server/pos-hardware-tax-return-source.d.ts +99 -0
  301. package/src/lib/server/pos-hardware-tax-return-source.js +462 -0
  302. package/src/lib/server/pos-hardware-tax-return-source.js.map +1 -0
  303. package/src/lib/server/pos-hardware-terms.d.ts +64 -0
  304. package/src/lib/server/pos-hardware-terms.js +154 -0
  305. package/src/lib/server/pos-hardware-terms.js.map +1 -0
  306. package/src/lib/server/pos-hardware-user-eraser.d.ts +3 -0
  307. package/src/lib/server/pos-hardware-user-eraser.js +31 -7
  308. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -1
  309. package/src/lib/server/pos-hardware-webhook.d.ts +5 -0
  310. package/src/lib/server/pos-hardware-webhook.js +51 -6
  311. package/src/lib/server/pos-hardware-webhook.js.map +1 -1
  312. package/src/lib/server/pos-hardware.d.ts +74 -1
  313. package/src/lib/server/pos-hardware.js +175 -10
  314. package/src/lib/server/pos-hardware.js.map +1 -1
  315. package/src/lib/server/pos-kiosk.d.ts +2 -0
  316. package/src/lib/server/pos-kiosk.js +17 -12
  317. package/src/lib/server/pos-kiosk.js.map +1 -1
  318. package/src/lib/server/pos-offline-card-order.d.ts +64 -0
  319. package/src/lib/server/pos-offline-card-order.js +102 -0
  320. package/src/lib/server/pos-offline-card-order.js.map +1 -0
  321. package/src/lib/server/pos-offline-card-reconcile.d.ts +67 -1
  322. package/src/lib/server/pos-offline-card-reconcile.js +215 -18
  323. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -1
  324. package/src/lib/server/pos-offline-sync.d.ts +39 -0
  325. package/src/lib/server/pos-offline-sync.js +190 -55
  326. package/src/lib/server/pos-offline-sync.js.map +1 -1
  327. package/src/lib/server/pos-ops-gate.d.ts +28 -9
  328. package/src/lib/server/pos-ops-gate.js +50 -19
  329. package/src/lib/server/pos-ops-gate.js.map +1 -1
  330. package/src/lib/server/pos-ops-routes.js +24 -0
  331. package/src/lib/server/pos-ops-routes.js.map +1 -1
  332. package/src/lib/server/pos-order-refund-ids.d.ts +28 -0
  333. package/src/lib/server/pos-order-refund-ids.js +31 -0
  334. package/src/lib/server/pos-order-refund-ids.js.map +1 -0
  335. package/src/lib/server/pos-order.js +52 -49
  336. package/src/lib/server/pos-order.js.map +1 -1
  337. package/src/lib/server/pos-payment.js +1 -1
  338. package/src/lib/server/pos-payment.js.map +1 -1
  339. package/src/lib/server/pos-reader-splash-admin.js +30 -32
  340. package/src/lib/server/pos-reader-splash-admin.js.map +1 -1
  341. package/src/lib/server/pos-reader-splash-images-route.d.ts +17 -0
  342. package/src/lib/server/pos-reader-splash-images-route.js +113 -0
  343. package/src/lib/server/pos-reader-splash-images-route.js.map +1 -0
  344. package/src/lib/server/pos-reader-splash-images.d.ts +51 -0
  345. package/src/lib/server/pos-reader-splash-images.js +123 -0
  346. package/src/lib/server/pos-reader-splash-images.js.map +1 -0
  347. package/src/lib/server/pos-reader-splash.d.ts +68 -1
  348. package/src/lib/server/pos-reader-splash.js +182 -11
  349. package/src/lib/server/pos-reader-splash.js.map +1 -1
  350. package/src/lib/server/pos-readers.d.ts +29 -0
  351. package/src/lib/server/pos-readers.js +134 -35
  352. package/src/lib/server/pos-readers.js.map +1 -1
  353. package/src/lib/server/pos-refund-authority.d.ts +3 -1
  354. package/src/lib/server/pos-refund-authority.js +3 -2
  355. package/src/lib/server/pos-refund-authority.js.map +1 -1
  356. package/src/lib/server/pos-return.d.ts +10 -0
  357. package/src/lib/server/pos-return.js +29 -7
  358. package/src/lib/server/pos-return.js.map +1 -1
  359. package/src/lib/server/pos-sale.js +6 -1
  360. package/src/lib/server/pos-sale.js.map +1 -1
  361. package/src/lib/server/pos-shift.js +11 -0
  362. package/src/lib/server/pos-shift.js.map +1 -1
  363. package/src/lib/server/pos-staff-pin.js +29 -24
  364. package/src/lib/server/pos-staff-pin.js.map +1 -1
  365. package/src/lib/server/pos-terminal.d.ts +14 -4
  366. package/src/lib/server/pos-terminal.js +34 -38
  367. package/src/lib/server/pos-terminal.js.map +1 -1
  368. package/src/lib/server/printers.d.ts +7 -3
  369. package/src/lib/server/printers.js +18 -12
  370. package/src/lib/server/printers.js.map +1 -1
  371. package/src/lib/server/product-drafts.d.ts +11 -1
  372. package/src/lib/server/product-drafts.js +12 -8
  373. package/src/lib/server/product-drafts.js.map +1 -1
  374. package/src/lib/server/product.d.ts +15 -1
  375. package/src/lib/server/product.js +16 -3
  376. package/src/lib/server/product.js.map +1 -1
  377. package/src/lib/server/products-write.d.ts +26 -2
  378. package/src/lib/server/products-write.js +19 -7
  379. package/src/lib/server/products-write.js.map +1 -1
  380. package/src/lib/server/refund.js +55 -42
  381. package/src/lib/server/refund.js.map +1 -1
  382. package/src/lib/server/reservation-availability-query.d.ts +8 -0
  383. package/src/lib/server/reservation-availability-query.js +11 -1
  384. package/src/lib/server/reservation-availability-query.js.map +1 -1
  385. package/src/lib/server/reservation-availability.d.ts +1 -1
  386. package/src/lib/server/reservation-availability.js +7 -2
  387. package/src/lib/server/reservation-availability.js.map +1 -1
  388. package/src/lib/server/reservation-guests.d.ts +32 -0
  389. package/src/lib/server/reservation-guests.js +285 -0
  390. package/src/lib/server/reservation-guests.js.map +1 -0
  391. package/src/lib/server/reservation-walk-in.d.ts +11 -1
  392. package/src/lib/server/reservation-walk-in.js +56 -12
  393. package/src/lib/server/reservation-walk-in.js.map +1 -1
  394. package/src/lib/server/reserve.js +18 -5
  395. package/src/lib/server/reserve.js.map +1 -1
  396. package/src/lib/server/returns.js +9 -6
  397. package/src/lib/server/returns.js.map +1 -1
  398. package/src/lib/server/reviews.d.ts +22 -2
  399. package/src/lib/server/reviews.js +88 -20
  400. package/src/lib/server/reviews.js.map +1 -1
  401. package/src/lib/server/shipping-connectors.js +10 -11
  402. package/src/lib/server/shipping-connectors.js.map +1 -1
  403. package/src/lib/server/site-page-resolver.js +20 -3
  404. package/src/lib/server/site-page-resolver.js.map +1 -1
  405. package/src/lib/server/store-day.d.ts +33 -0
  406. package/src/lib/server/store-day.js +46 -0
  407. package/src/lib/server/store-day.js.map +1 -0
  408. package/src/lib/server/store-setup-ai-writer.d.ts +2 -1
  409. package/src/lib/server/store-setup-ai-writer.js +5 -6
  410. package/src/lib/server/store-setup-ai-writer.js.map +1 -1
  411. package/src/lib/server/storefront-tax.d.ts +13 -0
  412. package/src/lib/server/storefront-tax.js +8 -1
  413. package/src/lib/server/storefront-tax.js.map +1 -1
  414. package/src/lib/server/tax-return-source.d.ts +32 -3
  415. package/src/lib/server/tax-return-source.js +99 -20
  416. package/src/lib/server/tax-return-source.js.map +1 -1
  417. package/src/lib/server.js +100 -2
  418. package/src/lib/server.js.map +1 -1
  419. package/src/lib/testing/fake-firestore.js +58 -16
  420. package/src/lib/testing/fake-firestore.js.map +1 -1
  421. package/src/lib/testing/pos-ops-memory-firestore.js +18 -4
  422. package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -1
  423. package/src/lib/testing/site-register-permissions.d.ts +29 -0
  424. package/src/lib/testing/site-register-permissions.js +38 -0
  425. package/src/lib/testing/site-register-permissions.js.map +1 -0
  426. package/src/lib/transfer/tracking.server.js +8 -5
  427. package/src/lib/transfer/tracking.server.js.map +1 -1
  428. package/src/lib/utils/add-to-cart.d.ts +2 -0
  429. package/src/lib/utils/add-to-cart.js +6 -3
  430. package/src/lib/utils/add-to-cart.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-shift.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport {\n computePosShiftReport,\n posShiftPrintReport,\n posCashVarianceCents,\n type PosCashEvent,\n type PosCashEventType,\n type PosShift,\n type PosShiftRefund,\n type PosShiftReport,\n type PosShiftSale,\n} from '../model/commerce-pos-ops'\nimport {\n authorizePosOps,\n defaultPosOpsDeps,\n posOpsBody,\n posOpsCleanId,\n readPosRegister,\n resolvePosCashier,\n type PosOpsDeps,\n type PosOpsStaff,\n} from './pos-ops-gate'\n\n/*==========================================\n * SHIFTS AND THE CASH DRAWER (AGL-3609): `POST /api/commerce/pos-shift`.\n *\n * open a starting float; refused while the register has a shift open\n * cash-event paid in, paid out or a safe drop, with a reason\n * x-report the open shift's figures so far, changing nothing\n * close the counted cash, the Z report frozen onto the shift\n * current the register's open shift, or none\n *\n * ONE OPEN SHIFT PER REGISTER is the register document's `openShiftId`,\n * claimed and released inside the same transaction as the shift it names.\n * Two tablets opening the same till at once both read the register; the\n * transaction that commits second re-reads it, finds the first one's shift\n * and refuses — there is no window in which both stand.\n *=========================================*/\n\n/** The most sales one report reads; past it the report says it is partial. */\nexport const POS_SHIFT_SALES_CEILING = 5000\n/** The largest float or cash movement the drawer records in one entry. */\nexport const POS_CASH_ENTRY_MAX_CENTS = 10_000_000\n\nconst CASH_EVENT_TYPES: ReadonlySet<PosCashEventType> = new Set(['paid_in', 'paid_out', 'drop'])\n\nfunction cents(value: unknown): number | null {\n const number = Number(value)\n if (!Number.isFinite(number)) return null\n const rounded = Math.round(number)\n return rounded >= 0 && rounded <= POS_CASH_ENTRY_MAX_CENTS ? rounded : null\n}\n\n/**\n * The figures for a shift, read from the sales stamped with it and the\n * returns rung against it. Inside a transaction when one is given, so a Z\n * report and the close it freezes see the same sales.\n */\nexport async function readPosShiftReport(\n registerRef: FirebaseFirestore.DocumentReference,\n hostRef: FirebaseFirestore.DocumentReference,\n shiftId: string,\n shift: Pick<PosShift, 'openingFloatCents' | 'cashEvents'>,\n transaction?: FirebaseFirestore.Transaction,\n): Promise<PosShiftReport> {\n const salesQuery = hostRef\n .collection('orders')\n .where('shiftId', '==', shiftId)\n .limit(POS_SHIFT_SALES_CEILING + 1)\n const returnsQuery = registerRef.collection('returns').where('shiftId', '==', shiftId).limit(2000)\n const [salesSnapshot, returnsSnapshot] = transaction\n ? await Promise.all([transaction.get(salesQuery), transaction.get(returnsQuery)])\n : await Promise.all([salesQuery.get(), returnsQuery.get()])\n const sales = salesSnapshot.docs\n .slice(0, POS_SHIFT_SALES_CEILING)\n .map((doc) => (doc.data() ?? {}) as PosShiftSale)\n const refunds: PosShiftRefund[] = returnsSnapshot.docs.map((doc) => {\n const data = (doc.data() ?? {}) as Record<string, any>\n const tenders = Array.isArray(data['tenders']) ? data['tenders'] : []\n return {\n refundedCents: Number(data['refundedCents'] ?? 0),\n tenders: tenders\n .filter((tender: any) => tender?.status !== 'failed')\n .map((tender: any) => ({ method: tender.method, amountCents: Number(tender.amountCents ?? 0) })),\n }\n })\n return computePosShiftReport({\n shift,\n sales,\n refunds,\n truncated: salesSnapshot.docs.length > POS_SHIFT_SALES_CEILING,\n })\n}\n\nfunction shiftOf(data: Record<string, any> | undefined): PosShift | null {\n if (!data) return null\n return {\n ...(data as PosShift),\n cashEvents: Array.isArray(data['cashEvents']) ? (data['cashEvents'] as PosCashEvent[]) : [],\n openingFloatCents: Number(data['openingFloatCents'] ?? 0),\n }\n}\n\ntype Outcome = { status: number; body: Record<string, unknown> }\n\nexport async function handlePosShift(\n deps: PosOpsDeps,\n req: PluginApiRequest,\n): Promise<Outcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n const body = posOpsBody(req)\n const gate = await authorizePosOps(deps, req, body['hostId'])\n if ('error' in gate) return { status: gate.status, body: { error: gate.error } }\n const staff = gate.staff\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const registerId = register.ref.id\n const { cashierId } = await resolvePosCashier(deps, staff, registerId, body['cashierAssertion'])\n const action = String(body['action'] ?? '')\n const firestore = deps.firestore()\n const shifts = register.ref.collection('shifts')\n\n switch (action) {\n case 'current': {\n const openShiftId = String(register.data['openShiftId'] ?? '')\n if (!openShiftId) return { status: 200, body: { shift: null } }\n const shift = shiftOf((await shifts.doc(openShiftId).get()).data())\n return {\n status: 200,\n body: { shift: shift && shift.status === 'open' ? { id: openShiftId, ...shift } : null },\n }\n }\n\n case 'open': {\n const float = cents(body['openingFloatCents'] ?? 0)\n if (float == null) return { status: 400, body: { error: 'Enter the starting cash as an amount.' } }\n const shiftRef = shifts.doc(createResourceUid())\n const now = deps.now()\n const openedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const standing = String(fresh.get('openShiftId') ?? '')\n if (standing) {\n const standingShift = await transaction.get(shifts.doc(standing))\n if (standingShift.exists && standingShift.get('status') === 'open') {\n return {\n status: 409,\n body: {\n error: 'This register already has a shift open. Close it before opening another.',\n shiftId: standing,\n },\n }\n }\n }\n const shift: PosShift = {\n hostId: staff.hostId,\n registerId,\n status: 'open',\n openedBy: cashierId,\n openedByName,\n openedAtMs: now,\n openingFloatCents: float,\n cashEvents: [],\n // Null until the close: the history orders by each (AGL-3680).\n closedAtMs: null,\n countedCashCents: null,\n expectedCashCents: null,\n varianceCents: null,\n netSalesCents: null,\n }\n transaction.create(shiftRef, shift)\n transaction.update(register.ref, { openShiftId: shiftRef.id })\n return { status: 200, body: { shift: { id: shiftRef.id, ...shift } } }\n })\n }\n\n case 'cash-event': {\n const type = String(body['type'] ?? '') as PosCashEventType\n if (!CASH_EVENT_TYPES.has(type)) {\n return { status: 400, body: { error: 'Choose paid in, paid out or a safe drop.' } }\n }\n const amount = cents(body['amountCents'])\n if (!amount) return { status: 400, body: { error: 'Enter an amount above zero.' } }\n const reason = String(body['reason'] ?? '').trim().slice(0, 200)\n if (!reason && type !== 'drop') {\n return { status: 400, body: { error: 'Say what the cash was for.' } }\n }\n const eventId = posOpsCleanId(body['eventId']) || createResourceUid()\n const recorded: Outcome = await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'Open a shift first.' } }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'Open a shift first.' } }\n }\n // A retried tap carries the same id: recorded once, answered twice.\n if (shift.cashEvents.some((event) => event.id === eventId)) {\n return { status: 200, body: { shift: { id: shiftId, ...shift }, replayed: true } }\n }\n const event: PosCashEvent = {\n id: eventId,\n type,\n amountCents: amount,\n reason,\n by: cashierId,\n atMs: deps.now(),\n }\n const cashEvents = [...shift.cashEvents, event]\n transaction.update(shiftRef, { cashEvents })\n return { status: 200, body: { shift: { id: shiftId, ...shift, cashEvents } } }\n })\n // The drawer opens for the cash once it is on the record; a replayed\n // tap kicked it the first time.\n if (recorded.status === 200 && !recorded.body['replayed']) {\n await deps.printer.kickDrawer({\n hostId: staff.hostId,\n registerId,\n reason: type as Exclude<PosCashEventType, 'refund'>,\n causeId: eventId,\n createdBy: cashierId,\n })\n }\n return recorded\n }\n\n case 'print-report': {\n // An X report of the open shift, or the Z report a closed one froze, on\n // the register's receipt printer. `printed: false` tells the register\n // there is no cloud printer, and it prints through the browser instead.\n const requested = posOpsCleanId(body['shiftId']) || String(register.data['openShiftId'] ?? '')\n if (!requested) return { status: 409, body: { error: 'No shift is open on this register.' } }\n const shift = shiftOf((await shifts.doc(requested).get()).data())\n if (!shift) return { status: 404, body: { error: 'Unknown shift' } }\n const closed = shift.status === 'closed' && shift.report\n const report = closed\n ? (shift.report as PosShiftReport)\n : await readPosShiftReport(register.ref, staff.hostRef, requested, shift)\n const host = await staff.hostRef.get()\n const by = closed ? shift.closedByName : shift.openedByName\n const printed = await deps.printer.printReport({\n hostId: staff.hostId,\n registerId,\n shiftId: requested,\n report: posShiftPrintReport({\n title: closed ? 'Z REPORT' : 'X REPORT',\n storeName: String(host.get('displayName') ?? '') || 'Register',\n ...(register.data['name'] ? { registerName: String(register.data['name']) } : {}),\n ...(by ? { subtitle: `${closed ? 'Closed' : 'Opened'} by ${by}` } : {}),\n report,\n ...(closed ? { shift } : {}),\n }),\n createdBy: cashierId,\n attemptKey: posOpsCleanId(body['attemptKey']) || String(deps.now()),\n })\n return { status: 200, body: { printed: printed.jobIds.length > 0, jobIds: printed.jobIds } }\n }\n\n case 'x-report': {\n const shiftId = String(register.data['openShiftId'] ?? '')\n const shift = shiftId ? shiftOf((await shifts.doc(shiftId).get()).data()) : null\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(register.ref, staff.hostRef, shiftId, shift)\n return { status: 200, body: { shiftId, shift: { id: shiftId, ...shift }, report } }\n }\n\n case 'close': {\n const counted = cents(body['countedCashCents'])\n if (counted == null) return { status: 400, body: { error: 'Enter the cash you counted.' } }\n const note = String(body['note'] ?? '').trim().slice(0, 500)\n const expectedShiftId = posOpsCleanId(body['shiftId'])\n const closedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'No shift is open on this register.' } }\n // A stale tablet closing a shift another tablet already closed and\n // reopened must not close the new one with the old count.\n if (expectedShiftId && expectedShiftId !== shiftId) {\n return {\n status: 409,\n body: { error: 'That shift is already closed. Refresh to see the open one.' },\n }\n }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(\n register.ref,\n staff.hostRef,\n shiftId,\n shift,\n transaction,\n )\n const closed: Partial<PosShift> = {\n status: 'closed',\n closedBy: cashierId,\n closedByName,\n closedAtMs: deps.now(),\n countedCashCents: counted,\n expectedCashCents: report.expectedCashCents,\n varianceCents: posCashVarianceCents(counted, report.expectedCashCents),\n netSalesCents: report.netSalesCents,\n report,\n ...(note ? { closingNote: note } : {}),\n }\n transaction.update(shiftRef, closed)\n transaction.update(register.ref, { openShiftId: null })\n return { status: 200, body: { shift: { id: shiftId, ...shift, ...closed }, report } }\n })\n }\n\n default:\n return { status: 400, body: { error: 'Unknown action' } }\n }\n}\n\n/** Whether a register may take a sale or pay cash out, by the site's shift rule. */\nexport async function posShiftForSale(\n staff: Pick<PosOpsStaff, 'settings'>,\n registerRef: FirebaseFirestore.DocumentReference,\n): Promise<{ ok: true; shiftId: string | null } | { ok: false; error: string }> {\n const register = await registerRef.get()\n const shiftId = String(register.get('openShiftId') ?? '') || null\n if (!shiftId && staff.settings.requireOpenShift) {\n return { ok: false, error: 'Open a shift on this register before ringing a sale.' }\n }\n return { ok: true, shiftId }\n}\n\nexport function createPosShiftHandler(deps: () => PosOpsDeps = defaultPosOpsDeps): PluginApiHandler {\n return async (req, res) => {\n try {\n const outcome = await handlePosShift(deps(), req)\n return res.status(outcome.status).json(outcome.body)\n } catch (error) {\n console.error('[pos-shift] failed', error)\n return res.status(500).json({ error: 'The shift could not be updated' })\n }\n }\n}\n\nexport const posShiftHandler = createPosShiftHandler()\n"],"names":["createResourceUid","computePosShiftReport","posShiftPrintReport","posCashVarianceCents","authorizePosOps","defaultPosOpsDeps","posOpsBody","posOpsCleanId","readPosRegister","resolvePosCashier","POS_SHIFT_SALES_CEILING","POS_CASH_ENTRY_MAX_CENTS","CASH_EVENT_TYPES","Set","cents","value","number","Number","isFinite","rounded","Math","round","readPosShiftReport","registerRef","hostRef","shiftId","shift","transaction","salesQuery","collection","where","limit","returnsQuery","salesSnapshot","returnsSnapshot","Promise","all","get","sales","docs","slice","map","doc","data","refunds","tenders","Array","isArray","refundedCents","filter","tender","status","method","amountCents","truncated","length","shiftOf","cashEvents","openingFloatCents","handlePosShift","deps","req","body","error","gate","staff","register","registerId","ref","id","cashierId","action","String","firestore","shifts","openShiftId","float","shiftRef","now","openedByName","memberName","runTransaction","fresh","standing","standingShift","exists","hostId","openedBy","openedAtMs","closedAtMs","countedCashCents","expectedCashCents","varianceCents","netSalesCents","create","update","type","has","amount","reason","trim","eventId","recorded","some","event","replayed","by","atMs","printer","kickDrawer","causeId","createdBy","host","requested","closed","report","closedByName","printed","printReport","title","storeName","registerName","subtitle","attemptKey","jobIds","counted","note","expectedShiftId","closedBy","closingNote","posShiftForSale","settings","requireOpenShift","ok","createPosShiftHandler","res","outcome","json","console","posShiftHandler"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-shift.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport {\n computePosShiftReport,\n posShiftPrintReport,\n posCashVarianceCents,\n type PosCashEvent,\n type PosCashEventType,\n type PosShift,\n type PosShiftRefund,\n type PosShiftReport,\n type PosShiftSale,\n} from '../model/commerce-pos-ops'\nimport {\n authorizePosOps,\n defaultPosOpsDeps,\n posOpsBody,\n posOpsCleanId,\n readPosRegister,\n resolvePosCashier,\n type PosOpsDeps,\n type PosOpsStaff,\n} from './pos-ops-gate'\nimport { holdsPosPermission, posPermissionRefusal } from '../model/plugin-permissions'\n\n/*==========================================\n * SHIFTS AND THE CASH DRAWER (AGL-3609): `POST /api/commerce/pos-shift`.\n *\n * open a starting float; refused while the register has a shift open\n * cash-event paid in, paid out or a safe drop, with a reason\n * x-report the open shift's figures so far, changing nothing\n * close the counted cash, the Z report frozen onto the shift\n * current the register's open shift, or none\n *\n * ONE OPEN SHIFT PER REGISTER is the register document's `openShiftId`,\n * claimed and released inside the same transaction as the shift it names.\n * Two tablets opening the same till at once both read the register; the\n * transaction that commits second re-reads it, finds the first one's shift\n * and refuses — there is no window in which both stand.\n *=========================================*/\n\n/** The most sales one report reads; past it the report says it is partial. */\nexport const POS_SHIFT_SALES_CEILING = 5000\n/** The largest float or cash movement the drawer records in one entry. */\nexport const POS_CASH_ENTRY_MAX_CENTS = 10_000_000\n\nconst CASH_EVENT_TYPES: ReadonlySet<PosCashEventType> = new Set(['paid_in', 'paid_out', 'drop'])\n\nfunction cents(value: unknown): number | null {\n const number = Number(value)\n if (!Number.isFinite(number)) return null\n const rounded = Math.round(number)\n return rounded >= 0 && rounded <= POS_CASH_ENTRY_MAX_CENTS ? rounded : null\n}\n\n/**\n * The figures for a shift, read from the sales stamped with it and the\n * returns rung against it. Inside a transaction when one is given, so a Z\n * report and the close it freezes see the same sales.\n */\nexport async function readPosShiftReport(\n registerRef: FirebaseFirestore.DocumentReference,\n hostRef: FirebaseFirestore.DocumentReference,\n shiftId: string,\n shift: Pick<PosShift, 'openingFloatCents' | 'cashEvents'>,\n transaction?: FirebaseFirestore.Transaction,\n): Promise<PosShiftReport> {\n const salesQuery = hostRef\n .collection('orders')\n .where('shiftId', '==', shiftId)\n .limit(POS_SHIFT_SALES_CEILING + 1)\n const returnsQuery = registerRef.collection('returns').where('shiftId', '==', shiftId).limit(2000)\n const [salesSnapshot, returnsSnapshot] = transaction\n ? await Promise.all([transaction.get(salesQuery), transaction.get(returnsQuery)])\n : await Promise.all([salesQuery.get(), returnsQuery.get()])\n const sales = salesSnapshot.docs\n .slice(0, POS_SHIFT_SALES_CEILING)\n .map((doc) => (doc.data() ?? {}) as PosShiftSale)\n const refunds: PosShiftRefund[] = returnsSnapshot.docs.map((doc) => {\n const data = (doc.data() ?? {}) as Record<string, any>\n const tenders = Array.isArray(data['tenders']) ? data['tenders'] : []\n return {\n refundedCents: Number(data['refundedCents'] ?? 0),\n tenders: tenders\n .filter((tender: any) => tender?.status !== 'failed')\n .map((tender: any) => ({ method: tender.method, amountCents: Number(tender.amountCents ?? 0) })),\n }\n })\n return computePosShiftReport({\n shift,\n sales,\n refunds,\n truncated: salesSnapshot.docs.length > POS_SHIFT_SALES_CEILING,\n })\n}\n\nfunction shiftOf(data: Record<string, any> | undefined): PosShift | null {\n if (!data) return null\n return {\n ...(data as PosShift),\n cashEvents: Array.isArray(data['cashEvents']) ? (data['cashEvents'] as PosCashEvent[]) : [],\n openingFloatCents: Number(data['openingFloatCents'] ?? 0),\n }\n}\n\ntype Outcome = { status: number; body: Record<string, unknown> }\n\nexport async function handlePosShift(\n deps: PosOpsDeps,\n req: PluginApiRequest,\n): Promise<Outcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n const body = posOpsBody(req)\n const gate = await authorizePosOps(deps, req, body['hostId'])\n if ('error' in gate) return { status: gate.status, body: { error: gate.error } }\n const staff = gate.staff\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const registerId = register.ref.id\n const { cashierId } = await resolvePosCashier(deps, staff, registerId, body['cashierAssertion'])\n const action = String(body['action'] ?? '')\n // Opening and closing a shift, cash in and out and the shift's reports are\n // the cash drawer's (AGL-4047); which shift is open is the register's.\n if (action !== 'current' && !holdsPosPermission(staff.membership.permissions, 'posCashDrawer')) {\n return { status: 403, body: { error: posPermissionRefusal('posCashDrawer') } }\n }\n const firestore = deps.firestore()\n const shifts = register.ref.collection('shifts')\n\n switch (action) {\n case 'current': {\n const openShiftId = String(register.data['openShiftId'] ?? '')\n if (!openShiftId) return { status: 200, body: { shift: null } }\n const shift = shiftOf((await shifts.doc(openShiftId).get()).data())\n return {\n status: 200,\n body: { shift: shift && shift.status === 'open' ? { id: openShiftId, ...shift } : null },\n }\n }\n\n case 'open': {\n const float = cents(body['openingFloatCents'] ?? 0)\n if (float == null) return { status: 400, body: { error: 'Enter the starting cash as an amount.' } }\n const shiftRef = shifts.doc(createResourceUid())\n const now = deps.now()\n const openedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const standing = String(fresh.get('openShiftId') ?? '')\n if (standing) {\n const standingShift = await transaction.get(shifts.doc(standing))\n if (standingShift.exists && standingShift.get('status') === 'open') {\n return {\n status: 409,\n body: {\n error: 'This register already has a shift open. Close it before opening another.',\n shiftId: standing,\n },\n }\n }\n }\n const shift: PosShift = {\n hostId: staff.hostId,\n registerId,\n status: 'open',\n openedBy: cashierId,\n openedByName,\n openedAtMs: now,\n openingFloatCents: float,\n cashEvents: [],\n // Null until the close: the history orders by each (AGL-3680).\n closedAtMs: null,\n countedCashCents: null,\n expectedCashCents: null,\n varianceCents: null,\n netSalesCents: null,\n }\n transaction.create(shiftRef, shift)\n transaction.update(register.ref, { openShiftId: shiftRef.id })\n return { status: 200, body: { shift: { id: shiftRef.id, ...shift } } }\n })\n }\n\n case 'cash-event': {\n const type = String(body['type'] ?? '') as PosCashEventType\n if (!CASH_EVENT_TYPES.has(type)) {\n return { status: 400, body: { error: 'Choose paid in, paid out or a safe drop.' } }\n }\n const amount = cents(body['amountCents'])\n if (!amount) return { status: 400, body: { error: 'Enter an amount above zero.' } }\n const reason = String(body['reason'] ?? '').trim().slice(0, 200)\n if (!reason && type !== 'drop') {\n return { status: 400, body: { error: 'Say what the cash was for.' } }\n }\n const eventId = posOpsCleanId(body['eventId']) || createResourceUid()\n const recorded: Outcome = await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'Open a shift first.' } }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'Open a shift first.' } }\n }\n // A retried tap carries the same id: recorded once, answered twice.\n if (shift.cashEvents.some((event) => event.id === eventId)) {\n return { status: 200, body: { shift: { id: shiftId, ...shift }, replayed: true } }\n }\n const event: PosCashEvent = {\n id: eventId,\n type,\n amountCents: amount,\n reason,\n by: cashierId,\n atMs: deps.now(),\n }\n const cashEvents = [...shift.cashEvents, event]\n transaction.update(shiftRef, { cashEvents })\n return { status: 200, body: { shift: { id: shiftId, ...shift, cashEvents } } }\n })\n // The drawer opens for the cash once it is on the record; a replayed\n // tap kicked it the first time.\n if (recorded.status === 200 && !recorded.body['replayed']) {\n await deps.printer.kickDrawer({\n hostId: staff.hostId,\n registerId,\n reason: type as Exclude<PosCashEventType, 'refund'>,\n causeId: eventId,\n createdBy: cashierId,\n })\n }\n return recorded\n }\n\n case 'print-report': {\n // An X report of the open shift, or the Z report a closed one froze, on\n // the register's receipt printer. `printed: false` tells the register\n // there is no cloud printer, and it prints through the browser instead.\n const requested = posOpsCleanId(body['shiftId']) || String(register.data['openShiftId'] ?? '')\n if (!requested) return { status: 409, body: { error: 'No shift is open on this register.' } }\n const shift = shiftOf((await shifts.doc(requested).get()).data())\n if (!shift) return { status: 404, body: { error: 'Unknown shift' } }\n const closed = shift.status === 'closed' && shift.report\n const report = closed\n ? (shift.report as PosShiftReport)\n : await readPosShiftReport(register.ref, staff.hostRef, requested, shift)\n const host = await staff.hostRef.get()\n const by = closed ? shift.closedByName : shift.openedByName\n const printed = await deps.printer.printReport({\n hostId: staff.hostId,\n registerId,\n shiftId: requested,\n report: posShiftPrintReport({\n title: closed ? 'Z REPORT' : 'X REPORT',\n storeName: String(host.get('displayName') ?? '') || 'Register',\n ...(register.data['name'] ? { registerName: String(register.data['name']) } : {}),\n ...(by ? { subtitle: `${closed ? 'Closed' : 'Opened'} by ${by}` } : {}),\n report,\n ...(closed ? { shift } : {}),\n }),\n createdBy: cashierId,\n attemptKey: posOpsCleanId(body['attemptKey']) || String(deps.now()),\n })\n return { status: 200, body: { printed: printed.jobIds.length > 0, jobIds: printed.jobIds } }\n }\n\n case 'x-report': {\n const shiftId = String(register.data['openShiftId'] ?? '')\n const shift = shiftId ? shiftOf((await shifts.doc(shiftId).get()).data()) : null\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(register.ref, staff.hostRef, shiftId, shift)\n return { status: 200, body: { shiftId, shift: { id: shiftId, ...shift }, report } }\n }\n\n case 'close': {\n const counted = cents(body['countedCashCents'])\n if (counted == null) return { status: 400, body: { error: 'Enter the cash you counted.' } }\n const note = String(body['note'] ?? '').trim().slice(0, 500)\n const expectedShiftId = posOpsCleanId(body['shiftId'])\n const closedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'No shift is open on this register.' } }\n // A stale tablet closing a shift another tablet already closed and\n // reopened must not close the new one with the old count.\n if (expectedShiftId && expectedShiftId !== shiftId) {\n return {\n status: 409,\n body: { error: 'That shift is already closed. Refresh to see the open one.' },\n }\n }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(\n register.ref,\n staff.hostRef,\n shiftId,\n shift,\n transaction,\n )\n const closed: Partial<PosShift> = {\n status: 'closed',\n closedBy: cashierId,\n closedByName,\n closedAtMs: deps.now(),\n countedCashCents: counted,\n expectedCashCents: report.expectedCashCents,\n varianceCents: posCashVarianceCents(counted, report.expectedCashCents),\n netSalesCents: report.netSalesCents,\n report,\n ...(note ? { closingNote: note } : {}),\n }\n transaction.update(shiftRef, closed)\n transaction.update(register.ref, { openShiftId: null })\n return { status: 200, body: { shift: { id: shiftId, ...shift, ...closed }, report } }\n })\n }\n\n default:\n return { status: 400, body: { error: 'Unknown action' } }\n }\n}\n\n/** Whether a register may take a sale or pay cash out, by the site's shift rule. */\nexport async function posShiftForSale(\n staff: Pick<PosOpsStaff, 'settings'>,\n registerRef: FirebaseFirestore.DocumentReference,\n): Promise<{ ok: true; shiftId: string | null } | { ok: false; error: string }> {\n const register = await registerRef.get()\n const shiftId = String(register.get('openShiftId') ?? '') || null\n if (!shiftId && staff.settings.requireOpenShift) {\n return { ok: false, error: 'Open a shift on this register before ringing a sale.' }\n }\n return { ok: true, shiftId }\n}\n\nexport function createPosShiftHandler(deps: () => PosOpsDeps = defaultPosOpsDeps): PluginApiHandler {\n return async (req, res) => {\n try {\n const outcome = await handlePosShift(deps(), req)\n return res.status(outcome.status).json(outcome.body)\n } catch (error) {\n console.error('[pos-shift] failed', error)\n return res.status(500).json({ error: 'The shift could not be updated' })\n }\n }\n}\n\nexport const posShiftHandler = createPosShiftHandler()\n"],"names":["createResourceUid","computePosShiftReport","posShiftPrintReport","posCashVarianceCents","authorizePosOps","defaultPosOpsDeps","posOpsBody","posOpsCleanId","readPosRegister","resolvePosCashier","holdsPosPermission","posPermissionRefusal","POS_SHIFT_SALES_CEILING","POS_CASH_ENTRY_MAX_CENTS","CASH_EVENT_TYPES","Set","cents","value","number","Number","isFinite","rounded","Math","round","readPosShiftReport","registerRef","hostRef","shiftId","shift","transaction","salesQuery","collection","where","limit","returnsQuery","salesSnapshot","returnsSnapshot","Promise","all","get","sales","docs","slice","map","doc","data","refunds","tenders","Array","isArray","refundedCents","filter","tender","status","method","amountCents","truncated","length","shiftOf","cashEvents","openingFloatCents","handlePosShift","deps","req","body","error","gate","staff","register","registerId","ref","id","cashierId","action","String","membership","permissions","firestore","shifts","openShiftId","float","shiftRef","now","openedByName","memberName","runTransaction","fresh","standing","standingShift","exists","hostId","openedBy","openedAtMs","closedAtMs","countedCashCents","expectedCashCents","varianceCents","netSalesCents","create","update","type","has","amount","reason","trim","eventId","recorded","some","event","replayed","by","atMs","printer","kickDrawer","causeId","createdBy","host","requested","closed","report","closedByName","printed","printReport","title","storeName","registerName","subtitle","attemptKey","jobIds","counted","note","expectedShiftId","closedBy","closingNote","posShiftForSale","settings","requireOpenShift","ok","createPosShiftHandler","res","outcome","json","console","posShiftHandler"],"mappings":"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"}
@@ -15,6 +15,8 @@
15
15
  * limitations under the License.
16
16
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  import { POS_PIN_LOCKOUT_MS, POS_PIN_MAX_ATTEMPTS, posPinProblem } from "../model/commerce-pos-ops.js";
18
+ import { holdsPosPermission } from "../model/plugin-permissions.js";
19
+ import { commerceWorkspacePermissionRefusal } from "../model/workspace-site-permissions.js";
18
20
  import { hashMemberPassword, verifyMemberPassword } from "./membership.js";
19
21
  import { authorizePosOps, defaultPosOpsDeps, isPosManager, mayWorkRegister, mintPosAssertion, posOpsBody, posOpsCleanId, readPosAssertion, readPosRegister, resolvePosStaffAssertion } from "./pos-ops-gate.js";
20
22
  function pinRef(hostRef, uid) {
@@ -60,22 +62,19 @@ export async function handlePosStaffPin(deps, req) {
60
62
  return {
61
63
  status: 403,
62
64
  body: {
63
- error: "Only a workspace admin can set another member's PIN."
65
+ error: commerceWorkspacePermissionRefusal('commerce.posApprovals')
64
66
  }
65
67
  };
66
68
  }
67
- if (target !== staff.uid) {
68
- var _host_get;
69
- const host = await staff.hostRef.get();
70
- const role = ((_host_get = host.get('memberRoles')) != null ? _host_get : {})[target];
71
- if (role !== 'admin' && role !== 'editor') {
72
- return {
73
- status: 404,
74
- body: {
75
- error: 'That member does not work on this site.'
76
- }
77
- };
78
- }
69
+ // Whoever works this site's register, by permission, not by site role
70
+ // (AGL-4047): the gate every PIN use re-asks.
71
+ if (target !== staff.uid && !mayWorkRegister(await deps.membership(target, staff.hostId))) {
72
+ return {
73
+ status: 404,
74
+ body: {
75
+ error: 'That member does not work on this site.'
76
+ }
77
+ };
79
78
  }
80
79
  if (action === 'clear') {
81
80
  await pinRef(staff.hostRef, target).delete();
@@ -112,11 +111,15 @@ export async function handlePosStaffPin(deps, req) {
112
111
  }
113
112
  case 'roster':
114
113
  {
115
- var _host_get1;
116
- const host = await staff.hostRef.get();
117
- const roles = (_host_get1 = host.get('memberRoles')) != null ? _host_get1 : {};
118
114
  const pins = await staff.hostRef.collection('posStaffPins').limit(200).get();
119
- const staffWithPins = pins.docs.map((doc)=>doc.id).filter((uid)=>roles[uid] === 'admin' || roles[uid] === 'editor');
115
+ // Those who may still ring a sale here, by permission (AGL-4047): the
116
+ // switch-in below refuses anybody else.
117
+ const holders = pins.docs.map((doc)=>String(doc.id));
118
+ const sells = await Promise.all(holders.map(async (uid)=>{
119
+ const membership = await deps.membership(uid, staff.hostId);
120
+ return mayWorkRegister(membership) && holdsPosPermission(membership.permissions, 'posSell');
121
+ }));
122
+ const staffWithPins = holders.filter((_, index)=>sells[index]);
120
123
  const members = await Promise.all(staffWithPins.map(async (uid)=>({
121
124
  uid,
122
125
  name: await deps.memberName(uid)
@@ -131,7 +134,7 @@ export async function handlePosStaffPin(deps, req) {
131
134
  }
132
135
  case 'verify':
133
136
  {
134
- var _body_pin1, _host_get2;
137
+ var _body_pin1;
135
138
  const register = await readPosRegister(staff, body['registerId']);
136
139
  if ('error' in register) return {
137
140
  status: register.status,
@@ -198,7 +201,7 @@ export async function handlePosStaffPin(deps, req) {
198
201
  return {
199
202
  status: 423,
200
203
  body: {
201
- error: 'Too many wrong PINs. Try again later, or ask a workspace admin to reset it.',
204
+ error: 'Too many wrong PINs. Try again later, or ask someone who can approve refunds over the register’s limit to reset it.',
202
205
  lockedUntilMs: claim.lockedUntilMs
203
206
  }
204
207
  };
@@ -213,7 +216,7 @@ export async function handlePosStaffPin(deps, req) {
213
216
  return {
214
217
  status: 423,
215
218
  body: {
216
- error: 'Too many wrong PINs. Try again later, or ask a workspace admin to reset it.',
219
+ error: 'Too many wrong PINs. Try again later, or ask someone who can approve refunds over the register’s limit to reset it.',
217
220
  lockedUntilMs: now + POS_PIN_LOCKOUT_MS
218
221
  }
219
222
  };
@@ -231,16 +234,18 @@ export async function handlePosStaffPin(deps, req) {
231
234
  lockedUntilMs: null,
232
235
  lastUsedAtMs: now
233
236
  });
234
- // The PIN is right; whether it still buys anything is today's role.
237
+ // The PIN is right; whether it still buys anything is today's
238
+ // permissions, and for a manager's approval today's role.
235
239
  const host = await staff.hostRef.get();
236
- const role = ((_host_get2 = host.get('memberRoles')) != null ? _host_get2 : {})[memberUid];
237
240
  const membership = await deps.membership(memberUid, staff.hostId);
238
- const allowed = purpose === 'manager' ? isPosManager(role, membership) : mayWorkRegister(role, membership);
241
+ // A cashier switched in by PIN rings sales, so they need the register's
242
+ // sell key too (AGL-4047), not only the register.
243
+ const allowed = purpose === 'manager' ? isPosManager(host, memberUid, membership) : mayWorkRegister(membership) && holdsPosPermission(membership.permissions, 'posSell');
239
244
  if (!allowed) {
240
245
  return {
241
246
  status: 403,
242
247
  body: {
243
- error: purpose === 'manager' ? 'Only a workspace admin can approve this.' : 'That member cannot use the register.'
248
+ error: purpose === 'manager' ? commerceWorkspacePermissionRefusal('commerce.posApprovals') : 'That member cannot use the register.'
244
249
  }
245
250
  };
246
251
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-staff-pin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport {\n POS_PIN_LOCKOUT_MS,\n POS_PIN_MAX_ATTEMPTS,\n posPinProblem,\n} from '../model/commerce-pos-ops'\nimport { hashMemberPassword, verifyMemberPassword } from './membership'\nimport {\n authorizePosOps,\n defaultPosOpsDeps,\n isPosManager,\n mayWorkRegister,\n mintPosAssertion,\n posOpsBody,\n posOpsCleanId,\n readPosAssertion,\n readPosRegister,\n resolvePosStaffAssertion,\n type PosAssertionPurpose,\n type PosOpsDeps,\n} from './pos-ops-gate'\n\n/*==========================================\n * STAFF PINS (AGL-3609): `POST /api/commerce/pos-staff-pin`.\n *\n * status whether the signed-in member has a PIN on this site\n * set a member sets their own PIN; a workspace admin may set or\n * reset anyone's, which also lifts a lockout\n * clear the same, removing it\n * roster who can switch in at this register: members with a PIN\n * verify a member's PIN at a register → a signed, short-lived\n * assertion that they are at it (`cashier`), or that a manager\n * approved one action (`manager`)\n * refresh a still-valid cashier assertion → a fresh one, re-checked\n *\n * The hash is scrypt with a per-PIN salt — the membership password helper —\n * at `hosts/{hostId}/posStaffPins/{uid}`, a collection no client may read or\n * write (the rules deny it outright, staff included).\n *\n * LOCKOUT is counted BEFORE the PIN is checked: each attempt is claimed in a\n * transaction that refuses once {@link POS_PIN_MAX_ATTEMPTS} have failed, so\n * a burst of parallel guesses gets five tries in the lockout window, not five\n * each. A right PIN clears the count.\n *=========================================*/\n\ntype Outcome = { status: number; body: Record<string, unknown> }\n\nfunction pinRef(hostRef: FirebaseFirestore.DocumentReference, uid: string) {\n return hostRef.collection('posStaffPins').doc(uid)\n}\n\nexport async function handlePosStaffPin(deps: PosOpsDeps, req: PluginApiRequest): Promise<Outcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n const body = posOpsBody(req)\n const gate = await authorizePosOps(deps, req, body['hostId'])\n if ('error' in gate) return { status: gate.status, body: { error: gate.error } }\n const staff = gate.staff\n const action = String(body['action'] ?? '')\n const firestore = deps.firestore()\n\n switch (action) {\n case 'status': {\n const snapshot = await pinRef(staff.hostRef, staff.uid).get()\n return {\n status: 200,\n body: {\n hasPin: snapshot.exists && Boolean(snapshot.get('pinScrypt')),\n isManager: staff.isManager,\n },\n }\n }\n\n case 'set':\n case 'clear': {\n const target = posOpsCleanId(body['memberUid']) || staff.uid\n if (target !== staff.uid && !staff.isManager) {\n return {\n status: 403,\n body: { error: \"Only a workspace admin can set another member's PIN.\" },\n }\n }\n if (target !== staff.uid) {\n const host = await staff.hostRef.get()\n const role = (host.get('memberRoles') ?? {})[target]\n if (role !== 'admin' && role !== 'editor') {\n return { status: 404, body: { error: 'That member does not work on this site.' } }\n }\n }\n if (action === 'clear') {\n await pinRef(staff.hostRef, target).delete()\n return { status: 200, body: { ok: true, hasPin: false } }\n }\n const pin = String(body['pin'] ?? '')\n const problem = posPinProblem(pin)\n if (problem) return { status: 400, body: { error: problem } }\n await pinRef(staff.hostRef, target).set({\n pinScrypt: hashMemberPassword(pin),\n setAtMs: deps.now(),\n setBy: staff.uid,\n failedAttempts: 0,\n lockedUntilMs: null,\n })\n return { status: 200, body: { ok: true, hasPin: true } }\n }\n\n case 'roster': {\n const host = await staff.hostRef.get()\n const roles = (host.get('memberRoles') ?? {}) as Record<string, string>\n const pins = await staff.hostRef.collection('posStaffPins').limit(200).get()\n const staffWithPins = pins.docs\n .map((doc) => doc.id)\n .filter((uid) => roles[uid] === 'admin' || roles[uid] === 'editor')\n const members = await Promise.all(\n staffWithPins.map(async (uid) => ({ uid, name: await deps.memberName(uid) })),\n )\n members.sort((a, b) => a.name.localeCompare(b.name))\n return { status: 200, body: { members } }\n }\n\n case 'verify': {\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const purpose: PosAssertionPurpose = body['purpose'] === 'manager' ? 'manager' : 'cashier'\n const memberUid = posOpsCleanId(body['memberUid'])\n const pin = String(body['pin'] ?? '')\n if (!memberUid || !/^\\d{4,6}$/.test(pin)) {\n return { status: 400, body: { error: 'Choose your name and enter your PIN.' } }\n }\n const ref = pinRef(staff.hostRef, memberUid)\n const now = deps.now()\n // Claim the attempt first; the PIN is checked only inside the budget.\n const claim = await firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(ref)\n if (!snapshot.exists || !snapshot.get('pinScrypt')) return { kind: 'none' as const }\n const lockedUntilMs = Number(snapshot.get('lockedUntilMs') ?? 0)\n if (lockedUntilMs > now) return { kind: 'locked' as const, lockedUntilMs }\n const failed = lockedUntilMs ? 0 : Number(snapshot.get('failedAttempts') ?? 0)\n if (failed >= POS_PIN_MAX_ATTEMPTS) {\n const until = now + POS_PIN_LOCKOUT_MS\n transaction.update(ref, { lockedUntilMs: until, failedAttempts: 0 })\n return { kind: 'locked' as const, lockedUntilMs: until }\n }\n transaction.update(ref, { failedAttempts: failed + 1, lockedUntilMs: null })\n return { kind: 'claimed' as const, hash: String(snapshot.get('pinScrypt')), attempt: failed + 1 }\n })\n if (claim.kind === 'none') {\n return { status: 404, body: { error: 'That member has not set a PIN on this site.' } }\n }\n if (claim.kind === 'locked') {\n return {\n status: 423,\n body: {\n error: 'Too many wrong PINs. Try again later, or ask a workspace admin to reset it.',\n lockedUntilMs: claim.lockedUntilMs,\n },\n }\n }\n if (!verifyMemberPassword(pin, claim.hash)) {\n const left = POS_PIN_MAX_ATTEMPTS - claim.attempt\n if (left <= 0) {\n await ref.update({ lockedUntilMs: now + POS_PIN_LOCKOUT_MS, failedAttempts: 0 })\n return {\n status: 423,\n body: {\n error: 'Too many wrong PINs. Try again later, or ask a workspace admin to reset it.',\n lockedUntilMs: now + POS_PIN_LOCKOUT_MS,\n },\n }\n }\n return {\n status: 401,\n body: { error: `That PIN is not right. ${left} ${left === 1 ? 'try' : 'tries'} left.`, attemptsLeft: left },\n }\n }\n await ref.update({ failedAttempts: 0, lockedUntilMs: null, lastUsedAtMs: now })\n // The PIN is right; whether it still buys anything is today's role.\n const host = await staff.hostRef.get()\n const role = (host.get('memberRoles') ?? {})[memberUid]\n const membership = await deps.membership(memberUid, staff.hostId)\n const allowed =\n purpose === 'manager' ? isPosManager(role, membership) : mayWorkRegister(role, membership)\n if (!allowed) {\n return {\n status: 403,\n body: {\n error:\n purpose === 'manager'\n ? 'Only a workspace admin can approve this.'\n : 'That member cannot use the register.',\n },\n }\n }\n let minted: { token: string; expiresAtMs: number }\n try {\n minted = mintPosAssertion(deps, {\n hostId: staff.hostId,\n registerId: register.ref.id,\n memberUid,\n purpose,\n })\n } catch {\n return { status: 501, body: { error: 'Staff PINs are not configured on this server.' } }\n }\n return {\n status: 200,\n body: {\n assertion: minted.token,\n expiresAtMs: minted.expiresAtMs,\n memberUid,\n name: await deps.memberName(memberUid),\n purpose,\n },\n }\n }\n\n case 'refresh': {\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const expected = { hostId: staff.hostId, registerId: register.ref.id, purpose: 'cashier' as const }\n if (!readPosAssertion(deps, body['assertion'], expected)) {\n return { status: 401, body: { error: 'Enter your PIN again.' } }\n }\n const memberUid = await resolvePosStaffAssertion(deps, staff.hostRef, body['assertion'], expected)\n if (!memberUid) return { status: 403, body: { error: 'That member cannot use the register.' } }\n const minted = mintPosAssertion(deps, { ...expected, memberUid })\n return { status: 200, body: { assertion: minted.token, expiresAtMs: minted.expiresAtMs, memberUid } }\n }\n\n default:\n return { status: 400, body: { error: 'Unknown action' } }\n }\n}\n\nexport function createPosStaffPinHandler(deps: () => PosOpsDeps = defaultPosOpsDeps): PluginApiHandler {\n return async (req, res) => {\n try {\n const outcome = await handlePosStaffPin(deps(), req)\n return res.status(outcome.status).json(outcome.body)\n } catch (error) {\n console.error('[pos-staff-pin] failed', error)\n return res.status(500).json({ error: 'The PIN could not be checked' })\n }\n }\n}\n\nexport const posStaffPinHandler = createPosStaffPinHandler()\n"],"names":["POS_PIN_LOCKOUT_MS","POS_PIN_MAX_ATTEMPTS","posPinProblem","hashMemberPassword","verifyMemberPassword","authorizePosOps","defaultPosOpsDeps","isPosManager","mayWorkRegister","mintPosAssertion","posOpsBody","posOpsCleanId","readPosAssertion","readPosRegister","resolvePosStaffAssertion","pinRef","hostRef","uid","collection","doc","handlePosStaffPin","deps","req","body","method","status","error","gate","staff","action","String","firestore","snapshot","get","hasPin","exists","Boolean","isManager","target","host","role","delete","ok","pin","problem","set","pinScrypt","setAtMs","now","setBy","failedAttempts","lockedUntilMs","roles","pins","limit","staffWithPins","docs","map","id","filter","members","Promise","all","name","memberName","sort","a","b","localeCompare","register","purpose","memberUid","test","ref","claim","runTransaction","transaction","kind","Number","failed","until","update","hash","attempt","left","attemptsLeft","lastUsedAtMs","membership","hostId","allowed","minted","registerId","assertion","token","expiresAtMs","expected","createPosStaffPinHandler","res","outcome","json","console","posStaffPinHandler"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-staff-pin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport {\n POS_PIN_LOCKOUT_MS,\n POS_PIN_MAX_ATTEMPTS,\n posPinProblem,\n} from '../model/commerce-pos-ops'\nimport { holdsPosPermission } from '../model/plugin-permissions'\nimport { commerceWorkspacePermissionRefusal } from '../model/workspace-site-permissions'\nimport { hashMemberPassword, verifyMemberPassword } from './membership'\nimport {\n authorizePosOps,\n defaultPosOpsDeps,\n isPosManager,\n mayWorkRegister,\n mintPosAssertion,\n posOpsBody,\n posOpsCleanId,\n readPosAssertion,\n readPosRegister,\n resolvePosStaffAssertion,\n type PosAssertionPurpose,\n type PosOpsDeps,\n} from './pos-ops-gate'\n\n/*==========================================\n * STAFF PINS (AGL-3609): `POST /api/commerce/pos-staff-pin`.\n *\n * status whether the signed-in member has a PIN on this site\n * set a member sets their own PIN; whoever holds \"Approve refunds over\n * the register's limit\" (AGL-4047) may set or\n * reset anyone's, which also lifts a lockout\n * clear the same, removing it\n * roster who can switch in at this register: members with a PIN\n * verify a member's PIN at a register → a signed, short-lived\n * assertion that they are at it (`cashier`), or that a manager\n * approved one action (`manager`)\n * refresh a still-valid cashier assertion → a fresh one, re-checked\n *\n * The hash is scrypt with a per-PIN salt — the membership password helper —\n * at `hosts/{hostId}/posStaffPins/{uid}`, a collection no client may read or\n * write (the rules deny it outright, staff included).\n *\n * LOCKOUT is counted BEFORE the PIN is checked: each attempt is claimed in a\n * transaction that refuses once {@link POS_PIN_MAX_ATTEMPTS} have failed, so\n * a burst of parallel guesses gets five tries in the lockout window, not five\n * each. A right PIN clears the count.\n *=========================================*/\n\ntype Outcome = { status: number; body: Record<string, unknown> }\n\nfunction pinRef(hostRef: FirebaseFirestore.DocumentReference, uid: string) {\n return hostRef.collection('posStaffPins').doc(uid)\n}\n\nexport async function handlePosStaffPin(deps: PosOpsDeps, req: PluginApiRequest): Promise<Outcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n const body = posOpsBody(req)\n const gate = await authorizePosOps(deps, req, body['hostId'])\n if ('error' in gate) return { status: gate.status, body: { error: gate.error } }\n const staff = gate.staff\n const action = String(body['action'] ?? '')\n const firestore = deps.firestore()\n\n switch (action) {\n case 'status': {\n const snapshot = await pinRef(staff.hostRef, staff.uid).get()\n return {\n status: 200,\n body: {\n hasPin: snapshot.exists && Boolean(snapshot.get('pinScrypt')),\n isManager: staff.isManager,\n },\n }\n }\n\n case 'set':\n case 'clear': {\n const target = posOpsCleanId(body['memberUid']) || staff.uid\n if (target !== staff.uid && !staff.isManager) {\n return {\n status: 403,\n body: { error: commerceWorkspacePermissionRefusal('commerce.posApprovals') },\n }\n }\n // Whoever works this site's register, by permission, not by site role\n // (AGL-4047): the gate every PIN use re-asks.\n if (target !== staff.uid && !mayWorkRegister(await deps.membership(target, staff.hostId))) {\n return { status: 404, body: { error: 'That member does not work on this site.' } }\n }\n if (action === 'clear') {\n await pinRef(staff.hostRef, target).delete()\n return { status: 200, body: { ok: true, hasPin: false } }\n }\n const pin = String(body['pin'] ?? '')\n const problem = posPinProblem(pin)\n if (problem) return { status: 400, body: { error: problem } }\n await pinRef(staff.hostRef, target).set({\n pinScrypt: hashMemberPassword(pin),\n setAtMs: deps.now(),\n setBy: staff.uid,\n failedAttempts: 0,\n lockedUntilMs: null,\n })\n return { status: 200, body: { ok: true, hasPin: true } }\n }\n\n case 'roster': {\n const pins = await staff.hostRef.collection('posStaffPins').limit(200).get()\n // Those who may still ring a sale here, by permission (AGL-4047): the\n // switch-in below refuses anybody else.\n const holders = pins.docs.map((doc) => String(doc.id))\n const sells = await Promise.all(\n holders.map(async (uid) => {\n const membership = await deps.membership(uid, staff.hostId)\n return mayWorkRegister(membership) && holdsPosPermission(membership.permissions, 'posSell')\n }),\n )\n const staffWithPins = holders.filter((_, index) => sells[index])\n const members = await Promise.all(\n staffWithPins.map(async (uid) => ({ uid, name: await deps.memberName(uid) })),\n )\n members.sort((a, b) => a.name.localeCompare(b.name))\n return { status: 200, body: { members } }\n }\n\n case 'verify': {\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const purpose: PosAssertionPurpose = body['purpose'] === 'manager' ? 'manager' : 'cashier'\n const memberUid = posOpsCleanId(body['memberUid'])\n const pin = String(body['pin'] ?? '')\n if (!memberUid || !/^\\d{4,6}$/.test(pin)) {\n return { status: 400, body: { error: 'Choose your name and enter your PIN.' } }\n }\n const ref = pinRef(staff.hostRef, memberUid)\n const now = deps.now()\n // Claim the attempt first; the PIN is checked only inside the budget.\n const claim = await firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(ref)\n if (!snapshot.exists || !snapshot.get('pinScrypt')) return { kind: 'none' as const }\n const lockedUntilMs = Number(snapshot.get('lockedUntilMs') ?? 0)\n if (lockedUntilMs > now) return { kind: 'locked' as const, lockedUntilMs }\n const failed = lockedUntilMs ? 0 : Number(snapshot.get('failedAttempts') ?? 0)\n if (failed >= POS_PIN_MAX_ATTEMPTS) {\n const until = now + POS_PIN_LOCKOUT_MS\n transaction.update(ref, { lockedUntilMs: until, failedAttempts: 0 })\n return { kind: 'locked' as const, lockedUntilMs: until }\n }\n transaction.update(ref, { failedAttempts: failed + 1, lockedUntilMs: null })\n return { kind: 'claimed' as const, hash: String(snapshot.get('pinScrypt')), attempt: failed + 1 }\n })\n if (claim.kind === 'none') {\n return { status: 404, body: { error: 'That member has not set a PIN on this site.' } }\n }\n if (claim.kind === 'locked') {\n return {\n status: 423,\n body: {\n error: 'Too many wrong PINs. Try again later, or ask someone who can approve refunds over the register’s limit to reset it.',\n lockedUntilMs: claim.lockedUntilMs,\n },\n }\n }\n if (!verifyMemberPassword(pin, claim.hash)) {\n const left = POS_PIN_MAX_ATTEMPTS - claim.attempt\n if (left <= 0) {\n await ref.update({ lockedUntilMs: now + POS_PIN_LOCKOUT_MS, failedAttempts: 0 })\n return {\n status: 423,\n body: {\n error: 'Too many wrong PINs. Try again later, or ask someone who can approve refunds over the register’s limit to reset it.',\n lockedUntilMs: now + POS_PIN_LOCKOUT_MS,\n },\n }\n }\n return {\n status: 401,\n body: { error: `That PIN is not right. ${left} ${left === 1 ? 'try' : 'tries'} left.`, attemptsLeft: left },\n }\n }\n await ref.update({ failedAttempts: 0, lockedUntilMs: null, lastUsedAtMs: now })\n // The PIN is right; whether it still buys anything is today's\n // permissions, and for a manager's approval today's role.\n const host = await staff.hostRef.get()\n const membership = await deps.membership(memberUid, staff.hostId)\n // A cashier switched in by PIN rings sales, so they need the register's\n // sell key too (AGL-4047), not only the register.\n const allowed =\n purpose === 'manager'\n ? isPosManager(host, memberUid, membership)\n : mayWorkRegister(membership) && holdsPosPermission(membership.permissions, 'posSell')\n if (!allowed) {\n return {\n status: 403,\n body: {\n error:\n purpose === 'manager'\n ? commerceWorkspacePermissionRefusal('commerce.posApprovals')\n : 'That member cannot use the register.',\n },\n }\n }\n let minted: { token: string; expiresAtMs: number }\n try {\n minted = mintPosAssertion(deps, {\n hostId: staff.hostId,\n registerId: register.ref.id,\n memberUid,\n purpose,\n })\n } catch {\n return { status: 501, body: { error: 'Staff PINs are not configured on this server.' } }\n }\n return {\n status: 200,\n body: {\n assertion: minted.token,\n expiresAtMs: minted.expiresAtMs,\n memberUid,\n name: await deps.memberName(memberUid),\n purpose,\n },\n }\n }\n\n case 'refresh': {\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const expected = { hostId: staff.hostId, registerId: register.ref.id, purpose: 'cashier' as const }\n if (!readPosAssertion(deps, body['assertion'], expected)) {\n return { status: 401, body: { error: 'Enter your PIN again.' } }\n }\n const memberUid = await resolvePosStaffAssertion(deps, staff.hostRef, body['assertion'], expected)\n if (!memberUid) return { status: 403, body: { error: 'That member cannot use the register.' } }\n const minted = mintPosAssertion(deps, { ...expected, memberUid })\n return { status: 200, body: { assertion: minted.token, expiresAtMs: minted.expiresAtMs, memberUid } }\n }\n\n default:\n return { status: 400, body: { error: 'Unknown action' } }\n }\n}\n\nexport function createPosStaffPinHandler(deps: () => PosOpsDeps = defaultPosOpsDeps): PluginApiHandler {\n return async (req, res) => {\n try {\n const outcome = await handlePosStaffPin(deps(), req)\n return res.status(outcome.status).json(outcome.body)\n } catch (error) {\n console.error('[pos-staff-pin] failed', error)\n return res.status(500).json({ error: 'The PIN could not be checked' })\n }\n }\n}\n\nexport const posStaffPinHandler = createPosStaffPinHandler()\n"],"names":["POS_PIN_LOCKOUT_MS","POS_PIN_MAX_ATTEMPTS","posPinProblem","holdsPosPermission","commerceWorkspacePermissionRefusal","hashMemberPassword","verifyMemberPassword","authorizePosOps","defaultPosOpsDeps","isPosManager","mayWorkRegister","mintPosAssertion","posOpsBody","posOpsCleanId","readPosAssertion","readPosRegister","resolvePosStaffAssertion","pinRef","hostRef","uid","collection","doc","handlePosStaffPin","deps","req","body","method","status","error","gate","staff","action","String","firestore","snapshot","get","hasPin","exists","Boolean","isManager","target","membership","hostId","delete","ok","pin","problem","set","pinScrypt","setAtMs","now","setBy","failedAttempts","lockedUntilMs","pins","limit","holders","docs","map","id","sells","Promise","all","permissions","staffWithPins","filter","_","index","members","name","memberName","sort","a","b","localeCompare","register","purpose","memberUid","test","ref","claim","runTransaction","transaction","kind","Number","failed","until","update","hash","attempt","left","attemptsLeft","lastUsedAtMs","host","allowed","minted","registerId","assertion","token","expiresAtMs","expected","createPosStaffPinHandler","res","outcome","json","console","posStaffPinHandler"],"mappings":"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"}
@@ -282,11 +282,21 @@ export declare function capturePosOfflineCardIntent(paymentIntentId: string, app
282
282
  } | {
283
283
  ok: false;
284
284
  error: string;
285
+ status: number;
285
286
  }>;
286
287
  /**
287
- * Turns Stripe's offline mode on for the site's readers: the same per-site
288
- * Terminal Configuration the reader tipping uses, with `offline[enabled]`,
289
- * assigned to the site's Location. Done once; a reader picks it up the next
290
- * time it connects online. False when Stripe refused it.
288
+ * Turns Stripe's offline mode on for the site's readers, on the site's ONE
289
+ * Terminal Configuration through {@link savePosReaderConfiguration}, like
290
+ * reader tipping and the start-up screen (AGL-4048). That helper claims a
291
+ * first create in a transaction, so offline mode racing tipping or the
292
+ * start-up screen to the first write lands on one configuration with both
293
+ * settings, never two configurations of which only one is assigned; and it
294
+ * finishes an assignment a previous first write left owing.
295
+ *
296
+ * Done once per configuration: `offlineConfigurationId` records which one
297
+ * carries `offline[enabled]`, so a site whose configuration was replaced or
298
+ * is still owed its assignment is set again rather than answered from a flag
299
+ * that no longer describes what its readers use. A reader picks the setting
300
+ * up the next time it connects online. False when Stripe refused it.
291
301
  */
292
302
  export declare function ensurePosOfflineCardConfiguration(hostId: string): Promise<boolean>;
@@ -1241,56 +1241,52 @@ function offlineCardIntent(body) {
1241
1241
  ]
1242
1242
  })
1243
1243
  });
1244
- if (!captured.ok) return {
1245
- ok: false,
1246
- error: posStripeErrorMessage(captured.body, 'The card payment could not be captured.')
1247
- };
1244
+ // The status tells the sync whether this attempt definitely failed (AGL-4039).
1245
+ if (!captured.ok) {
1246
+ return {
1247
+ ok: false,
1248
+ status: captured.status,
1249
+ error: posStripeErrorMessage(captured.body, 'The card payment could not be captured.')
1250
+ };
1251
+ }
1248
1252
  return {
1249
1253
  ok: true,
1250
1254
  intent: offlineCardIntent(captured.body)
1251
1255
  };
1252
1256
  }
1253
1257
  /**
1254
- * Turns Stripe's offline mode on for the site's readers: the same per-site
1255
- * Terminal Configuration the reader tipping uses, with `offline[enabled]`,
1256
- * assigned to the site's Location. Done once; a reader picks it up the next
1257
- * time it connects online. False when Stripe refused it.
1258
+ * Turns Stripe's offline mode on for the site's readers, on the site's ONE
1259
+ * Terminal Configuration through {@link savePosReaderConfiguration}, like
1260
+ * reader tipping and the start-up screen (AGL-4048). That helper claims a
1261
+ * first create in a transaction, so offline mode racing tipping or the
1262
+ * start-up screen to the first write lands on one configuration with both
1263
+ * settings, never two configurations of which only one is assigned; and it
1264
+ * finishes an assignment a previous first write left owing.
1265
+ *
1266
+ * Done once per configuration: `offlineConfigurationId` records which one
1267
+ * carries `offline[enabled]`, so a site whose configuration was replaced or
1268
+ * is still owed its assignment is set again rather than answered from a flag
1269
+ * that no longer describes what its readers use. A reader picks the setting
1270
+ * up the next time it connects online. False when Stripe refused it.
1258
1271
  */ export async function ensurePosOfflineCardConfiguration(hostId) {
1259
- var _config_get, _config_get1;
1260
- var _saved_body;
1272
+ var _config_get;
1261
1273
  const ref = terminalConfigRef(hostId);
1262
1274
  const config = await ref.get();
1263
- if (config.get('offlineEnabled') === true) return true;
1264
- const locationId = String((_config_get = config.get('stripeLocationId')) != null ? _config_get : '');
1265
- if (!locationId) return false;
1266
- const configurationId = String((_config_get1 = config.get('stripeConfigurationId')) != null ? _config_get1 : '');
1267
- const saved = await posStripe('POST', configurationId ? `terminal/configurations/${configurationId}` : 'terminal/configurations', _extends({
1268
- params: {
1269
- 'offline[enabled]': true
1270
- }
1271
- }, configurationId ? {} : {
1272
- idempotencyKey: `pos-configuration:${hostId}:offline`
1273
- }));
1274
- if (!saved.ok || !((_saved_body = saved.body) == null ? void 0 : _saved_body.id)) {
1275
- var _saved_body1;
1276
- console.error('[pos-terminal] offline configuration failed', hostId, (_saved_body1 = saved.body) == null ? void 0 : _saved_body1.error);
1277
- return false;
1275
+ if (!config.get('stripeLocationId')) return false;
1276
+ const configurationId = String((_config_get = config.get('stripeConfigurationId')) != null ? _config_get : '');
1277
+ if (config.get('offlineEnabled') === true && configurationId && config.get('offlineConfigurationId') === configurationId && config.get('stripeConfigurationAssigned') !== false) {
1278
+ return true;
1278
1279
  }
1279
- if (!configurationId) {
1280
- const assigned = await posStripe('POST', `terminal/locations/${locationId}`, {
1281
- params: {
1282
- configuration_overrides: String(saved.body.id)
1283
- }
1284
- });
1285
- if (!assigned.ok) {
1286
- var _assigned_body;
1287
- console.error('[pos-terminal] offline configuration assignment failed', hostId, (_assigned_body = assigned.body) == null ? void 0 : _assigned_body.error);
1288
- return false;
1289
- }
1280
+ const saved = await savePosReaderConfiguration(hostId, {
1281
+ 'offline[enabled]': true
1282
+ }, 'offline');
1283
+ if ('error' in saved) {
1284
+ console.error('[pos-terminal] offline configuration failed', hostId, saved.error);
1285
+ return false;
1290
1286
  }
1291
1287
  await ref.set({
1292
- stripeConfigurationId: String(saved.body.id),
1293
- offlineEnabled: true
1288
+ offlineEnabled: true,
1289
+ offlineConfigurationId: saved.configurationId
1294
1290
  }, {
1295
1291
  merge: true
1296
1292
  });