@aglyn/plugins-commerce 1.0.0-beta.232 → 1.0.0-beta.233

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 (228) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart-extras.d.ts +6 -0
  3. package/src/lib/components/cart-extras.js +4 -1
  4. package/src/lib/components/cart-extras.js.map +1 -1
  5. package/src/lib/components/cart-fulfillment.d.ts +117 -0
  6. package/src/lib/components/cart-fulfillment.js +367 -0
  7. package/src/lib/components/cart-fulfillment.js.map +1 -0
  8. package/src/lib/components/cart.js +52 -10
  9. package/src/lib/components/cart.js.map +1 -1
  10. package/src/lib/components/commerce-console-page.js +42 -1
  11. package/src/lib/components/commerce-console-page.js.map +1 -1
  12. package/src/lib/components/console/host-orders-card.component.js +38 -10
  13. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  14. package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
  15. package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
  16. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
  17. package/src/lib/components/console/locations-card.component.js +177 -3
  18. package/src/lib/components/console/locations-card.component.js.map +1 -1
  19. package/src/lib/components/console/order-detail-dialog.component.js +10 -0
  20. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  21. package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
  22. package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
  23. package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
  24. package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
  25. package/src/lib/components/console/order-offline-notice.component.js +53 -0
  26. package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
  27. package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
  28. package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
  30. package/src/lib/components/console/pos/pos-api.d.ts +24 -1
  31. package/src/lib/components/console/pos/pos-api.js +13 -0
  32. package/src/lib/components/console/pos/pos-api.js.map +1 -1
  33. package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
  34. package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
  35. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  36. package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
  37. package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
  38. package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
  39. package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
  40. package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
  41. package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
  42. package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
  43. package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
  44. package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
  45. package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
  46. package/src/lib/components/console/pos/use-pos-display.js +3 -2
  47. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  48. package/src/lib/components/console/pos-devices-card.component.js +2 -2
  49. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  50. package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
  51. package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
  52. package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
  53. package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
  54. package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
  55. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  56. package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
  57. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
  58. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
  59. package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
  60. package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
  61. package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
  62. package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
  63. package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
  64. package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
  65. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
  66. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
  67. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
  68. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
  69. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
  70. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
  71. package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
  72. package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
  73. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
  74. package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
  75. package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
  76. package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
  77. package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
  78. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
  79. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
  80. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
  81. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
  82. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
  83. package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
  84. package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
  85. package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
  86. package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
  87. package/src/lib/components/console/pos-offline/register-offline.js +24 -0
  88. package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
  89. package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
  90. package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
  91. package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
  92. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
  93. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
  94. package/src/lib/components/console/pos-page.component.js +174 -27
  95. package/src/lib/components/console/pos-page.component.js.map +1 -1
  96. package/src/lib/components/console/products-hub-card.component.js +35 -10
  97. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  98. package/src/lib/components/console/shipping-settings-card.component.js +1 -1
  99. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  100. package/src/lib/components/console/stock-movements-card.component.js +48 -16
  101. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  102. package/src/lib/components/console/store-zones.d.ts +8 -1
  103. package/src/lib/components/console/store-zones.js +11 -1
  104. package/src/lib/components/console/store-zones.js.map +1 -1
  105. package/src/lib/components/order-status.js +66 -0
  106. package/src/lib/components/order-status.js.map +1 -1
  107. package/src/lib/components/product-detail.js +42 -15
  108. package/src/lib/components/product-detail.js.map +1 -1
  109. package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
  110. package/src/lib/constants/local-fulfillment-list-query.js +84 -0
  111. package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
  112. package/src/lib/constants/orders-list-query.d.ts +7 -2
  113. package/src/lib/constants/orders-list-query.js +89 -8
  114. package/src/lib/constants/orders-list-query.js.map +1 -1
  115. package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
  116. package/src/lib/constants/pos-shift-list-query.js +115 -0
  117. package/src/lib/constants/pos-shift-list-query.js.map +1 -0
  118. package/src/lib/constants/product-list-query.d.ts +6 -1
  119. package/src/lib/constants/product-list-query.js +77 -8
  120. package/src/lib/constants/product-list-query.js.map +1 -1
  121. package/src/lib/constants/stock-movement-query.d.ts +3 -2
  122. package/src/lib/constants/stock-movement-query.js +52 -8
  123. package/src/lib/constants/stock-movement-query.js.map +1 -1
  124. package/src/lib/model/buyer-notifications.d.ts +5 -3
  125. package/src/lib/model/buyer-notifications.js +25 -4
  126. package/src/lib/model/buyer-notifications.js.map +1 -1
  127. package/src/lib/model/commerce-orders.d.ts +59 -0
  128. package/src/lib/model/commerce-orders.js.map +1 -1
  129. package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
  130. package/src/lib/model/commerce-pos-kiosk.js +147 -0
  131. package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
  132. package/src/lib/model/commerce-pos-offline.d.ts +234 -0
  133. package/src/lib/model/commerce-pos-offline.js +301 -0
  134. package/src/lib/model/commerce-pos-offline.js.map +1 -0
  135. package/src/lib/model/commerce-pos-ops.d.ts +16 -6
  136. package/src/lib/model/commerce-pos-ops.js +12 -4
  137. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  138. package/src/lib/model/commerce-pos.d.ts +13 -1
  139. package/src/lib/model/commerce-pos.js +5 -4
  140. package/src/lib/model/commerce-pos.js.map +1 -1
  141. package/src/lib/model/commerce-stock-holds.d.ts +24 -3
  142. package/src/lib/model/commerce-stock-holds.js +26 -8
  143. package/src/lib/model/commerce-stock-holds.js.map +1 -1
  144. package/src/lib/model/commerce.d.ts +3 -0
  145. package/src/lib/model/commerce.js.map +1 -1
  146. package/src/lib/model/index.d.ts +4 -0
  147. package/src/lib/model/index.js +4 -0
  148. package/src/lib/model/index.js.map +1 -1
  149. package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
  150. package/src/lib/model/local-fulfillment-settings.js +361 -0
  151. package/src/lib/model/local-fulfillment-settings.js.map +1 -0
  152. package/src/lib/model/order-list-fields.d.ts +10 -1
  153. package/src/lib/model/order-list-fields.js +1 -0
  154. package/src/lib/model/order-list-fields.js.map +1 -1
  155. package/src/lib/model/order-local-fulfillment.d.ts +131 -0
  156. package/src/lib/model/order-local-fulfillment.js +125 -0
  157. package/src/lib/model/order-local-fulfillment.js.map +1 -0
  158. package/src/lib/model/order-shipping-export.js +4 -0
  159. package/src/lib/model/order-shipping-export.js.map +1 -1
  160. package/src/lib/model/order-status-view.d.ts +20 -1
  161. package/src/lib/model/order-status-view.js +60 -6
  162. package/src/lib/model/order-status-view.js.map +1 -1
  163. package/src/lib/plugin-config.js +6 -2
  164. package/src/lib/plugin-config.js.map +1 -1
  165. package/src/lib/plugin.js +8 -0
  166. package/src/lib/plugin.js.map +1 -1
  167. package/src/lib/pos-kiosk-config.d.ts +26 -0
  168. package/src/lib/pos-kiosk-config.js +59 -0
  169. package/src/lib/pos-kiosk-config.js.map +1 -0
  170. package/src/lib/server/api-v1/openapi.js +8 -0
  171. package/src/lib/server/api-v1/openapi.js.map +1 -1
  172. package/src/lib/server/api-v1/order-view.d.ts +19 -0
  173. package/src/lib/server/api-v1/order-view.js +35 -0
  174. package/src/lib/server/api-v1/order-view.js.map +1 -1
  175. package/src/lib/server/billing-webhook.js +238 -87
  176. package/src/lib/server/billing-webhook.js.map +1 -1
  177. package/src/lib/server/cart-checkout.d.ts +2 -0
  178. package/src/lib/server/cart-checkout.js +173 -13
  179. package/src/lib/server/cart-checkout.js.map +1 -1
  180. package/src/lib/server/checkout-extras.js +18 -4
  181. package/src/lib/server/checkout-extras.js.map +1 -1
  182. package/src/lib/server/checkout.js +71 -14
  183. package/src/lib/server/checkout.js.map +1 -1
  184. package/src/lib/server/local-fulfillment-status.d.ts +86 -0
  185. package/src/lib/server/local-fulfillment-status.js +379 -0
  186. package/src/lib/server/local-fulfillment-status.js.map +1 -0
  187. package/src/lib/server/local-fulfillment.d.ts +190 -0
  188. package/src/lib/server/local-fulfillment.js +580 -0
  189. package/src/lib/server/local-fulfillment.js.map +1 -0
  190. package/src/lib/server/membership-register.js +3 -1
  191. package/src/lib/server/membership-register.js.map +1 -1
  192. package/src/lib/server/order-notifications.js +61 -0
  193. package/src/lib/server/order-notifications.js.map +1 -1
  194. package/src/lib/server/pos-display.d.ts +27 -1
  195. package/src/lib/server/pos-display.js +50 -9
  196. package/src/lib/server/pos-display.js.map +1 -1
  197. package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
  198. package/src/lib/server/pos-kiosk-principal.js +42 -0
  199. package/src/lib/server/pos-kiosk-principal.js.map +1 -0
  200. package/src/lib/server/pos-kiosk.d.ts +54 -0
  201. package/src/lib/server/pos-kiosk.js +972 -0
  202. package/src/lib/server/pos-kiosk.js.map +1 -0
  203. package/src/lib/server/pos-offline-sync.d.ts +85 -0
  204. package/src/lib/server/pos-offline-sync.js +765 -0
  205. package/src/lib/server/pos-offline-sync.js.map +1 -0
  206. package/src/lib/server/pos-order.js +22 -4
  207. package/src/lib/server/pos-order.js.map +1 -1
  208. package/src/lib/server/pos-payment.d.ts +6 -0
  209. package/src/lib/server/pos-payment.js +225 -6
  210. package/src/lib/server/pos-payment.js.map +1 -1
  211. package/src/lib/server/pos-print.js +5 -0
  212. package/src/lib/server/pos-print.js.map +1 -1
  213. package/src/lib/server/pos-return.js +27 -0
  214. package/src/lib/server/pos-return.js.map +1 -1
  215. package/src/lib/server/pos-sale.d.ts +6 -0
  216. package/src/lib/server/pos-sale.js +2 -0
  217. package/src/lib/server/pos-sale.js.map +1 -1
  218. package/src/lib/server/pos-shift.js +8 -1
  219. package/src/lib/server/pos-shift.js.map +1 -1
  220. package/src/lib/server/refund.js +3 -0
  221. package/src/lib/server/refund.js.map +1 -1
  222. package/src/lib/server/stock-hold.d.ts +9 -1
  223. package/src/lib/server/stock-hold.js +15 -7
  224. package/src/lib/server/stock-hold.js.map +1 -1
  225. package/src/lib/server.js +16 -0
  226. package/src/lib/server.js.map +1 -1
  227. package/src/lib/tenant-emails.js +161 -0
  228. package/src/lib/tenant-emails.js.map +1 -1
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import type { PluginApiHandler } from '@aglyn/aglyn/server';
17
+ import type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server';
18
18
  import * as CommerceModel from '../model';
19
19
  export declare function sha256(value: string): string;
20
20
  /** The one state document a register's display reads. */
@@ -47,6 +47,32 @@ export declare function resetPosDisplay(hostId: string, registerId: string): Pro
47
47
  export declare function finishPosDisplayReceipt(hostId: string, registerId: string): Promise<void>;
48
48
  /** Signs every display of a register out, and forgets its state. */
49
49
  export declare function revokePosDisplays(hostId: string, registerId: string): Promise<void>;
50
+ /** The store's name, logo and welcome line, for the idle and thank-you screens. */
51
+ export declare function displayBranding(hostId: string): Promise<{
52
+ name: string;
53
+ logoUrl: string;
54
+ logoDarkUrl: string;
55
+ message: string;
56
+ }>;
57
+ /** The display token on a request, or ''. */
58
+ export declare function displayToken(req: PluginApiRequest, body: Record<string, any>): string;
59
+ /** What a paired device's token opens. */
60
+ export interface PosDeviceToken {
61
+ hostId: string;
62
+ registerId: string;
63
+ ref: any;
64
+ /**
65
+ * A customer display, or a self-service kiosk (AGL-3623). A token minted
66
+ * before kiosks existed has no mode and is a display.
67
+ */
68
+ mode: CommerceModel.PosDeviceMode;
69
+ /** The staff member whose pairing code opened this device. */
70
+ createdBy: string;
71
+ /** The token document, for the caller that keeps device settings on it. */
72
+ data: Record<string, any>;
73
+ }
74
+ /** The register a live display token opens, or null. */
75
+ export declare function resolveDisplayToken(token: string): Promise<PosDeviceToken | null>;
50
76
  /**
51
77
  * `POST /api/commerce/pos-display`.
52
78
  *
@@ -135,7 +135,7 @@ async function registerExists(hostId, registerId) {
135
135
  if (!/^[A-Za-z0-9_-]{1,128}$/.test(registerId)) return false;
136
136
  return (await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).collection('registers').doc(registerId).get()).exists;
137
137
  }
138
- /** The store's name, logo and welcome line, for the idle and thank-you screens. */ async function displayBranding(hostId) {
138
+ /** The store's name, logo and welcome line, for the idle and thank-you screens. */ export async function displayBranding(hostId) {
139
139
  var _ref, _host_get, _resolveMediaSrc, _resolveMediaSrc1;
140
140
  const firestore = firebaseAdmin.app().firestore();
141
141
  const host = await firestore.collection('hosts').doc(hostId).get();
@@ -154,13 +154,13 @@ async function registerExists(hostId, registerId) {
154
154
  message: settings.displayMessage || 'Welcome'
155
155
  };
156
156
  }
157
- /** The display token on a request, or ''. */ function displayToken(req, body) {
157
+ /** The display token on a request, or ''. */ export function displayToken(req, body) {
158
158
  var _req_headers_xposdisplaytoken, _body_token;
159
159
  const header = String((_req_headers_xposdisplaytoken = req.headers['x-pos-display-token']) != null ? _req_headers_xposdisplaytoken : '');
160
160
  return (header || String((_body_token = body['token']) != null ? _body_token : '')).trim().slice(0, 128);
161
161
  }
162
- /** The register a live display token opens, or null. */ async function resolveDisplayToken(token) {
163
- var _snapshot_get, _snapshot_get1, _snapshot_get2;
162
+ /** The register a live display token opens, or null. */ export async function resolveDisplayToken(token) {
163
+ var _snapshot_get, _snapshot_get1, _snapshot_get2, _snapshot_get3, _snapshot_data;
164
164
  if (!/^[A-Za-z0-9_-]{40,128}$/.test(token)) return null;
165
165
  const ref = firebaseAdmin.app().firestore().collection(TOKENS).doc(sha256(token));
166
166
  const snapshot = await ref.get();
@@ -182,9 +182,15 @@ async function registerExists(hostId, registerId) {
182
182
  return {
183
183
  hostId,
184
184
  registerId,
185
- ref
185
+ ref,
186
+ mode: snapshot.get('mode') === 'kiosk' ? 'kiosk' : 'display',
187
+ createdBy: String((_snapshot_get3 = snapshot.get('createdBy')) != null ? _snapshot_get3 : ''),
188
+ data: (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {}
186
189
  };
187
190
  }
191
+ /** A token row's mode, `display` for one that predates kiosks. */ function tokenMode(doc) {
192
+ return doc.get('mode') === 'kiosk' ? 'kiosk' : 'display';
193
+ }
188
194
  /**
189
195
  * `POST /api/commerce/pos-display`.
190
196
  *
@@ -216,11 +222,25 @@ async function registerExists(hostId, registerId) {
216
222
  error: 'This display is not paired.'
217
223
  });
218
224
  if (action === 'forget') {
225
+ // A kiosk leaves kiosk mode behind a staff PIN (AGL-3623), through
226
+ // `commerce/pos-kiosk`, never by a customer's tap here.
227
+ if (display.mode === 'kiosk') {
228
+ return res.status(403).json({
229
+ error: 'Ask a staff member to unlock this kiosk.'
230
+ });
231
+ }
219
232
  await display.ref.delete();
220
233
  return res.status(200).json({
221
234
  ok: true
222
235
  });
223
236
  }
237
+ // A kiosk is not the register's customer display (AGL-3623): the
238
+ // basket the cashier is ringing up belongs to another customer.
239
+ if (display.mode === 'kiosk') {
240
+ return res.status(403).json({
241
+ error: 'This device is a self-service kiosk.'
242
+ });
243
+ }
224
244
  const snapshot = await stateRef(display.hostId, display.registerId).get();
225
245
  const state = snapshot.exists ? snapshot.data() : null;
226
246
  if (action === 'poll') {
@@ -268,6 +288,9 @@ async function pair(req, body, res) {
268
288
  });
269
289
  }
270
290
  const code = String((_body_code = body['code']) != null ? _body_code : '').replace(/\D/g, '');
291
+ // The device asks for the kind of screen it is; the code decides. A kiosk
292
+ // code opens a kiosk, and a display code never does (AGL-3623).
293
+ const wanted = body['mode'] === 'kiosk' ? 'kiosk' : 'display';
271
294
  if (code.length !== 6) return res.status(400).json({
272
295
  error: 'Enter the 6-digit code from the register.'
273
296
  });
@@ -285,17 +308,25 @@ async function pair(req, body, res) {
285
308
  if (!(expiresAtMs > Date.now())) return null;
286
309
  const hostId = String((_snapshot_get1 = snapshot.get('hostId')) != null ? _snapshot_get1 : '');
287
310
  const registerId = String((_snapshot_get2 = snapshot.get('registerId')) != null ? _snapshot_get2 : '');
311
+ const mode = snapshot.get('mode') === 'kiosk' ? 'kiosk' : 'display';
312
+ // The wrong kind of screen leaves the code spent: the register shows a
313
+ // new one for the device it meant.
314
+ if (mode !== wanted) return {
315
+ mismatch: mode
316
+ };
288
317
  transaction.set(firestore.collection(TOKENS).doc(sha256(token)), {
289
318
  hostId,
290
319
  registerId,
320
+ mode,
291
321
  createdAtMs: Date.now(),
292
322
  createdBy: String((_snapshot_get3 = snapshot.get('createdBy')) != null ? _snapshot_get3 : ''),
293
323
  lastSeenAtMs: Date.now(),
294
- label: String((_body_label = body['label']) != null ? _body_label : '').slice(0, 60) || 'Customer display'
324
+ label: String((_body_label = body['label']) != null ? _body_label : '').slice(0, 60) || (mode === 'kiosk' ? 'Self-service kiosk' : 'Customer display')
295
325
  });
296
326
  return {
297
327
  hostId,
298
- registerId
328
+ registerId,
329
+ mode
299
330
  };
300
331
  });
301
332
  if (!paired) {
@@ -303,8 +334,14 @@ async function pair(req, body, res) {
303
334
  error: 'That code is not valid. Show a new code on the register.'
304
335
  });
305
336
  }
337
+ if ('mismatch' in paired) {
338
+ return res.status(409).json({
339
+ error: paired.mismatch === 'kiosk' ? 'That code is for a self-service kiosk. Open the kiosk address, or show a display code on the register.' : 'That code is for a customer display. Choose Self-service kiosk on the register and show a new code.'
340
+ });
341
+ }
306
342
  return res.status(200).json({
307
343
  token,
344
+ mode: paired.mode,
308
345
  branding: await displayBranding(paired.hostId)
309
346
  });
310
347
  }
@@ -338,7 +375,9 @@ async function staffAction(req, body, action, res) {
338
375
  hostId,
339
376
  registerId,
340
377
  expiresAtMs,
341
- createdBy: gate.staff.uid
378
+ createdBy: gate.staff.uid,
379
+ // A kiosk code (AGL-3623) opens a self-service kiosk instead.
380
+ mode: body['mode'] === 'kiosk' ? 'kiosk' : 'display'
342
381
  });
343
382
  return true;
344
383
  });
@@ -377,9 +416,10 @@ async function staffAction(req, body, action, res) {
377
416
  return res.status(200).json({
378
417
  state,
379
418
  // A display that polled within the last two minutes is "connected".
419
+ // A kiosk is not the register's display (AGL-3623).
380
420
  connected: displays.docs.some((doc)=>{
381
421
  var _doc_get;
382
- return Date.now() - Number((_doc_get = doc.get('lastSeenAtMs')) != null ? _doc_get : 0) < 120000;
422
+ return tokenMode(doc) === 'display' && Date.now() - Number((_doc_get = doc.get('lastSeenAtMs')) != null ? _doc_get : 0) < 120000;
383
423
  })
384
424
  });
385
425
  }
@@ -392,6 +432,7 @@ async function staffAction(req, body, action, res) {
392
432
  return {
393
433
  id: doc.id.slice(0, 16),
394
434
  label: String((_doc_get = doc.get('label')) != null ? _doc_get : 'Customer display'),
435
+ mode: tokenMode(doc),
395
436
  createdAtMs: Number((_doc_get1 = doc.get('createdAtMs')) != null ? _doc_get1 : 0),
396
437
  lastSeenAtMs: Number((_doc_get2 = doc.get('lastSeenAtMs')) != null ? _doc_get2 : 0)
397
438
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-display.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash, randomBytes, randomInt } from 'crypto'\nimport { consumeRateLimit, firebaseAdmin, getPluginConfig, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { pluginSmsAvailable } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport * as CommerceModel from '../model'\nimport { posRegisterSettings } from '../plugin-config'\nimport { authorizePosStaff, posQueryBody, posRequestBody } from './pos-auth'\nimport { POS_CURRENCY } from './pos-stripe'\n\n/*==========================================\n * THE CUSTOMER DISPLAY (AGL-3608).\n *\n * A second screen — a tablet on a stand facing the customer — that shows the\n * basket, takes the tip and the receipt choice, and hands both back to the\n * cashier. It must work with NO staff sign-in on the customer's device, so it\n * holds nothing but a display token.\n *\n * ## Pairing\n *\n * The register asks for a six-digit code (`pairing-code`), good for ten\n * minutes and usable once. The display sends it (`pair`) and receives a\n * random 256-bit token. Only hashes are stored: `posDisplayPairings/{sha256}`\n * for the code and `posDisplayTokens/{sha256}` for the token, each naming the\n * one site and register it opens. Revoking a display, or removing its\n * register, kills the token. A six-digit space is small, so `pair` is rate\n * limited per address and a code dies at its first use.\n *\n * ## Why polling through this route, not a Firestore listener\n *\n * A Firestore listener needs a Firebase identity the rules can test, which on\n * the customer's device means minting a custom auth session — a credential\n * that would sit on an unattended tablet with the console's own SDK. Polling\n * this route once a second with the display token is the simplest thing that\n * is secure: the token opens exactly one register's display state and\n * nothing else, the state lives in a server-only collection\n * (`posDisplayStates`, denied to every client by the rules) so not even a\n * signed-in site member can read a customer's typed address out of it, and a\n * revoked token stops working on the next poll.\n *=========================================*/\n\nconst PAIRINGS = 'posDisplayPairings'\nconst TOKENS = 'posDisplayTokens'\nconst STATES = 'posDisplayStates'\n\nexport function sha256(value: string): string {\n return createHash('sha256').update(value).digest('hex')\n}\n\n/** The one state document a register's display reads. */\nexport function posDisplayStateId(hostId: string, registerId: string): string {\n return `${hostId}__${registerId}`\n}\n\nfunction stateRef(hostId: string, registerId: string) {\n return firebaseAdmin\n .app()\n .firestore()\n .collection(STATES)\n .doc(posDisplayStateId(hostId, registerId))\n}\n\n/**\n * The store's currency (`hosts/{hostId}/settings/store.currency`, lower\n * case), which every figure on the customer's screen is formatted in. A\n * store that never chose one is in the currency the register charges.\n */\nexport async function posDisplayCurrency(hostId: string): Promise<string> {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('settings')\n .doc('store')\n .get()\n .catch(() => null)\n const chosen = String(snapshot?.exists ? (snapshot.get('currency') ?? '') : '')\n .trim()\n .toLowerCase()\n return /^[a-z]{3}$/.test(chosen) ? chosen : POS_CURRENCY\n}\n\n/**\n * Writes the display state whole, so nothing from the last prompt lingers —\n * a customer's typed address included. The currency is the store's, stamped\n * here so the screen never formats a figure in one the register made up.\n */\nexport async function writePosDisplayState(\n hostId: string,\n registerId: string,\n state: CommerceModel.PosDisplayState,\n): Promise<void> {\n await stateRef(hostId, registerId).set({\n ...state,\n currency: await posDisplayCurrency(hostId),\n hostId,\n registerId,\n })\n}\n\n/** The register's display returns to the thank-you screen with no customer data. */\nexport async function resetPosDisplay(hostId: string, registerId: string): Promise<void> {\n await writePosDisplayState(hostId, registerId, { mode: 'thanks', updatedAtMs: Date.now() })\n}\n\n/**\n * Ends the customer's turn once the register has acted on their receipt\n * choice (AGL-3608): a display still on the receipt prompt says thank you,\n * and an answer holding a typed email address or phone number is dropped\n * from the state either way, so no contact detail outlives the sale on the\n * server doc a public screen reads.\n *\n * Conditional, in a transaction, because the receipt can be sent after the\n * cashier has started the next sale: a display already showing the next\n * customer's basket is left alone.\n */\nexport async function finishPosDisplayReceipt(hostId: string, registerId: string): Promise<void> {\n const firestore = firebaseAdmin.app().firestore()\n const ref = stateRef(hostId, registerId)\n const currency = await posDisplayCurrency(hostId)\n await firestore.runTransaction(async (transaction: any) => {\n const snapshot = await transaction.get(ref)\n if (!snapshot.exists) return\n const state = snapshot.data() as CommerceModel.PosDisplayState\n if (state.mode === 'receipt') {\n transaction.set(ref, {\n mode: 'thanks',\n updatedAtMs: Date.now(),\n currency,\n hostId,\n registerId,\n })\n return\n }\n if (state.response && (state.response.email || state.response.phone)) {\n const rest = { ...state }\n delete rest.response\n transaction.set(ref, rest)\n }\n })\n}\n\n/** Signs every display of a register out, and forgets its state. */\nexport async function revokePosDisplays(hostId: string, registerId: string): Promise<void> {\n const firestore = firebaseAdmin.app().firestore()\n const tokens = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(50)\n .get()\n await Promise.all(tokens.docs.map((doc: any) => doc.ref.delete()))\n await stateRef(hostId, registerId).delete().catch(() => undefined)\n}\n\nasync function registerExists(hostId: string, registerId: string): Promise<boolean> {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(registerId)) return false\n return (\n await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('registers')\n .doc(registerId)\n .get()\n ).exists\n}\n\n/** The store's name, logo and welcome line, for the idle and thank-you screens. */\nasync function displayBranding(hostId: string) {\n const firestore = firebaseAdmin.app().firestore()\n const host = await firestore.collection('hosts').doc(hostId).get()\n const ownerOrg = await getOrgForHost(hostId).catch(() => null)\n const settings = posRegisterSettings(\n await getPluginConfig(ownerOrg?.orgId, 'commerce', { hostId }).catch(() => ({})),\n )\n return {\n name: String(host.get('displayName') ?? host.get('name') ?? '').slice(0, 80) || 'Welcome',\n logoUrl: resolveMediaSrc(host.get('logoUrl'), { hostId }) ?? null,\n logoDarkUrl: resolveMediaSrc(host.get('logoDarkUrl'), { hostId }) ?? null,\n message: settings.displayMessage || 'Welcome',\n }\n}\n\n/** The display token on a request, or ''. */\nfunction displayToken(req: PluginApiRequest, body: Record<string, any>): string {\n const header = String(req.headers['x-pos-display-token'] ?? '')\n return (header || String(body['token'] ?? '')).trim().slice(0, 128)\n}\n\n/** The register a live display token opens, or null. */\nasync function resolveDisplayToken(\n token: string,\n): Promise<{ hostId: string; registerId: string; ref: any } | null> {\n if (!/^[A-Za-z0-9_-]{40,128}$/.test(token)) return null\n const ref = firebaseAdmin.app().firestore().collection(TOKENS).doc(sha256(token))\n const snapshot = await ref.get()\n if (!snapshot.exists) return null\n const hostId = String(snapshot.get('hostId') ?? '')\n const registerId = String(snapshot.get('registerId') ?? '')\n if (!hostId || !registerId) return null\n // A display whose register was removed is dead, even if its token row\n // somehow outlived the removal.\n if (!(await registerExists(hostId, registerId))) return null\n const lastSeen = Number(snapshot.get('lastSeenAtMs') ?? 0)\n if (Date.now() - lastSeen > 60_000) {\n await ref.set({ lastSeenAtMs: Date.now() }, { merge: true }).catch(() => undefined)\n }\n return { hostId, registerId, ref }\n}\n\n/**\n * `POST /api/commerce/pos-display`.\n *\n * Staff (Firebase token + the register's gate): `pairing-code`, `push`,\n * `state`, `displays`, `revoke`.\n * Display (display token only): `pair`, `poll`, `respond`, `forget`.\n */\nexport const posDisplayHandler: PluginApiHandler = async (req, res) => {\n // The two polls are GETs: a read is not a console write, so a display and a\n // register asking once a second do not spend the console's write budget.\n const body = req.method === 'GET' ? posQueryBody(req) : posRequestBody(req)\n const action = String(body['action'] ?? '')\n if (\n !(req.method === 'POST' || (req.method === 'GET' && (action === 'poll' || action === 'state')))\n ) {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n res.setHeader('Cache-Control', 'no-store')\n try {\n switch (action) {\n case 'pair':\n return await pair(req, body, res)\n case 'poll':\n case 'respond':\n case 'forget': {\n const display = await resolveDisplayToken(displayToken(req, body))\n if (!display) return res.status(401).json({ error: 'This display is not paired.' })\n if (action === 'forget') {\n await display.ref.delete()\n return res.status(200).json({ ok: true })\n }\n const snapshot = await stateRef(display.hostId, display.registerId).get()\n const state = snapshot.exists ? (snapshot.data() as CommerceModel.PosDisplayState) : null\n if (action === 'poll') {\n return res.status(200).json({\n state: CommerceModel.posDisplayPublicState(state, Date.now()),\n branding:\n body['branding'] === true || body['branding'] === 'true'\n ? await displayBranding(display.hostId)\n : undefined,\n })\n }\n const response = CommerceModel.sanitizePosDisplayResponse(state, body['response'], Date.now())\n if (!response) {\n return res.status(409).json({ error: 'That screen has moved on. Check the display.' })\n }\n await stateRef(display.hostId, display.registerId).set({ response }, { merge: true })\n return res.status(200).json({ ok: true })\n }\n default:\n return await staffAction(req, body, action, res)\n }\n } catch (error) {\n console.error('[pos-display]', action, error)\n return res.status(500).json({ error: 'The customer display could not be updated' })\n }\n}\n\nasync function pair(\n req: PluginApiRequest,\n body: Record<string, any>,\n res: Parameters<PluginApiHandler>[1],\n) {\n const address = String(req.socket?.remoteAddress ?? '') || 'no-address'\n const rate = await consumeRateLimit(`pos-display-pair:${address}`, {\n limit: 10,\n windowMs: 10 * 60 * 1000,\n })\n if (!rate.allowed) {\n return res.status(429).json({ error: 'Too many tries. Wait a few minutes and try again.' })\n }\n const code = String(body['code'] ?? '').replace(/\\D/g, '')\n if (code.length !== 6) return res.status(400).json({ error: 'Enter the 6-digit code from the register.' })\n const firestore = firebaseAdmin.app().firestore()\n const pairingRef = firestore.collection(PAIRINGS).doc(sha256(code))\n const token = randomBytes(32).toString('base64url')\n // Spent in the same transaction that mints the token: a code opens one\n // display, once, and a second display racing the first gets nothing.\n const paired = await firestore.runTransaction(async (transaction: any) => {\n const snapshot = await transaction.get(pairingRef)\n if (!snapshot.exists) return null\n const expiresAtMs = Number(snapshot.get('expiresAtMs') ?? 0)\n transaction.delete(pairingRef)\n if (!(expiresAtMs > Date.now())) return null\n const hostId = String(snapshot.get('hostId') ?? '')\n const registerId = String(snapshot.get('registerId') ?? '')\n transaction.set(firestore.collection(TOKENS).doc(sha256(token)), {\n hostId,\n registerId,\n createdAtMs: Date.now(),\n createdBy: String(snapshot.get('createdBy') ?? ''),\n lastSeenAtMs: Date.now(),\n label: String(body['label'] ?? '').slice(0, 60) || 'Customer display',\n })\n return { hostId, registerId }\n })\n if (!paired) {\n return res.status(404).json({ error: 'That code is not valid. Show a new code on the register.' })\n }\n return res.status(200).json({ token, branding: await displayBranding(paired.hostId) })\n}\n\nasync function staffAction(\n req: PluginApiRequest,\n body: Record<string, any>,\n action: string,\n res: Parameters<PluginApiHandler>[1],\n) {\n const hostId = String(body['hostId'] ?? '')\n const gate = await authorizePosStaff(req, hostId)\n if ('error' in gate) return res.status(gate.status).json({ error: gate.error })\n const registerId = String(body['registerId'] ?? '')\n if (!(await registerExists(hostId, registerId))) {\n return res.status(404).json({ error: 'Unknown register' })\n }\n const firestore = firebaseAdmin.app().firestore()\n switch (action) {\n case 'pairing-code': {\n for (let attempt = 0; attempt < 8; attempt++) {\n const code = String(randomInt(0, 1_000_000)).padStart(6, '0')\n const ref = firestore.collection(PAIRINGS).doc(sha256(code))\n const expiresAtMs = Date.now() + CommerceModel.POS_DISPLAY_PAIRING_TTL_MS\n const claimed = await firestore.runTransaction(async (transaction: any) => {\n const existing = await transaction.get(ref)\n // A live code another register is showing is never overwritten.\n if (existing.exists && Number(existing.get('expiresAtMs') ?? 0) > Date.now()) return false\n transaction.set(ref, {\n hostId,\n registerId,\n expiresAtMs,\n createdBy: gate.staff.uid,\n })\n return true\n })\n if (claimed) return res.status(200).json({ code, expiresAtMs })\n }\n return res.status(503).json({ error: 'Could not make a code. Try again.' })\n }\n case 'push': {\n const state = CommerceModel.sanitizePosDisplayState(body['state'], Date.now())\n // \"Text\" is never offered on a screen when the store cannot send one.\n if (state.receipt && !pluginSmsAvailable()) {\n const channels = state.receipt.channels.filter((channel) => channel !== 'sms')\n state.receipt.channels = channels.length ? channels : ['print', 'none']\n }\n await writePosDisplayState(hostId, registerId, state)\n return res.status(200).json({ ok: true, promptId: state.promptId ?? null })\n }\n case 'state': {\n const snapshot = await stateRef(hostId, registerId).get()\n const state = snapshot.exists ? (snapshot.data() as CommerceModel.PosDisplayState) : null\n const displays = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(10)\n .get()\n return res.status(200).json({\n state,\n // A display that polled within the last two minutes is \"connected\".\n connected: displays.docs.some(\n (doc: any) => Date.now() - Number(doc.get('lastSeenAtMs') ?? 0) < 120_000,\n ),\n })\n }\n case 'displays': {\n const displays = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(10)\n .get()\n return res.status(200).json({\n displays: displays.docs.map((doc: any) => ({\n id: doc.id.slice(0, 16),\n label: String(doc.get('label') ?? 'Customer display'),\n createdAtMs: Number(doc.get('createdAtMs') ?? 0),\n lastSeenAtMs: Number(doc.get('lastSeenAtMs') ?? 0),\n })),\n })\n }\n case 'revoke': {\n const prefix = String(body['displayId'] ?? '')\n const displays = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(10)\n .get()\n const doomed = displays.docs.filter(\n (doc: any) => prefix === '*' || (prefix.length === 16 && doc.id.startsWith(prefix)),\n )\n await Promise.all(doomed.map((doc: any) => doc.ref.delete()))\n return res.status(200).json({ revoked: doomed.length })\n }\n default:\n return res.status(400).json({ error: 'Unknown action' })\n }\n}\n"],"names":["createHash","randomBytes","randomInt","consumeRateLimit","firebaseAdmin","getPluginConfig","getOrgForHost","resolveMediaSrc","pluginSmsAvailable","CommerceModel","posRegisterSettings","authorizePosStaff","posQueryBody","posRequestBody","POS_CURRENCY","PAIRINGS","TOKENS","STATES","sha256","value","update","digest","posDisplayStateId","hostId","registerId","stateRef","app","firestore","collection","doc","posDisplayCurrency","snapshot","get","catch","chosen","String","exists","trim","toLowerCase","test","writePosDisplayState","state","set","currency","resetPosDisplay","mode","updatedAtMs","Date","now","finishPosDisplayReceipt","ref","runTransaction","transaction","data","response","email","phone","rest","revokePosDisplays","tokens","where","limit","Promise","all","docs","map","delete","undefined","registerExists","displayBranding","host","ownerOrg","settings","orgId","name","slice","logoUrl","logoDarkUrl","message","displayMessage","displayToken","req","body","header","headers","resolveDisplayToken","token","lastSeen","Number","lastSeenAtMs","merge","posDisplayHandler","res","method","action","status","json","error","setHeader","pair","display","ok","posDisplayPublicState","branding","sanitizePosDisplayResponse","staffAction","console","address","socket","remoteAddress","rate","windowMs","allowed","code","replace","length","pairingRef","toString","paired","expiresAtMs","createdAtMs","createdBy","label","gate","attempt","padStart","POS_DISPLAY_PAIRING_TTL_MS","claimed","existing","staff","uid","sanitizePosDisplayState","receipt","channels","filter","channel","promptId","displays","connected","some","id","prefix","doomed","startsWith","revoked"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-display.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash, randomBytes, randomInt } from 'crypto'\nimport { consumeRateLimit, firebaseAdmin, getPluginConfig, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { pluginSmsAvailable } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport * as CommerceModel from '../model'\nimport { posRegisterSettings } from '../plugin-config'\nimport { authorizePosStaff, posQueryBody, posRequestBody } from './pos-auth'\nimport { POS_CURRENCY } from './pos-stripe'\n\n/*==========================================\n * THE CUSTOMER DISPLAY (AGL-3608).\n *\n * A second screen — a tablet on a stand facing the customer — that shows the\n * basket, takes the tip and the receipt choice, and hands both back to the\n * cashier. It must work with NO staff sign-in on the customer's device, so it\n * holds nothing but a display token.\n *\n * ## Pairing\n *\n * The register asks for a six-digit code (`pairing-code`), good for ten\n * minutes and usable once. The display sends it (`pair`) and receives a\n * random 256-bit token. Only hashes are stored: `posDisplayPairings/{sha256}`\n * for the code and `posDisplayTokens/{sha256}` for the token, each naming the\n * one site and register it opens. Revoking a display, or removing its\n * register, kills the token. A six-digit space is small, so `pair` is rate\n * limited per address and a code dies at its first use.\n *\n * ## Why polling through this route, not a Firestore listener\n *\n * A Firestore listener needs a Firebase identity the rules can test, which on\n * the customer's device means minting a custom auth session — a credential\n * that would sit on an unattended tablet with the console's own SDK. Polling\n * this route once a second with the display token is the simplest thing that\n * is secure: the token opens exactly one register's display state and\n * nothing else, the state lives in a server-only collection\n * (`posDisplayStates`, denied to every client by the rules) so not even a\n * signed-in site member can read a customer's typed address out of it, and a\n * revoked token stops working on the next poll.\n *=========================================*/\n\nconst PAIRINGS = 'posDisplayPairings'\nconst TOKENS = 'posDisplayTokens'\nconst STATES = 'posDisplayStates'\n\nexport function sha256(value: string): string {\n return createHash('sha256').update(value).digest('hex')\n}\n\n/** The one state document a register's display reads. */\nexport function posDisplayStateId(hostId: string, registerId: string): string {\n return `${hostId}__${registerId}`\n}\n\nfunction stateRef(hostId: string, registerId: string) {\n return firebaseAdmin\n .app()\n .firestore()\n .collection(STATES)\n .doc(posDisplayStateId(hostId, registerId))\n}\n\n/**\n * The store's currency (`hosts/{hostId}/settings/store.currency`, lower\n * case), which every figure on the customer's screen is formatted in. A\n * store that never chose one is in the currency the register charges.\n */\nexport async function posDisplayCurrency(hostId: string): Promise<string> {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('settings')\n .doc('store')\n .get()\n .catch(() => null)\n const chosen = String(snapshot?.exists ? (snapshot.get('currency') ?? '') : '')\n .trim()\n .toLowerCase()\n return /^[a-z]{3}$/.test(chosen) ? chosen : POS_CURRENCY\n}\n\n/**\n * Writes the display state whole, so nothing from the last prompt lingers —\n * a customer's typed address included. The currency is the store's, stamped\n * here so the screen never formats a figure in one the register made up.\n */\nexport async function writePosDisplayState(\n hostId: string,\n registerId: string,\n state: CommerceModel.PosDisplayState,\n): Promise<void> {\n await stateRef(hostId, registerId).set({\n ...state,\n currency: await posDisplayCurrency(hostId),\n hostId,\n registerId,\n })\n}\n\n/** The register's display returns to the thank-you screen with no customer data. */\nexport async function resetPosDisplay(hostId: string, registerId: string): Promise<void> {\n await writePosDisplayState(hostId, registerId, { mode: 'thanks', updatedAtMs: Date.now() })\n}\n\n/**\n * Ends the customer's turn once the register has acted on their receipt\n * choice (AGL-3608): a display still on the receipt prompt says thank you,\n * and an answer holding a typed email address or phone number is dropped\n * from the state either way, so no contact detail outlives the sale on the\n * server doc a public screen reads.\n *\n * Conditional, in a transaction, because the receipt can be sent after the\n * cashier has started the next sale: a display already showing the next\n * customer's basket is left alone.\n */\nexport async function finishPosDisplayReceipt(hostId: string, registerId: string): Promise<void> {\n const firestore = firebaseAdmin.app().firestore()\n const ref = stateRef(hostId, registerId)\n const currency = await posDisplayCurrency(hostId)\n await firestore.runTransaction(async (transaction: any) => {\n const snapshot = await transaction.get(ref)\n if (!snapshot.exists) return\n const state = snapshot.data() as CommerceModel.PosDisplayState\n if (state.mode === 'receipt') {\n transaction.set(ref, {\n mode: 'thanks',\n updatedAtMs: Date.now(),\n currency,\n hostId,\n registerId,\n })\n return\n }\n if (state.response && (state.response.email || state.response.phone)) {\n const rest = { ...state }\n delete rest.response\n transaction.set(ref, rest)\n }\n })\n}\n\n/** Signs every display of a register out, and forgets its state. */\nexport async function revokePosDisplays(hostId: string, registerId: string): Promise<void> {\n const firestore = firebaseAdmin.app().firestore()\n const tokens = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(50)\n .get()\n await Promise.all(tokens.docs.map((doc: any) => doc.ref.delete()))\n await stateRef(hostId, registerId).delete().catch(() => undefined)\n}\n\nasync function registerExists(hostId: string, registerId: string): Promise<boolean> {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(registerId)) return false\n return (\n await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('registers')\n .doc(registerId)\n .get()\n ).exists\n}\n\n/** The store's name, logo and welcome line, for the idle and thank-you screens. */\nexport async function displayBranding(hostId: string) {\n const firestore = firebaseAdmin.app().firestore()\n const host = await firestore.collection('hosts').doc(hostId).get()\n const ownerOrg = await getOrgForHost(hostId).catch(() => null)\n const settings = posRegisterSettings(\n await getPluginConfig(ownerOrg?.orgId, 'commerce', { hostId }).catch(() => ({})),\n )\n return {\n name: String(host.get('displayName') ?? host.get('name') ?? '').slice(0, 80) || 'Welcome',\n logoUrl: resolveMediaSrc(host.get('logoUrl'), { hostId }) ?? null,\n logoDarkUrl: resolveMediaSrc(host.get('logoDarkUrl'), { hostId }) ?? null,\n message: settings.displayMessage || 'Welcome',\n }\n}\n\n/** The display token on a request, or ''. */\nexport function displayToken(req: PluginApiRequest, body: Record<string, any>): string {\n const header = String(req.headers['x-pos-display-token'] ?? '')\n return (header || String(body['token'] ?? '')).trim().slice(0, 128)\n}\n\n/** What a paired device's token opens. */\nexport interface PosDeviceToken {\n hostId: string\n registerId: string\n ref: any\n /**\n * A customer display, or a self-service kiosk (AGL-3623). A token minted\n * before kiosks existed has no mode and is a display.\n */\n mode: CommerceModel.PosDeviceMode\n /** The staff member whose pairing code opened this device. */\n createdBy: string\n /** The token document, for the caller that keeps device settings on it. */\n data: Record<string, any>\n}\n\n/** The register a live display token opens, or null. */\nexport async function resolveDisplayToken(token: string): Promise<PosDeviceToken | null> {\n if (!/^[A-Za-z0-9_-]{40,128}$/.test(token)) return null\n const ref = firebaseAdmin.app().firestore().collection(TOKENS).doc(sha256(token))\n const snapshot = await ref.get()\n if (!snapshot.exists) return null\n const hostId = String(snapshot.get('hostId') ?? '')\n const registerId = String(snapshot.get('registerId') ?? '')\n if (!hostId || !registerId) return null\n // A display whose register was removed is dead, even if its token row\n // somehow outlived the removal.\n if (!(await registerExists(hostId, registerId))) return null\n const lastSeen = Number(snapshot.get('lastSeenAtMs') ?? 0)\n if (Date.now() - lastSeen > 60_000) {\n await ref.set({ lastSeenAtMs: Date.now() }, { merge: true }).catch(() => undefined)\n }\n return {\n hostId,\n registerId,\n ref,\n mode: snapshot.get('mode') === 'kiosk' ? 'kiosk' : 'display',\n createdBy: String(snapshot.get('createdBy') ?? ''),\n data: (snapshot.data() ?? {}) as Record<string, any>,\n }\n}\n\n/** A token row's mode, `display` for one that predates kiosks. */\nfunction tokenMode(doc: any): CommerceModel.PosDeviceMode {\n return doc.get('mode') === 'kiosk' ? 'kiosk' : 'display'\n}\n\n/**\n * `POST /api/commerce/pos-display`.\n *\n * Staff (Firebase token + the register's gate): `pairing-code`, `push`,\n * `state`, `displays`, `revoke`.\n * Display (display token only): `pair`, `poll`, `respond`, `forget`.\n */\nexport const posDisplayHandler: PluginApiHandler = async (req, res) => {\n // The two polls are GETs: a read is not a console write, so a display and a\n // register asking once a second do not spend the console's write budget.\n const body = req.method === 'GET' ? posQueryBody(req) : posRequestBody(req)\n const action = String(body['action'] ?? '')\n if (\n !(req.method === 'POST' || (req.method === 'GET' && (action === 'poll' || action === 'state')))\n ) {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n res.setHeader('Cache-Control', 'no-store')\n try {\n switch (action) {\n case 'pair':\n return await pair(req, body, res)\n case 'poll':\n case 'respond':\n case 'forget': {\n const display = await resolveDisplayToken(displayToken(req, body))\n if (!display) return res.status(401).json({ error: 'This display is not paired.' })\n if (action === 'forget') {\n // A kiosk leaves kiosk mode behind a staff PIN (AGL-3623), through\n // `commerce/pos-kiosk`, never by a customer's tap here.\n if (display.mode === 'kiosk') {\n return res.status(403).json({ error: 'Ask a staff member to unlock this kiosk.' })\n }\n await display.ref.delete()\n return res.status(200).json({ ok: true })\n }\n // A kiosk is not the register's customer display (AGL-3623): the\n // basket the cashier is ringing up belongs to another customer.\n if (display.mode === 'kiosk') {\n return res.status(403).json({ error: 'This device is a self-service kiosk.' })\n }\n const snapshot = await stateRef(display.hostId, display.registerId).get()\n const state = snapshot.exists ? (snapshot.data() as CommerceModel.PosDisplayState) : null\n if (action === 'poll') {\n return res.status(200).json({\n state: CommerceModel.posDisplayPublicState(state, Date.now()),\n branding:\n body['branding'] === true || body['branding'] === 'true'\n ? await displayBranding(display.hostId)\n : undefined,\n })\n }\n const response = CommerceModel.sanitizePosDisplayResponse(state, body['response'], Date.now())\n if (!response) {\n return res.status(409).json({ error: 'That screen has moved on. Check the display.' })\n }\n await stateRef(display.hostId, display.registerId).set({ response }, { merge: true })\n return res.status(200).json({ ok: true })\n }\n default:\n return await staffAction(req, body, action, res)\n }\n } catch (error) {\n console.error('[pos-display]', action, error)\n return res.status(500).json({ error: 'The customer display could not be updated' })\n }\n}\n\nasync function pair(\n req: PluginApiRequest,\n body: Record<string, any>,\n res: Parameters<PluginApiHandler>[1],\n) {\n const address = String(req.socket?.remoteAddress ?? '') || 'no-address'\n const rate = await consumeRateLimit(`pos-display-pair:${address}`, {\n limit: 10,\n windowMs: 10 * 60 * 1000,\n })\n if (!rate.allowed) {\n return res.status(429).json({ error: 'Too many tries. Wait a few minutes and try again.' })\n }\n const code = String(body['code'] ?? '').replace(/\\D/g, '')\n // The device asks for the kind of screen it is; the code decides. A kiosk\n // code opens a kiosk, and a display code never does (AGL-3623).\n const wanted: CommerceModel.PosDeviceMode = body['mode'] === 'kiosk' ? 'kiosk' : 'display'\n if (code.length !== 6) return res.status(400).json({ error: 'Enter the 6-digit code from the register.' })\n const firestore = firebaseAdmin.app().firestore()\n const pairingRef = firestore.collection(PAIRINGS).doc(sha256(code))\n const token = randomBytes(32).toString('base64url')\n // Spent in the same transaction that mints the token: a code opens one\n // display, once, and a second display racing the first gets nothing.\n const paired = await firestore.runTransaction(async (transaction: any) => {\n const snapshot = await transaction.get(pairingRef)\n if (!snapshot.exists) return null\n const expiresAtMs = Number(snapshot.get('expiresAtMs') ?? 0)\n transaction.delete(pairingRef)\n if (!(expiresAtMs > Date.now())) return null\n const hostId = String(snapshot.get('hostId') ?? '')\n const registerId = String(snapshot.get('registerId') ?? '')\n const mode: CommerceModel.PosDeviceMode = snapshot.get('mode') === 'kiosk' ? 'kiosk' : 'display'\n // The wrong kind of screen leaves the code spent: the register shows a\n // new one for the device it meant.\n if (mode !== wanted) return { mismatch: mode }\n transaction.set(firestore.collection(TOKENS).doc(sha256(token)), {\n hostId,\n registerId,\n mode,\n createdAtMs: Date.now(),\n createdBy: String(snapshot.get('createdBy') ?? ''),\n lastSeenAtMs: Date.now(),\n label:\n String(body['label'] ?? '').slice(0, 60) ||\n (mode === 'kiosk' ? 'Self-service kiosk' : 'Customer display'),\n })\n return { hostId, registerId, mode }\n })\n if (!paired) {\n return res.status(404).json({ error: 'That code is not valid. Show a new code on the register.' })\n }\n if ('mismatch' in paired) {\n return res.status(409).json({\n error:\n paired.mismatch === 'kiosk'\n ? 'That code is for a self-service kiosk. Open the kiosk address, or show a display code on the register.'\n : 'That code is for a customer display. Choose Self-service kiosk on the register and show a new code.',\n })\n }\n return res\n .status(200)\n .json({ token, mode: paired.mode, branding: await displayBranding(paired.hostId) })\n}\n\nasync function staffAction(\n req: PluginApiRequest,\n body: Record<string, any>,\n action: string,\n res: Parameters<PluginApiHandler>[1],\n) {\n const hostId = String(body['hostId'] ?? '')\n const gate = await authorizePosStaff(req, hostId)\n if ('error' in gate) return res.status(gate.status).json({ error: gate.error })\n const registerId = String(body['registerId'] ?? '')\n if (!(await registerExists(hostId, registerId))) {\n return res.status(404).json({ error: 'Unknown register' })\n }\n const firestore = firebaseAdmin.app().firestore()\n switch (action) {\n case 'pairing-code': {\n for (let attempt = 0; attempt < 8; attempt++) {\n const code = String(randomInt(0, 1_000_000)).padStart(6, '0')\n const ref = firestore.collection(PAIRINGS).doc(sha256(code))\n const expiresAtMs = Date.now() + CommerceModel.POS_DISPLAY_PAIRING_TTL_MS\n const claimed = await firestore.runTransaction(async (transaction: any) => {\n const existing = await transaction.get(ref)\n // A live code another register is showing is never overwritten.\n if (existing.exists && Number(existing.get('expiresAtMs') ?? 0) > Date.now()) return false\n transaction.set(ref, {\n hostId,\n registerId,\n expiresAtMs,\n createdBy: gate.staff.uid,\n // A kiosk code (AGL-3623) opens a self-service kiosk instead.\n mode: body['mode'] === 'kiosk' ? 'kiosk' : 'display',\n })\n return true\n })\n if (claimed) return res.status(200).json({ code, expiresAtMs })\n }\n return res.status(503).json({ error: 'Could not make a code. Try again.' })\n }\n case 'push': {\n const state = CommerceModel.sanitizePosDisplayState(body['state'], Date.now())\n // \"Text\" is never offered on a screen when the store cannot send one.\n if (state.receipt && !pluginSmsAvailable()) {\n const channels = state.receipt.channels.filter((channel) => channel !== 'sms')\n state.receipt.channels = channels.length ? channels : ['print', 'none']\n }\n await writePosDisplayState(hostId, registerId, state)\n return res.status(200).json({ ok: true, promptId: state.promptId ?? null })\n }\n case 'state': {\n const snapshot = await stateRef(hostId, registerId).get()\n const state = snapshot.exists ? (snapshot.data() as CommerceModel.PosDisplayState) : null\n const displays = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(10)\n .get()\n return res.status(200).json({\n state,\n // A display that polled within the last two minutes is \"connected\".\n // A kiosk is not the register's display (AGL-3623).\n connected: displays.docs.some(\n (doc: any) =>\n tokenMode(doc) === 'display' && Date.now() - Number(doc.get('lastSeenAtMs') ?? 0) < 120_000,\n ),\n })\n }\n case 'displays': {\n const displays = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(10)\n .get()\n return res.status(200).json({\n displays: displays.docs.map((doc: any) => ({\n id: doc.id.slice(0, 16),\n label: String(doc.get('label') ?? 'Customer display'),\n mode: tokenMode(doc),\n createdAtMs: Number(doc.get('createdAtMs') ?? 0),\n lastSeenAtMs: Number(doc.get('lastSeenAtMs') ?? 0),\n })),\n })\n }\n case 'revoke': {\n const prefix = String(body['displayId'] ?? '')\n const displays = await firestore\n .collection(TOKENS)\n .where('hostId', '==', hostId)\n .where('registerId', '==', registerId)\n .limit(10)\n .get()\n const doomed = displays.docs.filter(\n (doc: any) => prefix === '*' || (prefix.length === 16 && doc.id.startsWith(prefix)),\n )\n await Promise.all(doomed.map((doc: any) => doc.ref.delete()))\n return res.status(200).json({ revoked: doomed.length })\n }\n default:\n return res.status(400).json({ error: 'Unknown action' })\n }\n}\n"],"names":["createHash","randomBytes","randomInt","consumeRateLimit","firebaseAdmin","getPluginConfig","getOrgForHost","resolveMediaSrc","pluginSmsAvailable","CommerceModel","posRegisterSettings","authorizePosStaff","posQueryBody","posRequestBody","POS_CURRENCY","PAIRINGS","TOKENS","STATES","sha256","value","update","digest","posDisplayStateId","hostId","registerId","stateRef","app","firestore","collection","doc","posDisplayCurrency","snapshot","get","catch","chosen","String","exists","trim","toLowerCase","test","writePosDisplayState","state","set","currency","resetPosDisplay","mode","updatedAtMs","Date","now","finishPosDisplayReceipt","ref","runTransaction","transaction","data","response","email","phone","rest","revokePosDisplays","tokens","where","limit","Promise","all","docs","map","delete","undefined","registerExists","displayBranding","host","ownerOrg","settings","orgId","name","slice","logoUrl","logoDarkUrl","message","displayMessage","displayToken","req","body","header","headers","resolveDisplayToken","token","lastSeen","Number","lastSeenAtMs","merge","createdBy","tokenMode","posDisplayHandler","res","method","action","status","json","error","setHeader","pair","display","ok","posDisplayPublicState","branding","sanitizePosDisplayResponse","staffAction","console","address","socket","remoteAddress","rate","windowMs","allowed","code","replace","wanted","length","pairingRef","toString","paired","expiresAtMs","mismatch","createdAtMs","label","gate","attempt","padStart","POS_DISPLAY_PAIRING_TTL_MS","claimed","existing","staff","uid","sanitizePosDisplayState","receipt","channels","filter","channel","promptId","displays","connected","some","id","prefix","doomed","startsWith","revoked"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginApiRequest } from '@aglyn/aglyn/server';
18
+ export interface PosKioskPrincipal {
19
+ /** The staff member who paired the kiosk; re-checked by the sale route. */
20
+ uid: string;
21
+ /** The one register the kiosk was paired to. */
22
+ registerId: string;
23
+ /** The kiosk's token hash prefix, for the order's record. */
24
+ deviceId: string;
25
+ }
26
+ /** A request the sale route reads as the kiosk's. */
27
+ export declare function withPosKioskPrincipal<T extends PluginApiRequest>(req: T, principal: PosKioskPrincipal): T;
28
+ /** The kiosk a request was made for in-process, or null. */
29
+ export declare function posKioskPrincipal(req: PluginApiRequest): PosKioskPrincipal | null;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*==========================================
17
+ * A SELF-SERVICE KIOSK'S SALE, AS THE SALE ROUTE SEES IT (AGL-3623).
18
+ *
19
+ * The kiosk route (`pos-kiosk.ts`) prices a customer's cart by calling the
20
+ * register's own sale route in-process, so a kiosk order is priced, taxed,
21
+ * discounted and fee'd by exactly the code every register sale is. It holds
22
+ * no Firebase session, so it hands the sale route this principal instead of
23
+ * an ID token: the staff member whose pairing code opened the kiosk, whose
24
+ * role, `managePos` and plan the sale route re-checks as it would theirs.
25
+ *
26
+ * The principal rides on a module-private Symbol. Nothing that arrives over
27
+ * HTTP — a header, a query, a JSON body — can carry one, so only code in
28
+ * this process that imports this module can make a request a kiosk's.
29
+ *=========================================*/ const PRINCIPAL = Symbol('aglyn.commerce.posKioskPrincipal');
30
+ /** A request the sale route reads as the kiosk's. */ export function withPosKioskPrincipal(req, principal) {
31
+ Object.defineProperty(req, PRINCIPAL, {
32
+ value: principal,
33
+ enumerable: false
34
+ });
35
+ return req;
36
+ }
37
+ /** The kiosk a request was made for in-process, or null. */ export function posKioskPrincipal(req) {
38
+ const value = req[PRINCIPAL];
39
+ return value && typeof value.uid === 'string' && value.uid ? value : null;
40
+ }
41
+
42
+ //# sourceMappingURL=pos-kiosk-principal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-kiosk-principal.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 { PluginApiRequest } from '@aglyn/aglyn/server'\n\n/*==========================================\n * A SELF-SERVICE KIOSK'S SALE, AS THE SALE ROUTE SEES IT (AGL-3623).\n *\n * The kiosk route (`pos-kiosk.ts`) prices a customer's cart by calling the\n * register's own sale route in-process, so a kiosk order is priced, taxed,\n * discounted and fee'd by exactly the code every register sale is. It holds\n * no Firebase session, so it hands the sale route this principal instead of\n * an ID token: the staff member whose pairing code opened the kiosk, whose\n * role, `managePos` and plan the sale route re-checks as it would theirs.\n *\n * The principal rides on a module-private Symbol. Nothing that arrives over\n * HTTP — a header, a query, a JSON body — can carry one, so only code in\n * this process that imports this module can make a request a kiosk's.\n *=========================================*/\n\nconst PRINCIPAL = Symbol('aglyn.commerce.posKioskPrincipal')\n\nexport interface PosKioskPrincipal {\n /** The staff member who paired the kiosk; re-checked by the sale route. */\n uid: string\n /** The one register the kiosk was paired to. */\n registerId: string\n /** The kiosk's token hash prefix, for the order's record. */\n deviceId: string\n}\n\n/** A request the sale route reads as the kiosk's. */\nexport function withPosKioskPrincipal<T extends PluginApiRequest>(\n req: T,\n principal: PosKioskPrincipal,\n): T {\n Object.defineProperty(req, PRINCIPAL, { value: principal, enumerable: false })\n return req\n}\n\n/** The kiosk a request was made for in-process, or null. */\nexport function posKioskPrincipal(req: PluginApiRequest): PosKioskPrincipal | null {\n const value = (req as unknown as Record<symbol, unknown>)[PRINCIPAL] as PosKioskPrincipal | undefined\n return value && typeof value.uid === 'string' && value.uid ? value : null\n}\n"],"names":["PRINCIPAL","Symbol","withPosKioskPrincipal","req","principal","Object","defineProperty","value","enumerable","posKioskPrincipal","uid"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;2CAa2C,GAE3C,MAAMA,YAAYC,OAAO;AAWzB,mDAAmD,GACnD,OAAO,SAASC,sBACdC,GAAM,EACNC,SAA4B;IAE5BC,OAAOC,cAAc,CAACH,KAAKH,WAAW;QAAEO,OAAOH;QAAWI,YAAY;IAAM;IAC5E,OAAOL;AACT;AAEA,0DAA0D,GAC1D,OAAO,SAASM,kBAAkBN,GAAqB;IACrD,MAAMI,QAAQ,AAACJ,GAA0C,CAACH,UAAU;IACpE,OAAOO,SAAS,OAAOA,MAAMG,GAAG,KAAK,YAAYH,MAAMG,GAAG,GAAGH,QAAQ;AACvE"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server';
18
+ import * as CommerceModel from '../model';
19
+ import { type PosDeviceToken } from './pos-display';
20
+ import { type PosLiftedOrder } from './pos-sale';
21
+ type Outcome = {
22
+ status: number;
23
+ body: Record<string, unknown>;
24
+ };
25
+ /** The prefix of a token row's id a kiosk's orders are stamped with. */
26
+ export declare function posKioskDeviceId(device: PosDeviceToken): string;
27
+ /**
28
+ * Whether a product may be sold at a kiosk at all: what the register sells,
29
+ * less what a customer cannot be handed at a counter — a subscription-only
30
+ * product, a gift card whose code needs issuing, and a digital download.
31
+ */
32
+ export declare function posKioskSellable(product: CommerceModel.HostProduct): boolean;
33
+ /** A product as the kiosk may show it: names, prices and choices, nothing else. */
34
+ export declare function posKioskProduct(id: string, product: CommerceModel.HostProduct, hostId: string): CommerceModel.PosKioskProduct;
35
+ /** An order as its kiosk reads it back; null for any order this kiosk did not open. */
36
+ export declare function posKioskSaleView(order: PosLiftedOrder | null, deviceId: string): CommerceModel.PosKioskSale | null;
37
+ /** A short-lived, signed "staff unlocked THIS kiosk". */
38
+ export declare function mintPosKioskUnlock(deviceId: string, memberUid: string, nowMs: number): {
39
+ unlock: string;
40
+ expiresAtMs: number;
41
+ };
42
+ /** The member a valid unlock names for this kiosk, or null. */
43
+ export declare function readPosKioskUnlock(token: unknown, deviceId: string, nowMs: number): string | null;
44
+ export interface PosKioskQueueEntry {
45
+ orderId: string;
46
+ number: number;
47
+ queuedAtMs: number;
48
+ totalCents: number;
49
+ itemCount: number;
50
+ }
51
+ export declare function handlePosKiosk(req: PluginApiRequest): Promise<Outcome>;
52
+ /** `GET|POST /api/commerce/pos-kiosk`. */
53
+ export declare const posKioskHandler: PluginApiHandler;
54
+ export {};