@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
@@ -14,9 +14,18 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import type { BillingWebhookHandler } from '@aglyn/aglyn/server';
17
+ import type { BillingWebhookHandler, PluginJobHostGate } from '@aglyn/aglyn/server';
18
+ /** The subcollection, under `hosts/{hostId}/terminal/config`, and its collection-group name. */
19
+ export declare const POS_OFFLINE_CARD_CHARGES = "offlineCardCharges";
20
+ /**
21
+ * How long an intent that holds money may wait for its sale before the sweep
22
+ * says so: six hours, which leaves well over a day of Stripe's two-day
23
+ * authorization to sync it, and is far past the seconds a healthy sync takes.
24
+ */
25
+ export declare const POS_OFFLINE_CARD_UNSYNCED_ALERT_MS: number;
18
26
  /** What the site keeps about one offline card's PaymentIntent. */
19
27
  export interface PosOfflineCardChargeRecord {
28
+ hostId: string;
20
29
  saleKey: string;
21
30
  paymentIntentId: string;
22
31
  status: string;
@@ -25,9 +34,18 @@ export interface PosOfflineCardChargeRecord {
25
34
  livemode: boolean;
26
35
  /** Whether the sale has synced (its order exists). */
27
36
  synced: boolean;
37
+ orderId?: string;
38
+ syncedAtMs?: number;
28
39
  lastEvent: string;
29
40
  updatedAtMs: number;
41
+ /** When the sweep says the sale never synced; absent once synced or told. */
42
+ alertDueAtMs?: number;
43
+ alertedAtMs?: number;
44
+ /** The capture's idempotency attempt, raised only after a definite failure. */
45
+ captureAttempt?: number;
30
46
  }
47
+ /** `hosts/{hostId}/terminal/config/offlineCardCharges/{saleKey}`. */
48
+ export declare function posOfflineCardChargeRef(hostRef: FirebaseFirestore.DocumentReference, saleKey: string): FirebaseFirestore.DocumentReference;
31
49
  export declare function reconcilePosOfflineCardEvent(input: {
32
50
  type: string;
33
51
  object: any;
@@ -36,3 +54,51 @@ export declare function reconcilePosOfflineCardEvent(input: {
36
54
  } | null>;
37
55
  /** The platform webhook's handler for offline card intents: claims only its own. */
38
56
  export declare const posOfflineCardBillingWebhookHandler: BillingWebhookHandler;
57
+ export interface UnsyncedOfflineCardReport {
58
+ hostId: string;
59
+ saleKey: string;
60
+ paymentIntentId: string;
61
+ status: string;
62
+ amountCents: number;
63
+ registerId: string;
64
+ /** How long ago the intent was first seen waiting, in ms. */
65
+ waitedMs: number;
66
+ }
67
+ export interface UnsyncedOfflineCardScan {
68
+ due: number;
69
+ alerted: number;
70
+ /** Synced, or no longer holding money, since the alert was set. */
71
+ cleared: number;
72
+ skippedLocked: number;
73
+ }
74
+ /** Tells the operator and the site's managers about one card. Never throws. */
75
+ export declare function reportUnsyncedOfflineCard(report: UnsyncedOfflineCardReport): Promise<void>;
76
+ /** Tells the operator a card was made out to an account that is not the store's. Never throws. */
77
+ export declare function reportPosOfflineCardWrongAccount(input: {
78
+ hostId: string;
79
+ orderId: string;
80
+ paymentIntentId: string;
81
+ status: string;
82
+ amountCents: number;
83
+ destination: string;
84
+ expected: string;
85
+ }): Promise<void>;
86
+ /**
87
+ * The hourly sweep (registered in `server.ts`): every offline card record
88
+ * whose alert is due. Re-checks the sale first, so a sale that synced while
89
+ * the event was in flight clears the record and tells nobody.
90
+ *
91
+ * One collection-group range query on `alertDueAtMs`, which is set only on a
92
+ * record that holds money and has not synced, so the ordinary beat reads an
93
+ * empty result. Its COLLECTION_GROUP index is declared in
94
+ * `cloud/firebase-firestore.indexes.json` and pinned by
95
+ * `cron-scan-indexes.spec.ts`.
96
+ */
97
+ export declare function scanUnsyncedOfflineCards(
98
+ /** The lockdown gate, injected by the runner (AGL-2495). */
99
+ gate: PluginJobHostGate, options?: {
100
+ firestore?: FirebaseFirestore.Firestore;
101
+ nowMs?: number;
102
+ limit?: number;
103
+ report?: (report: UnsyncedOfflineCardReport) => Promise<void>;
104
+ }): Promise<UnsyncedOfflineCardScan>;
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2026 Aglyn LLC
@@ -13,32 +14,102 @@
13
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
15
  * See the License for the specific language governing permissions and
15
16
  * limitations under the License.
16
- */ import { firebaseAdmin } from "@aglyn/tenant-data-admin";
17
+ */ import { formatOperatorAlertAmount } from "@aglyn/aglyn/app-utils/operator-alerts";
18
+ import { firebaseAdmin, hostDisplayName, notifyHostManagers, raiseOperatorAlert } from "@aglyn/tenant-data-admin";
19
+ import { FieldValue } from "firebase-admin/firestore";
20
+ import { COMMERCE_POS_OFFLINE_CARD_UNSYNCED, COMMERCE_POS_OFFLINE_CARD_WRONG_ACCOUNT } from "../constants/operator-alerts.js";
17
21
  import { POS_OFFLINE_CARD_INTENT_KIND, posOfflineSaleKeyIsValid } from "../model/commerce-pos-offline.js";
22
+ /*==========================================
23
+ * A CARD TAKEN OFFLINE, AS STRIPE REPORTS IT (AGL-4039).
24
+ *
25
+ * The Aglyn POS app creates the PaymentIntent of a card it takes offline, so
26
+ * Stripe can know of a charge before the sale itself reaches the server: the
27
+ * SDK forwards the card the moment a connection returns, and the sale syncs
28
+ * right after, from the same device. Usually seconds apart. If the sale never
29
+ * arrives (the device was lost or wiped between the two, or it waits for a
30
+ * member who never signs in again), the charge must not be invisible: each
31
+ * event on such an intent is kept on the site, by the sale's key, under the
32
+ * site's server-only Terminal document
33
+ * (`terminal/config/offlineCardCharges/{saleKey}`), beside whether the sale
34
+ * has synced. The sync itself never depends on this: it reads Stripe.
35
+ *
36
+ * WHO SAYS "SYNCED". Both writers, in transactions, so neither can undo the
37
+ * other: the sync marks the record when it creates the order, and an event
38
+ * reads the record and the sync record together. An event that arrives after
39
+ * the sync therefore keeps `synced`, and one that raced it retries and sees
40
+ * it.
41
+ *
42
+ * NOBODY LOOKING. An intent that holds or has taken money while its sale has
43
+ * not synced gets `alertDueAtMs`, {@link POS_OFFLINE_CARD_UNSYNCED_ALERT_MS}
44
+ * after it was first seen. The hourly sweep ({@link scanUnsyncedOfflineCards})
45
+ * tells the operator and the site's managers once, with time left to sync the
46
+ * sale before Stripe's authorization lapses (about two days). It never
47
+ * captures: without the sale there is no fee to capture with.
48
+ *
49
+ * Self-selecting: every other event returns `null` untouched. Registered on
50
+ * the platform webhook from the commerce server module, beside the other
51
+ * commerce handlers, rather than as a branch of the storefront sections. The
52
+ * four `payment_intent.*` events it reads are in the platform destination's
53
+ * `WEBHOOK_EVENTS` (`tools/scripts/lib/stripe-webhook-health.mjs`).
54
+ *=========================================*/ /** The subcollection, under `hosts/{hostId}/terminal/config`, and its collection-group name. */ export const POS_OFFLINE_CARD_CHARGES = 'offlineCardCharges';
55
+ /**
56
+ * How long an intent that holds money may wait for its sale before the sweep
57
+ * says so: six hours, which leaves well over a day of Stripe's two-day
58
+ * authorization to sync it, and is far past the seconds a healthy sync takes.
59
+ */ export const POS_OFFLINE_CARD_UNSYNCED_ALERT_MS = 6 * 60 * 60 * 1000;
60
+ /** Statuses in which an intent holds money or has taken it. */ const HOLDS_MONEY = new Set([
61
+ 'requires_capture',
62
+ 'processing',
63
+ 'succeeded'
64
+ ]);
65
+ /** Statuses an intent never leaves; an older event must not turn one back. */ const FINAL = new Set([
66
+ 'succeeded',
67
+ 'canceled'
68
+ ]);
69
+ /** `hosts/{hostId}/terminal/config/offlineCardCharges/{saleKey}`. */ export function posOfflineCardChargeRef(hostRef, saleKey) {
70
+ return hostRef.collection('terminal').doc('config').collection(POS_OFFLINE_CARD_CHARGES).doc(saleKey);
71
+ }
72
+ const validHostId = (hostId)=>/^[A-Za-z0-9_-]{1,128}$/.test(hostId);
18
73
  export async function reconcilePosOfflineCardEvent(input, firestore, now = Date.now) {
19
- var _input_object, _intent_metadata, _metadata_hostId, _metadata_saleKey, _intent_id, _intent_status, _intent_amount, _metadata_registerId;
74
+ var _input_object, _intent_metadata, _metadata_hostId, _metadata_saleKey;
20
75
  if (!input.type.startsWith('payment_intent.')) return null;
21
76
  const intent = (_input_object = input.object) != null ? _input_object : {};
22
77
  const metadata = (_intent_metadata = intent.metadata) != null ? _intent_metadata : {};
23
78
  if (metadata['kind'] !== POS_OFFLINE_CARD_INTENT_KIND) return null;
24
79
  const hostId = String((_metadata_hostId = metadata['hostId']) != null ? _metadata_hostId : '');
25
80
  const saleKey = String((_metadata_saleKey = metadata['saleKey']) != null ? _metadata_saleKey : '');
26
- if (!/^[A-Za-z0-9_-]{1,128}$/.test(hostId) || !posOfflineSaleKeyIsValid(saleKey)) return null;
27
- const hostRef = (firestore != null ? firestore : firebaseAdmin.app().firestore()).collection('hosts').doc(hostId);
28
- const synced = (await hostRef.collection('posOfflineSales').doc(saleKey).get()).exists;
29
- const record = {
30
- saleKey,
31
- paymentIntentId: String((_intent_id = intent.id) != null ? _intent_id : ''),
32
- status: String((_intent_status = intent.status) != null ? _intent_status : ''),
33
- amountCents: Number((_intent_amount = intent.amount) != null ? _intent_amount : 0),
34
- registerId: String((_metadata_registerId = metadata['registerId']) != null ? _metadata_registerId : '').slice(0, 128),
35
- livemode: Boolean(intent.livemode),
36
- synced,
37
- lastEvent: input.type,
38
- updatedAtMs: now()
39
- };
40
- await hostRef.collection('terminal').doc('config').collection('offlineCardCharges').doc(saleKey).set(record, {
41
- merge: true
81
+ if (!validHostId(hostId) || !posOfflineSaleKeyIsValid(saleKey)) return null;
82
+ const db = firestore != null ? firestore : firebaseAdmin.app().firestore();
83
+ const hostRef = db.collection('hosts').doc(hostId);
84
+ const chargeRef = posOfflineCardChargeRef(hostRef, saleKey);
85
+ const saleRef = hostRef.collection('posOfflineSales').doc(saleKey);
86
+ await db.runTransaction(async (transaction)=>{
87
+ var _intent_status, _intent_id, _intent_amount, _metadata_registerId;
88
+ const charge = await transaction.get(chargeRef);
89
+ const sale = await transaction.get(saleRef);
90
+ const previous = charge.exists ? charge.data() : {};
91
+ const synced = previous.synced === true || sale.exists;
92
+ const incoming = String((_intent_status = intent.status) != null ? _intent_status : '');
93
+ // Stripe does not promise event order: a late `amount_capturable_updated`
94
+ // must not turn a captured or canceled intent back into an authorized one.
95
+ const status = previous.status && FINAL.has(previous.status) && !FINAL.has(incoming) ? previous.status : incoming;
96
+ const at = now();
97
+ const alertDueAtMs = !synced && HOLDS_MONEY.has(status) && !previous.alertedAtMs ? typeof previous.alertDueAtMs === 'number' ? previous.alertDueAtMs : at + POS_OFFLINE_CARD_UNSYNCED_ALERT_MS : null;
98
+ transaction.set(chargeRef, {
99
+ hostId,
100
+ saleKey,
101
+ paymentIntentId: String((_intent_id = intent.id) != null ? _intent_id : ''),
102
+ status,
103
+ amountCents: Math.max(0, Math.round(Number((_intent_amount = intent.amount) != null ? _intent_amount : 0))),
104
+ registerId: String((_metadata_registerId = metadata['registerId']) != null ? _metadata_registerId : '').slice(0, 128),
105
+ livemode: Boolean(intent.livemode),
106
+ synced,
107
+ lastEvent: input.type,
108
+ updatedAtMs: at,
109
+ alertDueAtMs: alertDueAtMs != null ? alertDueAtMs : FieldValue.delete()
110
+ }, {
111
+ merge: true
112
+ });
42
113
  });
43
114
  return {
44
115
  hostId
@@ -54,5 +125,131 @@ export async function reconcilePosOfflineCardEvent(input, firestore, now = Date.
54
125
  hostId: reconciled.hostId
55
126
  } : undefined;
56
127
  };
128
+ const describeWait = (ms)=>{
129
+ const hours = Math.max(1, Math.round(ms / (60 * 60 * 1000)));
130
+ return hours >= 48 ? `${Math.round(hours / 24)} days` : `${hours} hour${hours === 1 ? '' : 's'}`;
131
+ };
132
+ /** Tells the operator and the site's managers about one card. Never throws. */ export async function reportUnsyncedOfflineCard(report) {
133
+ try {
134
+ const host = await firebaseAdmin.app().firestore().collection('hosts').doc(report.hostId).get().catch(()=>null);
135
+ const site = (host == null ? void 0 : host.exists) ? hostDisplayName(host.data(), report.hostId) : report.hostId;
136
+ const amount = formatOperatorAlertAmount(report.amountCents);
137
+ await raiseOperatorAlert(COMMERCE_POS_OFFLINE_CARD_UNSYNCED, {
138
+ dedupeKey: `${report.hostId}:${report.saleKey}`,
139
+ hostId: report.hostId,
140
+ context: {
141
+ amount,
142
+ paymentIntentId: report.paymentIntentId,
143
+ status: report.status,
144
+ site: `${site} (${report.hostId})`,
145
+ registerId: report.registerId || 'unknown',
146
+ age: describeWait(report.waitedMs)
147
+ }
148
+ });
149
+ await notifyHostManagers(report.hostId, {
150
+ type: 'content.order',
151
+ title: `A ${amount} card taken offline on {site} has no sale`,
152
+ body: report.status === 'succeeded' ? `A ${amount} card taken offline was charged, but the register never sent its sale, so there is no order for it. ` + 'Open the register that took it, signed in as the member who rang it, so the sale syncs.' : `A ${amount} card taken offline is approved but not yet charged, because the register never sent its sale. ` + 'Open the register that took it, signed in as the member who rang it, so the sale syncs. ' + 'If it does not sync within about two days of the sale, the card is never charged.',
153
+ link: `/${report.hostId}/products`
154
+ });
155
+ } catch (error) {
156
+ console.error('[pos-offline-card] unsynced report failed', report.hostId, report.saleKey, error);
157
+ }
158
+ }
159
+ /** Tells the operator a card was made out to an account that is not the store's. Never throws. */ export async function reportPosOfflineCardWrongAccount(input) {
160
+ try {
161
+ const host = await firebaseAdmin.app().firestore().collection('hosts').doc(input.hostId).get().catch(()=>null);
162
+ const site = (host == null ? void 0 : host.exists) ? hostDisplayName(host.data(), input.hostId) : input.hostId;
163
+ await raiseOperatorAlert(COMMERCE_POS_OFFLINE_CARD_WRONG_ACCOUNT, {
164
+ dedupeKey: `${input.hostId}:${input.orderId}`,
165
+ hostId: input.hostId,
166
+ context: {
167
+ amount: formatOperatorAlertAmount(input.amountCents),
168
+ paymentIntentId: input.paymentIntentId,
169
+ status: input.status,
170
+ orderId: input.orderId,
171
+ site: `${site} (${input.hostId})`,
172
+ destination: input.destination || 'none',
173
+ expected: input.expected
174
+ }
175
+ });
176
+ } catch (error) {
177
+ console.error('[pos-offline-card] wrong-account alert failed', input.hostId, input.orderId, error);
178
+ }
179
+ }
180
+ /**
181
+ * The hourly sweep (registered in `server.ts`): every offline card record
182
+ * whose alert is due. Re-checks the sale first, so a sale that synced while
183
+ * the event was in flight clears the record and tells nobody.
184
+ *
185
+ * One collection-group range query on `alertDueAtMs`, which is set only on a
186
+ * record that holds money and has not synced, so the ordinary beat reads an
187
+ * empty result. Its COLLECTION_GROUP index is declared in
188
+ * `cloud/firebase-firestore.indexes.json` and pinned by
189
+ * `cron-scan-indexes.spec.ts`.
190
+ */ export async function scanUnsyncedOfflineCards(/** The lockdown gate, injected by the runner (AGL-2495). */ gate, options = {}) {
191
+ var _options_firestore, _options_nowMs, _options_report, _options_limit;
192
+ const db = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
193
+ const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
194
+ const report = (_options_report = options.report) != null ? _options_report : reportUnsyncedOfflineCard;
195
+ const scan = {
196
+ due: 0,
197
+ alerted: 0,
198
+ cleared: 0,
199
+ skippedLocked: 0
200
+ };
201
+ const due = await db.collectionGroup(POS_OFFLINE_CARD_CHARGES).where('alertDueAtMs', '<=', nowMs).orderBy('alertDueAtMs', 'asc').limit((_options_limit = options.limit) != null ? _options_limit : 100).get().catch((error)=>{
202
+ console.error('[pos-offline-card] unsynced scan failed', error);
203
+ return null;
204
+ });
205
+ if (!due) return scan;
206
+ for (const doc of due.docs){
207
+ var _doc_data, _record_status, _record_paymentIntentId, _record_status1, _record_amountCents, _record_registerId;
208
+ var _doc_ref_parent_parent_parent_parent, _doc_ref_parent_parent_parent, _doc_ref_parent_parent;
209
+ const record = (_doc_data = doc.data()) != null ? _doc_data : {};
210
+ if (typeof record.alertDueAtMs !== 'number') continue;
211
+ // `hosts/{hostId}/terminal/config/offlineCardCharges/{saleKey}`.
212
+ const hostId = String(record.hostId || ((_doc_ref_parent_parent = doc.ref.parent.parent) == null ? void 0 : (_doc_ref_parent_parent_parent = _doc_ref_parent_parent.parent) == null ? void 0 : (_doc_ref_parent_parent_parent_parent = _doc_ref_parent_parent_parent.parent) == null ? void 0 : _doc_ref_parent_parent_parent_parent.id) || '');
213
+ const saleKey = String(record.saleKey || doc.id);
214
+ if (!validHostId(hostId) || !posOfflineSaleKeyIsValid(saleKey)) continue;
215
+ scan.due += 1;
216
+ // A locked site is skipped, not dropped: its record stays due and is
217
+ // told on the first beat after the lift.
218
+ if (await gate.isLocked(hostId)) {
219
+ scan.skippedLocked += 1;
220
+ continue;
221
+ }
222
+ const hostRef = db.collection('hosts').doc(hostId);
223
+ const synced = (await hostRef.collection('posOfflineSales').doc(saleKey).get()).exists;
224
+ if (synced || !HOLDS_MONEY.has(String((_record_status = record.status) != null ? _record_status : ''))) {
225
+ await doc.ref.set(_extends({}, synced ? {
226
+ synced: true
227
+ } : {}, {
228
+ alertDueAtMs: FieldValue.delete()
229
+ }), {
230
+ merge: true
231
+ });
232
+ scan.cleared += 1;
233
+ continue;
234
+ }
235
+ await report({
236
+ hostId,
237
+ saleKey,
238
+ paymentIntentId: String((_record_paymentIntentId = record.paymentIntentId) != null ? _record_paymentIntentId : ''),
239
+ status: String((_record_status1 = record.status) != null ? _record_status1 : ''),
240
+ amountCents: Math.max(0, Math.round(Number((_record_amountCents = record.amountCents) != null ? _record_amountCents : 0))),
241
+ registerId: String((_record_registerId = record.registerId) != null ? _record_registerId : ''),
242
+ waitedMs: Math.max(0, nowMs - (record.alertDueAtMs - POS_OFFLINE_CARD_UNSYNCED_ALERT_MS))
243
+ });
244
+ await doc.ref.set({
245
+ alertedAtMs: nowMs,
246
+ alertDueAtMs: FieldValue.delete()
247
+ }, {
248
+ merge: true
249
+ });
250
+ scan.alerted += 1;
251
+ }
252
+ return scan;
253
+ }
57
254
 
58
255
  //# sourceMappingURL=pos-offline-card-reconcile.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-offline-card-reconcile.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 { BillingWebhookHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n POS_OFFLINE_CARD_INTENT_KIND,\n posOfflineSaleKeyIsValid,\n} from '../model/commerce-pos-offline'\n\n/*==========================================\n * A CARD TAKEN OFFLINE, AS STRIPE REPORTS IT (AGL-4039).\n *\n * The Aglyn POS app creates the PaymentIntent of a card it takes offline, so\n * Stripe can know of a charge before the sale itself reaches the server: the\n * SDK forwards the card the moment a connection returns, and the sale syncs\n * right after, from the same device. Usually seconds apart. If the sale never\n * arrives (the device was lost or wiped between the two), the charge must not\n * be invisible: each event on such an intent is kept on the site, by the\n * sale's key, under the site's server-only Terminal document\n * (`terminal/config/offlineCardCharges/{saleKey}`), beside whether the sale\n * has synced. The sync itself never depends on this: it reads Stripe.\n *\n * Self-selecting: every other event returns `null` untouched. Registered on\n * the platform webhook from the commerce server module, beside the other\n * commerce handlers, rather than as a branch of the storefront sections.\n *=========================================*/\n\n/** What the site keeps about one offline card's PaymentIntent. */\nexport interface PosOfflineCardChargeRecord {\n saleKey: string\n paymentIntentId: string\n status: string\n amountCents: number\n registerId: string\n livemode: boolean\n /** Whether the sale has synced (its order exists). */\n synced: boolean\n lastEvent: string\n updatedAtMs: number\n}\n\nexport async function reconcilePosOfflineCardEvent(\n input: { type: string; object: any },\n firestore?: FirebaseFirestore.Firestore,\n now: () => number = Date.now,\n): Promise<{ hostId: string } | null> {\n if (!input.type.startsWith('payment_intent.')) return null\n const intent = input.object ?? {}\n const metadata = (intent.metadata ?? {}) as Record<string, string>\n if (metadata['kind'] !== POS_OFFLINE_CARD_INTENT_KIND) return null\n const hostId = String(metadata['hostId'] ?? '')\n const saleKey = String(metadata['saleKey'] ?? '')\n if (\n !/^[A-Za-z0-9_-]{1,128}$/.test(hostId) ||\n !posOfflineSaleKeyIsValid(saleKey)\n )\n return null\n const hostRef = (firestore ?? firebaseAdmin.app().firestore())\n .collection('hosts')\n .doc(hostId)\n const synced = (\n await hostRef.collection('posOfflineSales').doc(saleKey).get()\n ).exists\n const record: PosOfflineCardChargeRecord = {\n saleKey,\n paymentIntentId: String(intent.id ?? ''),\n status: String(intent.status ?? ''),\n amountCents: Number(intent.amount ?? 0),\n registerId: String(metadata['registerId'] ?? '').slice(0, 128),\n livemode: Boolean(intent.livemode),\n synced,\n lastEvent: input.type,\n updatedAtMs: now(),\n }\n await hostRef\n .collection('terminal')\n .doc('config')\n .collection('offlineCardCharges')\n .doc(saleKey)\n .set(record, { merge: true })\n return { hostId }\n}\n\n/** The platform webhook's handler for offline card intents: claims only its own. */\nexport const posOfflineCardBillingWebhookHandler: BillingWebhookHandler =\n async ({ type, object }) => {\n const reconciled = await reconcilePosOfflineCardEvent({ type, object })\n return reconciled ? { claimed: true, hostId: reconciled.hostId } : undefined\n }\n"],"names":["firebaseAdmin","POS_OFFLINE_CARD_INTENT_KIND","posOfflineSaleKeyIsValid","reconcilePosOfflineCardEvent","input","firestore","now","Date","intent","metadata","type","startsWith","object","hostId","String","saleKey","test","hostRef","app","collection","doc","synced","get","exists","record","paymentIntentId","id","status","amountCents","Number","amount","registerId","slice","livemode","Boolean","lastEvent","updatedAtMs","set","merge","posOfflineCardBillingWebhookHandler","reconciled","claimed","undefined"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,aAAa,QAAQ,2BAA0B;AACxD,SACEC,4BAA4B,EAC5BC,wBAAwB,QACnB,mCAA+B;AAkCtC,OAAO,eAAeC,6BACpBC,KAAoC,EACpCC,SAAuC,EACvCC,MAAoBC,KAAKD,GAAG;QAGbF,eACGI,kBAEIC,kBACCA,mBAcGD,YACTA,gBACKA,gBACDC;IAtBrB,IAAI,CAACL,MAAMM,IAAI,CAACC,UAAU,CAAC,oBAAoB,OAAO;IACtD,MAAMH,UAASJ,gBAAAA,MAAMQ,MAAM,YAAZR,gBAAgB,CAAC;IAChC,MAAMK,YAAYD,mBAAAA,OAAOC,QAAQ,YAAfD,mBAAmB,CAAC;IACtC,IAAIC,QAAQ,CAAC,OAAO,KAAKR,8BAA8B,OAAO;IAC9D,MAAMY,SAASC,QAAOL,mBAAAA,QAAQ,CAAC,SAAS,YAAlBA,mBAAsB;IAC5C,MAAMM,UAAUD,QAAOL,oBAAAA,QAAQ,CAAC,UAAU,YAAnBA,oBAAuB;IAC9C,IACE,CAAC,yBAAyBO,IAAI,CAACH,WAC/B,CAACX,yBAAyBa,UAE1B,OAAO;IACT,MAAME,UAAU,CAACZ,oBAAAA,YAAaL,cAAckB,GAAG,GAAGb,SAAS,IACxDc,UAAU,CAAC,SACXC,GAAG,CAACP;IACP,MAAMQ,SAAS,AACb,CAAA,MAAMJ,QAAQE,UAAU,CAAC,mBAAmBC,GAAG,CAACL,SAASO,GAAG,EAAC,EAC7DC,MAAM;IACR,MAAMC,SAAqC;QACzCT;QACAU,iBAAiBX,QAAON,aAAAA,OAAOkB,EAAE,YAATlB,aAAa;QACrCmB,QAAQb,QAAON,iBAAAA,OAAOmB,MAAM,YAAbnB,iBAAiB;QAChCoB,aAAaC,QAAOrB,iBAAAA,OAAOsB,MAAM,YAAbtB,iBAAiB;QACrCuB,YAAYjB,QAAOL,uBAAAA,QAAQ,CAAC,aAAa,YAAtBA,uBAA0B,IAAIuB,KAAK,CAAC,GAAG;QAC1DC,UAAUC,QAAQ1B,OAAOyB,QAAQ;QACjCZ;QACAc,WAAW/B,MAAMM,IAAI;QACrB0B,aAAa9B;IACf;IACA,MAAMW,QACHE,UAAU,CAAC,YACXC,GAAG,CAAC,UACJD,UAAU,CAAC,sBACXC,GAAG,CAACL,SACJsB,GAAG,CAACb,QAAQ;QAAEc,OAAO;IAAK;IAC7B,OAAO;QAAEzB;IAAO;AAClB;AAEA,kFAAkF,GAClF,OAAO,MAAM0B,sCACX,OAAO,EAAE7B,IAAI,EAAEE,MAAM,EAAE;IACrB,MAAM4B,aAAa,MAAMrC,6BAA6B;QAAEO;QAAME;IAAO;IACrE,OAAO4B,aAAa;QAAEC,SAAS;QAAM5B,QAAQ2B,WAAW3B,MAAM;IAAC,IAAI6B;AACrE,EAAC"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-offline-card-reconcile.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 { formatOperatorAlertAmount } from '@aglyn/aglyn/app-utils/operator-alerts'\nimport type { BillingWebhookHandler, PluginJobHostGate } from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n hostDisplayName,\n notifyHostManagers,\n raiseOperatorAlert,\n} from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n COMMERCE_POS_OFFLINE_CARD_UNSYNCED,\n COMMERCE_POS_OFFLINE_CARD_WRONG_ACCOUNT,\n} from '../constants/operator-alerts'\nimport {\n POS_OFFLINE_CARD_INTENT_KIND,\n posOfflineSaleKeyIsValid,\n} from '../model/commerce-pos-offline'\n\n/*==========================================\n * A CARD TAKEN OFFLINE, AS STRIPE REPORTS IT (AGL-4039).\n *\n * The Aglyn POS app creates the PaymentIntent of a card it takes offline, so\n * Stripe can know of a charge before the sale itself reaches the server: the\n * SDK forwards the card the moment a connection returns, and the sale syncs\n * right after, from the same device. Usually seconds apart. If the sale never\n * arrives (the device was lost or wiped between the two, or it waits for a\n * member who never signs in again), the charge must not be invisible: each\n * event on such an intent is kept on the site, by the sale's key, under the\n * site's server-only Terminal document\n * (`terminal/config/offlineCardCharges/{saleKey}`), beside whether the sale\n * has synced. The sync itself never depends on this: it reads Stripe.\n *\n * WHO SAYS \"SYNCED\". Both writers, in transactions, so neither can undo the\n * other: the sync marks the record when it creates the order, and an event\n * reads the record and the sync record together. An event that arrives after\n * the sync therefore keeps `synced`, and one that raced it retries and sees\n * it.\n *\n * NOBODY LOOKING. An intent that holds or has taken money while its sale has\n * not synced gets `alertDueAtMs`, {@link POS_OFFLINE_CARD_UNSYNCED_ALERT_MS}\n * after it was first seen. The hourly sweep ({@link scanUnsyncedOfflineCards})\n * tells the operator and the site's managers once, with time left to sync the\n * sale before Stripe's authorization lapses (about two days). It never\n * captures: without the sale there is no fee to capture with.\n *\n * Self-selecting: every other event returns `null` untouched. Registered on\n * the platform webhook from the commerce server module, beside the other\n * commerce handlers, rather than as a branch of the storefront sections. The\n * four `payment_intent.*` events it reads are in the platform destination's\n * `WEBHOOK_EVENTS` (`tools/scripts/lib/stripe-webhook-health.mjs`).\n *=========================================*/\n\n/** The subcollection, under `hosts/{hostId}/terminal/config`, and its collection-group name. */\nexport const POS_OFFLINE_CARD_CHARGES = 'offlineCardCharges'\n\n/**\n * How long an intent that holds money may wait for its sale before the sweep\n * says so: six hours, which leaves well over a day of Stripe's two-day\n * authorization to sync it, and is far past the seconds a healthy sync takes.\n */\nexport const POS_OFFLINE_CARD_UNSYNCED_ALERT_MS = 6 * 60 * 60 * 1000\n\n/** Statuses in which an intent holds money or has taken it. */\nconst HOLDS_MONEY: ReadonlySet<string> = new Set(['requires_capture', 'processing', 'succeeded'])\n\n/** Statuses an intent never leaves; an older event must not turn one back. */\nconst FINAL: ReadonlySet<string> = new Set(['succeeded', 'canceled'])\n\n/** What the site keeps about one offline card's PaymentIntent. */\nexport interface PosOfflineCardChargeRecord {\n hostId: string\n saleKey: string\n paymentIntentId: string\n status: string\n amountCents: number\n registerId: string\n livemode: boolean\n /** Whether the sale has synced (its order exists). */\n synced: boolean\n orderId?: string\n syncedAtMs?: number\n lastEvent: string\n updatedAtMs: number\n /** When the sweep says the sale never synced; absent once synced or told. */\n alertDueAtMs?: number\n alertedAtMs?: number\n /** The capture's idempotency attempt, raised only after a definite failure. */\n captureAttempt?: number\n}\n\n/** `hosts/{hostId}/terminal/config/offlineCardCharges/{saleKey}`. */\nexport function posOfflineCardChargeRef(\n hostRef: FirebaseFirestore.DocumentReference,\n saleKey: string,\n): FirebaseFirestore.DocumentReference {\n return hostRef.collection('terminal').doc('config').collection(POS_OFFLINE_CARD_CHARGES).doc(saleKey)\n}\n\nconst validHostId = (hostId: string) => /^[A-Za-z0-9_-]{1,128}$/.test(hostId)\n\nexport async function reconcilePosOfflineCardEvent(\n input: { type: string; object: any },\n firestore?: FirebaseFirestore.Firestore,\n now: () => number = Date.now,\n): Promise<{ hostId: string } | null> {\n if (!input.type.startsWith('payment_intent.')) return null\n const intent = input.object ?? {}\n const metadata = (intent.metadata ?? {}) as Record<string, string>\n if (metadata['kind'] !== POS_OFFLINE_CARD_INTENT_KIND) return null\n const hostId = String(metadata['hostId'] ?? '')\n const saleKey = String(metadata['saleKey'] ?? '')\n if (!validHostId(hostId) || !posOfflineSaleKeyIsValid(saleKey)) return null\n const db = firestore ?? firebaseAdmin.app().firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const chargeRef = posOfflineCardChargeRef(hostRef, saleKey)\n const saleRef = hostRef.collection('posOfflineSales').doc(saleKey)\n await db.runTransaction(async (transaction) => {\n const charge = await transaction.get(chargeRef)\n const sale = await transaction.get(saleRef)\n const previous = (charge.exists ? charge.data() : {}) as Partial<PosOfflineCardChargeRecord>\n const synced = previous.synced === true || sale.exists\n const incoming = String(intent.status ?? '')\n // Stripe does not promise event order: a late `amount_capturable_updated`\n // must not turn a captured or canceled intent back into an authorized one.\n const status =\n previous.status && FINAL.has(previous.status) && !FINAL.has(incoming) ? previous.status : incoming\n const at = now()\n const alertDueAtMs =\n !synced && HOLDS_MONEY.has(status) && !previous.alertedAtMs\n ? typeof previous.alertDueAtMs === 'number'\n ? previous.alertDueAtMs\n : at + POS_OFFLINE_CARD_UNSYNCED_ALERT_MS\n : null\n transaction.set(\n chargeRef,\n {\n hostId,\n saleKey,\n paymentIntentId: String(intent.id ?? ''),\n status,\n amountCents: Math.max(0, Math.round(Number(intent.amount ?? 0))),\n registerId: String(metadata['registerId'] ?? '').slice(0, 128),\n livemode: Boolean(intent.livemode),\n synced,\n lastEvent: input.type,\n updatedAtMs: at,\n alertDueAtMs: alertDueAtMs ?? FieldValue.delete(),\n },\n { merge: true },\n )\n })\n return { hostId }\n}\n\n/** The platform webhook's handler for offline card intents: claims only its own. */\nexport const posOfflineCardBillingWebhookHandler: BillingWebhookHandler =\n async ({ type, object }) => {\n const reconciled = await reconcilePosOfflineCardEvent({ type, object })\n return reconciled ? { claimed: true, hostId: reconciled.hostId } : undefined\n }\n\n/*==========================================\n * THE SWEEP: AN OFFLINE CARD WHOSE SALE NEVER SYNCED.\n *=========================================*/\n\nexport interface UnsyncedOfflineCardReport {\n hostId: string\n saleKey: string\n paymentIntentId: string\n status: string\n amountCents: number\n registerId: string\n /** How long ago the intent was first seen waiting, in ms. */\n waitedMs: number\n}\n\nexport interface UnsyncedOfflineCardScan {\n due: number\n alerted: number\n /** Synced, or no longer holding money, since the alert was set. */\n cleared: number\n skippedLocked: number\n}\n\nconst describeWait = (ms: number) => {\n const hours = Math.max(1, Math.round(ms / (60 * 60 * 1000)))\n return hours >= 48 ? `${Math.round(hours / 24)} days` : `${hours} hour${hours === 1 ? '' : 's'}`\n}\n\n/** Tells the operator and the site's managers about one card. Never throws. */\nexport async function reportUnsyncedOfflineCard(report: UnsyncedOfflineCardReport): Promise<void> {\n try {\n const host = await firebaseAdmin.app().firestore().collection('hosts').doc(report.hostId).get().catch(() => null)\n const site = host?.exists ? hostDisplayName(host.data() as Record<string, unknown>, report.hostId) : report.hostId\n const amount = formatOperatorAlertAmount(report.amountCents)\n await raiseOperatorAlert(COMMERCE_POS_OFFLINE_CARD_UNSYNCED, {\n dedupeKey: `${report.hostId}:${report.saleKey}`,\n hostId: report.hostId,\n context: {\n amount,\n paymentIntentId: report.paymentIntentId,\n status: report.status,\n site: `${site} (${report.hostId})`,\n registerId: report.registerId || 'unknown',\n age: describeWait(report.waitedMs),\n },\n })\n await notifyHostManagers(report.hostId, {\n type: 'content.order',\n title: `A ${amount} card taken offline on {site} has no sale`,\n body:\n report.status === 'succeeded'\n ? `A ${amount} card taken offline was charged, but the register never sent its sale, so there is no order for it. ` +\n 'Open the register that took it, signed in as the member who rang it, so the sale syncs.'\n : `A ${amount} card taken offline is approved but not yet charged, because the register never sent its sale. ` +\n 'Open the register that took it, signed in as the member who rang it, so the sale syncs. ' +\n 'If it does not sync within about two days of the sale, the card is never charged.',\n link: `/${report.hostId}/products`,\n })\n } catch (error) {\n console.error('[pos-offline-card] unsynced report failed', report.hostId, report.saleKey, error)\n }\n}\n\n/** Tells the operator a card was made out to an account that is not the store's. Never throws. */\nexport async function reportPosOfflineCardWrongAccount(input: {\n hostId: string\n orderId: string\n paymentIntentId: string\n status: string\n amountCents: number\n destination: string\n expected: string\n}): Promise<void> {\n try {\n const host = await firebaseAdmin.app().firestore().collection('hosts').doc(input.hostId).get().catch(() => null)\n const site = host?.exists ? hostDisplayName(host.data() as Record<string, unknown>, input.hostId) : input.hostId\n await raiseOperatorAlert(COMMERCE_POS_OFFLINE_CARD_WRONG_ACCOUNT, {\n dedupeKey: `${input.hostId}:${input.orderId}`,\n hostId: input.hostId,\n context: {\n amount: formatOperatorAlertAmount(input.amountCents),\n paymentIntentId: input.paymentIntentId,\n status: input.status,\n orderId: input.orderId,\n site: `${site} (${input.hostId})`,\n destination: input.destination || 'none',\n expected: input.expected,\n },\n })\n } catch (error) {\n console.error('[pos-offline-card] wrong-account alert failed', input.hostId, input.orderId, error)\n }\n}\n\n/**\n * The hourly sweep (registered in `server.ts`): every offline card record\n * whose alert is due. Re-checks the sale first, so a sale that synced while\n * the event was in flight clears the record and tells nobody.\n *\n * One collection-group range query on `alertDueAtMs`, which is set only on a\n * record that holds money and has not synced, so the ordinary beat reads an\n * empty result. Its COLLECTION_GROUP index is declared in\n * `cloud/firebase-firestore.indexes.json` and pinned by\n * `cron-scan-indexes.spec.ts`.\n */\nexport async function scanUnsyncedOfflineCards(\n /** The lockdown gate, injected by the runner (AGL-2495). */\n gate: PluginJobHostGate,\n options: {\n firestore?: FirebaseFirestore.Firestore\n nowMs?: number\n limit?: number\n report?: (report: UnsyncedOfflineCardReport) => Promise<void>\n } = {},\n): Promise<UnsyncedOfflineCardScan> {\n const db = options.firestore ?? firebaseAdmin.app().firestore()\n const nowMs = options.nowMs ?? Date.now()\n const report = options.report ?? reportUnsyncedOfflineCard\n const scan: UnsyncedOfflineCardScan = { due: 0, alerted: 0, cleared: 0, skippedLocked: 0 }\n const due = await db\n .collectionGroup(POS_OFFLINE_CARD_CHARGES)\n .where('alertDueAtMs', '<=', nowMs)\n .orderBy('alertDueAtMs', 'asc')\n .limit(options.limit ?? 100)\n .get()\n .catch((error: unknown) => {\n console.error('[pos-offline-card] unsynced scan failed', error)\n return null\n })\n if (!due) return scan\n for (const doc of due.docs) {\n const record = (doc.data() ?? {}) as Partial<PosOfflineCardChargeRecord>\n if (typeof record.alertDueAtMs !== 'number') continue\n // `hosts/{hostId}/terminal/config/offlineCardCharges/{saleKey}`.\n const hostId = String(record.hostId || doc.ref.parent.parent?.parent?.parent?.id || '')\n const saleKey = String(record.saleKey || doc.id)\n if (!validHostId(hostId) || !posOfflineSaleKeyIsValid(saleKey)) continue\n scan.due += 1\n // A locked site is skipped, not dropped: its record stays due and is\n // told on the first beat after the lift.\n if (await gate.isLocked(hostId)) {\n scan.skippedLocked += 1\n continue\n }\n const hostRef = db.collection('hosts').doc(hostId)\n const synced = (await hostRef.collection('posOfflineSales').doc(saleKey).get()).exists\n if (synced || !HOLDS_MONEY.has(String(record.status ?? ''))) {\n await doc.ref.set(\n { ...(synced ? { synced: true } : {}), alertDueAtMs: FieldValue.delete() },\n { merge: true },\n )\n scan.cleared += 1\n continue\n }\n await report({\n hostId,\n saleKey,\n paymentIntentId: String(record.paymentIntentId ?? ''),\n status: String(record.status ?? ''),\n amountCents: Math.max(0, Math.round(Number(record.amountCents ?? 0))),\n registerId: String(record.registerId ?? ''),\n waitedMs: Math.max(0, nowMs - (record.alertDueAtMs - POS_OFFLINE_CARD_UNSYNCED_ALERT_MS)),\n })\n await doc.ref.set({ alertedAtMs: nowMs, alertDueAtMs: FieldValue.delete() }, { merge: true })\n scan.alerted += 1\n }\n return scan\n}\n"],"names":["formatOperatorAlertAmount","firebaseAdmin","hostDisplayName","notifyHostManagers","raiseOperatorAlert","FieldValue","COMMERCE_POS_OFFLINE_CARD_UNSYNCED","COMMERCE_POS_OFFLINE_CARD_WRONG_ACCOUNT","POS_OFFLINE_CARD_INTENT_KIND","posOfflineSaleKeyIsValid","POS_OFFLINE_CARD_CHARGES","POS_OFFLINE_CARD_UNSYNCED_ALERT_MS","HOLDS_MONEY","Set","FINAL","posOfflineCardChargeRef","hostRef","saleKey","collection","doc","validHostId","hostId","test","reconcilePosOfflineCardEvent","input","firestore","now","Date","intent","metadata","type","startsWith","object","String","db","app","chargeRef","saleRef","runTransaction","transaction","charge","get","sale","previous","exists","data","synced","incoming","status","has","at","alertDueAtMs","alertedAtMs","set","paymentIntentId","id","amountCents","Math","max","round","Number","amount","registerId","slice","livemode","Boolean","lastEvent","updatedAtMs","delete","merge","posOfflineCardBillingWebhookHandler","reconciled","claimed","undefined","describeWait","ms","hours","reportUnsyncedOfflineCard","report","host","catch","site","dedupeKey","context","age","waitedMs","title","body","link","error","console","reportPosOfflineCardWrongAccount","orderId","destination","expected","scanUnsyncedOfflineCards","gate","options","nowMs","scan","due","alerted","cleared","skippedLocked","collectionGroup","where","orderBy","limit","docs","record","ref","parent","isLocked"],"mappings":"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"}
@@ -85,13 +85,28 @@ export interface PosOfflineCardDeps {
85
85
  ok: false;
86
86
  error: string;
87
87
  }>;
88
+ /**
89
+ * Captures with the platform fee. A failure carries Stripe's HTTP status
90
+ * when Stripe answered; a request that never got an answer throws.
91
+ */
88
92
  capture(paymentIntentId: string, applicationFeeCents: number, idempotencyKey: string): Promise<{
89
93
  ok: true;
90
94
  intent: PosOfflineCardIntent;
91
95
  } | {
92
96
  ok: false;
93
97
  error: string;
98
+ status?: number;
94
99
  }>;
100
+ /** Tells the operator a card was made out to another account. Never throws. */
101
+ wrongAccount(input: {
102
+ hostId: string;
103
+ orderId: string;
104
+ paymentIntentId: string;
105
+ status: string;
106
+ amountCents: number;
107
+ destination: string;
108
+ expected: string;
109
+ }): Promise<void>;
95
110
  }
96
111
  export declare function defaultPosOfflineCardDeps(): PosOfflineCardDeps;
97
112
  export declare function defaultPosOfflineSyncDeps(): PosOfflineSyncDeps;
@@ -111,6 +126,30 @@ export declare function posInPersonTax(settings: CommerceModel.TaxSettings | und
111
126
  };
112
127
  /** The kit a register caches to sell offline. */
113
128
  export declare function buildPosOfflineKit(deps: PosOfflineSyncDeps, staff: PosOpsStaff): Promise<PosOfflineKit>;
129
+ /**
130
+ * How long after a capture that Stripe answered with a 5xx the sync waits
131
+ * before reading "still waiting for capture" as the definite answer.
132
+ */
133
+ export declare const POS_OFFLINE_CAPTURE_SETTLE_MS = 60000;
134
+ /**
135
+ * The capture's idempotency key (AGL-4039). Attempt 0 is the key every
136
+ * capture used before attempts existed, so a retry of one already in flight
137
+ * still finds it.
138
+ *
139
+ * WHY AN ATTEMPT. Stripe keeps the answer to an idempotent request for a day,
140
+ * failures included, so a capture that failed once on a per-sale key would
141
+ * fail the same way on every sync for a day, deep into the two days the
142
+ * authorization lasts. The attempt moves on only when the previous one
143
+ * DEFINITELY did not capture: Stripe refused it with a 4xx (not 409, a
144
+ * request with the same key still running, and not 429, rate limited), or it
145
+ * answered 5xx and the intent still waits for capture a minute later. A
146
+ * request that got no answer at all throws before anything is recorded, so it
147
+ * retries on the same key. Every sync reads the intent before it captures,
148
+ * and Stripe captures an intent once, so a new key can never capture twice.
149
+ */
150
+ export declare function posOfflineCaptureKey(hostId: string, saleKey: string, attempt: number): string;
151
+ /** A capture Stripe refused for good: retrying the same key would replay the refusal. */
152
+ export declare function posOfflineCaptureRefused(status: number | undefined): boolean;
114
153
  export declare function handlePosOfflineSync(deps: PosOfflineSyncDeps, req: PluginApiRequest): Promise<Outcome>;
115
154
  export declare function createPosOfflineSyncHandler(deps?: () => PosOfflineSyncDeps): PluginApiHandler;
116
155
  export declare const posOfflineSyncHandler: Aglyn.PluginApiHandler;