@aglyn/plugins-commerce 1.0.0-beta.277 → 1.0.0-beta.282

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 (212) hide show
  1. package/package.json +17 -14
  2. package/src/lib/components/commerce-console-page.js +33 -1
  3. package/src/lib/components/commerce-console-page.js.map +1 -1
  4. package/src/lib/components/console/payments-settings-card.component.js +1 -1
  5. package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
  6. package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
  7. package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
  8. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
  9. package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
  10. package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
  11. package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
  12. package/src/lib/components/console/pos-devices-card.component.js +5 -0
  13. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  14. package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
  15. package/src/lib/components/console/pos-hardware-card.component.js +456 -0
  16. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
  17. package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
  18. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
  19. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
  20. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
  21. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
  22. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
  23. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
  24. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
  25. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
  26. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
  27. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
  28. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
  29. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
  30. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
  31. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
  32. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
  33. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
  34. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
  35. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
  36. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
  37. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  38. package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
  39. package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
  40. package/src/lib/components/console/pos-page.component.js +6 -3
  41. package/src/lib/components/console/pos-page.component.js.map +1 -1
  42. package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
  43. package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
  44. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
  45. package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
  46. package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
  47. package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
  48. package/src/lib/components/console/pos-zones.d.ts +37 -3
  49. package/src/lib/components/console/pos-zones.js +11 -1
  50. package/src/lib/components/console/pos-zones.js.map +1 -1
  51. package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
  52. package/src/lib/components/console/product-check-in-fields.component.js +172 -0
  53. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
  54. package/src/lib/components/console/product-editor-dialog.component.js +48 -9
  55. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  56. package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
  57. package/src/lib/components/console/product-register-fields.component.js +39 -1
  58. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  59. package/src/lib/components/console/products-hub-card.component.js +21 -6
  60. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  61. package/src/lib/components/console/reservations-card.component.js +239 -70
  62. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  63. package/src/lib/components/reservation-widget.js +16 -0
  64. package/src/lib/components/reservation-widget.js.map +1 -1
  65. package/src/lib/constants/operator-alerts.d.ts +6 -0
  66. package/src/lib/constants/operator-alerts.js +20 -1
  67. package/src/lib/constants/operator-alerts.js.map +1 -1
  68. package/src/lib/declarations.console-server.js +7 -0
  69. package/src/lib/declarations.console-server.js.map +1 -1
  70. package/src/lib/declarations.d.ts +26 -0
  71. package/src/lib/declarations.js +34 -0
  72. package/src/lib/declarations.js.map +1 -0
  73. package/src/lib/model/commerce-check-in-product.d.ts +37 -0
  74. package/src/lib/model/commerce-check-in-product.js +49 -0
  75. package/src/lib/model/commerce-check-in-product.js.map +1 -0
  76. package/src/lib/model/commerce-check-in.d.ts +187 -0
  77. package/src/lib/model/commerce-check-in.js +468 -0
  78. package/src/lib/model/commerce-check-in.js.map +1 -0
  79. package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
  80. package/src/lib/model/commerce-pos-day-report.js +572 -0
  81. package/src/lib/model/commerce-pos-day-report.js.map +1 -0
  82. package/src/lib/model/commerce-pos-offline.d.ts +65 -3
  83. package/src/lib/model/commerce-pos-offline.js +104 -7
  84. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  85. package/src/lib/model/commerce-pos-ops.d.ts +3 -0
  86. package/src/lib/model/commerce-pos-ops.js +1 -0
  87. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  88. package/src/lib/model/commerce-pos.d.ts +43 -3
  89. package/src/lib/model/commerce-pos.js +73 -5
  90. package/src/lib/model/commerce-pos.js.map +1 -1
  91. package/src/lib/model/commerce-reservations.d.ts +73 -7
  92. package/src/lib/model/commerce-reservations.js +90 -10
  93. package/src/lib/model/commerce-reservations.js.map +1 -1
  94. package/src/lib/model/commerce.d.ts +40 -0
  95. package/src/lib/model/commerce.js +22 -4
  96. package/src/lib/model/commerce.js.map +1 -1
  97. package/src/lib/model/index.d.ts +1 -0
  98. package/src/lib/model/index.js +1 -0
  99. package/src/lib/model/index.js.map +1 -1
  100. package/src/lib/model/plugin-permissions.d.ts +1 -1
  101. package/src/lib/model/plugin-permissions.js +2 -1
  102. package/src/lib/model/plugin-permissions.js.map +1 -1
  103. package/src/lib/model/pos-hardware.d.ts +288 -0
  104. package/src/lib/model/pos-hardware.js +499 -0
  105. package/src/lib/model/pos-hardware.js.map +1 -0
  106. package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
  107. package/src/lib/model/pos-offline-card-limits.js +28 -0
  108. package/src/lib/model/pos-offline-card-limits.js.map +1 -0
  109. package/src/lib/model/pos-reader-splash.d.ts +114 -0
  110. package/src/lib/model/pos-reader-splash.js +141 -0
  111. package/src/lib/model/pos-reader-splash.js.map +1 -0
  112. package/src/lib/model/product-write.d.ts +6 -0
  113. package/src/lib/plugin-config.d.ts +10 -0
  114. package/src/lib/plugin-config.js +40 -0
  115. package/src/lib/plugin-config.js.map +1 -1
  116. package/src/lib/plugin.js +28 -0
  117. package/src/lib/plugin.js.map +1 -1
  118. package/src/lib/server/cart-checkout.js +3 -1
  119. package/src/lib/server/cart-checkout.js.map +1 -1
  120. package/src/lib/server/cart.js +12 -1
  121. package/src/lib/server/cart.js.map +1 -1
  122. package/src/lib/server/catalog.js +49 -12
  123. package/src/lib/server/catalog.js.map +1 -1
  124. package/src/lib/server/checkout-extras.js +1 -1
  125. package/src/lib/server/checkout-extras.js.map +1 -1
  126. package/src/lib/server/checkout.js +7 -0
  127. package/src/lib/server/checkout.js.map +1 -1
  128. package/src/lib/server/person-eraser.d.ts +8 -1
  129. package/src/lib/server/person-eraser.js +23 -1
  130. package/src/lib/server/person-eraser.js.map +1 -1
  131. package/src/lib/server/pos-check-in.d.ts +49 -0
  132. package/src/lib/server/pos-check-in.js +652 -0
  133. package/src/lib/server/pos-check-in.js.map +1 -0
  134. package/src/lib/server/pos-day-report.d.ts +46 -0
  135. package/src/lib/server/pos-day-report.js +299 -0
  136. package/src/lib/server/pos-day-report.js.map +1 -0
  137. package/src/lib/server/pos-hardware-admin.d.ts +50 -0
  138. package/src/lib/server/pos-hardware-admin.js +366 -0
  139. package/src/lib/server/pos-hardware-admin.js.map +1 -0
  140. package/src/lib/server/pos-hardware-notify.d.ts +27 -0
  141. package/src/lib/server/pos-hardware-notify.js +142 -0
  142. package/src/lib/server/pos-hardware-notify.js.map +1 -0
  143. package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
  144. package/src/lib/server/pos-hardware-user-eraser.js +67 -0
  145. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
  146. package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
  147. package/src/lib/server/pos-hardware-webhook.js +176 -0
  148. package/src/lib/server/pos-hardware-webhook.js.map +1 -0
  149. package/src/lib/server/pos-hardware.d.ts +58 -0
  150. package/src/lib/server/pos-hardware.js +234 -0
  151. package/src/lib/server/pos-hardware.js.map +1 -0
  152. package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
  153. package/src/lib/server/pos-offline-card-reconcile.js +58 -0
  154. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
  155. package/src/lib/server/pos-offline-sync.d.ts +32 -0
  156. package/src/lib/server/pos-offline-sync.js +191 -15
  157. package/src/lib/server/pos-offline-sync.js.map +1 -1
  158. package/src/lib/server/pos-ops-routes.d.ts +4 -2
  159. package/src/lib/server/pos-ops-routes.js +14 -2
  160. package/src/lib/server/pos-ops-routes.js.map +1 -1
  161. package/src/lib/server/pos-order.js +39 -51
  162. package/src/lib/server/pos-order.js.map +1 -1
  163. package/src/lib/server/pos-payment.js +8 -4
  164. package/src/lib/server/pos-payment.js.map +1 -1
  165. package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
  166. package/src/lib/server/pos-reader-splash-admin.js +295 -0
  167. package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
  168. package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
  169. package/src/lib/server/pos-reader-splash-render.js +138 -0
  170. package/src/lib/server/pos-reader-splash-render.js.map +1 -0
  171. package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
  172. package/src/lib/server/pos-reader-splash-server.js +32 -0
  173. package/src/lib/server/pos-reader-splash-server.js.map +1 -0
  174. package/src/lib/server/pos-reader-splash.d.ts +105 -0
  175. package/src/lib/server/pos-reader-splash.js +548 -0
  176. package/src/lib/server/pos-reader-splash.js.map +1 -0
  177. package/src/lib/server/pos-sale.d.ts +2 -1
  178. package/src/lib/server/pos-sale.js +3 -1
  179. package/src/lib/server/pos-sale.js.map +1 -1
  180. package/src/lib/server/pos-terminal.d.ts +67 -4
  181. package/src/lib/server/pos-terminal.js +239 -32
  182. package/src/lib/server/pos-terminal.js.map +1 -1
  183. package/src/lib/server/process-restock.js +4 -0
  184. package/src/lib/server/process-restock.js.map +1 -1
  185. package/src/lib/server/product-card.js +2 -2
  186. package/src/lib/server/product-card.js.map +1 -1
  187. package/src/lib/server/product-catalog.d.ts +2 -0
  188. package/src/lib/server/product-catalog.js +11 -4
  189. package/src/lib/server/product-catalog.js.map +1 -1
  190. package/src/lib/server/product.js +3 -1
  191. package/src/lib/server/product.js.map +1 -1
  192. package/src/lib/server/related.js +8 -1
  193. package/src/lib/server/related.js.map +1 -1
  194. package/src/lib/server/reservation-availability-query.d.ts +35 -0
  195. package/src/lib/server/reservation-availability-query.js +34 -0
  196. package/src/lib/server/reservation-availability-query.js.map +1 -0
  197. package/src/lib/server/reservation-availability.d.ts +21 -0
  198. package/src/lib/server/reservation-availability.js +64 -10
  199. package/src/lib/server/reservation-availability.js.map +1 -1
  200. package/src/lib/server/reservation-walk-in.d.ts +28 -0
  201. package/src/lib/server/reservation-walk-in.js +166 -0
  202. package/src/lib/server/reservation-walk-in.js.map +1 -0
  203. package/src/lib/server/reserve.js +13 -10
  204. package/src/lib/server/reserve.js.map +1 -1
  205. package/src/lib/server/reviews.js +3 -1
  206. package/src/lib/server/reviews.js.map +1 -1
  207. package/src/lib/server/site-page-resolver.js +5 -2
  208. package/src/lib/server/site-page-resolver.js.map +1 -1
  209. package/src/lib/server.js +26 -0
  210. package/src/lib/server.js.map +1 -1
  211. package/src/lib/testing/fake-firestore.js +72 -0
  212. package/src/lib/testing/fake-firestore.js.map +1 -1
@@ -0,0 +1,114 @@
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
+ export type PosReaderSplashChoice = 'platform' | 'logo' | 'image';
18
+ export declare const POS_READER_SPLASH_CHOICES: readonly PosReaderSplashChoice[];
19
+ export declare function isPosReaderSplashChoice(value: unknown): value is PosReaderSplashChoice;
20
+ /** A group of readers that share one screen size, so one image. */
21
+ export type PosReaderSplashFamilyId = 'portrait-large' | 'portrait-small';
22
+ export interface PosReaderSplashFamily {
23
+ id: PosReaderSplashFamilyId;
24
+ /** The readers in it, for a person. */
25
+ label: string;
26
+ width: number;
27
+ height: number;
28
+ /** The Terminal Configuration keys that take this family's image. */
29
+ deviceTypes: readonly string[];
30
+ }
31
+ export declare const POS_READER_SPLASH_FAMILIES: readonly PosReaderSplashFamily[];
32
+ export declare function posReaderSplashFamily(id: unknown): PosReaderSplashFamily | null;
33
+ /** Every Terminal Configuration device key a start-up screen is set on. */
34
+ export declare const POS_READER_SPLASH_DEVICE_TYPES: readonly string[];
35
+ /** Stripe's limit is "less than 2 MB"; read as decimal megabytes, the smaller. */
36
+ export declare const POS_READER_SPLASH_MAX_BYTES = 2000000;
37
+ /** What Stripe takes for a still start-up screen. */
38
+ export declare const POS_READER_SPLASH_CONTENT_TYPES: readonly string[];
39
+ /** What a merchant's own full-screen image may be picked as; it is re-encoded. */
40
+ export declare const POS_READER_SPLASH_SOURCE_TYPES: readonly string[];
41
+ /** The largest source read to draw a screen from: a logo or a photo, never a video. */
42
+ export declare const POS_READER_SPLASH_SOURCE_MAX_BYTES = 15000000;
43
+ /** The choice a store gets before anyone picks one. */
44
+ export declare function posReaderSplashDefaultChoice(whiteLabel: boolean): PosReaderSplashChoice;
45
+ /**
46
+ * The choice that is applied: the stored one when it is valid, the default
47
+ * otherwise, and never `platform` for a white-label workspace (one that
48
+ * stored it before it was white-labeled gets its logo instead).
49
+ */
50
+ export declare function posReaderSplashEffectiveChoice(stored: unknown, whiteLabel: boolean): PosReaderSplashChoice;
51
+ export interface PosReaderSplashImageFacts {
52
+ contentType: string;
53
+ bytes: number;
54
+ width: number;
55
+ height: number;
56
+ }
57
+ /**
58
+ * Why an image cannot be sent to Stripe as `family`'s start-up screen, or
59
+ * null when it can: the exact screen size, JPEG or PNG, under 2 MB. Staff's
60
+ * default screens are checked with this as they are uploaded, and every
61
+ * rendered screen again before it leaves.
62
+ */
63
+ export declare function posReaderSplashImageProblem(facts: PosReaderSplashImageFacts, family: PosReaderSplashFamily): string | null;
64
+ /** The largest screen, which a merchant's full-screen image must cover. */
65
+ export declare const POS_READER_SPLASH_LARGEST: PosReaderSplashFamily;
66
+ /**
67
+ * Why a merchant's own image cannot be a full-screen start-up screen, or null
68
+ * when it can: a PNG, JPEG or WebP, portrait 9:16 (a 1% slack, since a
69
+ * designer's export is often a pixel off), at least as large as the largest
70
+ * screen so no reader shows it enlarged.
71
+ */
72
+ export declare function posReaderSplashArtworkProblem(facts: {
73
+ contentType?: string | null;
74
+ bytes?: number | null;
75
+ width: number;
76
+ height: number;
77
+ }): string | null;
78
+ /** `hosts/{hostId}/terminal/config.splash`: the store's choice. */
79
+ export interface PosReaderSplashSetting {
80
+ choice: PosReaderSplashChoice;
81
+ /** For `image`: the picked asset, as the media library names it. */
82
+ mediaScope?: string;
83
+ mediaId?: string;
84
+ updatedAtMs: number;
85
+ updatedBy?: string;
86
+ }
87
+ /** What the POS devices card shows about the store's start-up screen. */
88
+ export interface PosReaderSplashStatus {
89
+ /** Whether card readers are offered at all; the setting is hidden otherwise. */
90
+ available: boolean;
91
+ /** The stored choice, or null when none was made. */
92
+ choice: PosReaderSplashChoice | null;
93
+ /** What is applied, after the default and the white-label rule. */
94
+ effectiveChoice: PosReaderSplashChoice;
95
+ whiteLabel: boolean;
96
+ /** The brand a white-label workspace or the platform goes by, for copy. */
97
+ productName: string;
98
+ /** Whether staff have uploaded the platform's default screen. */
99
+ platformReady: boolean;
100
+ /** The workspace logo that `logo` would use, or null when there is none. */
101
+ logoUrl: string | null;
102
+ /** The asset `image` uses, when one was picked, with an address to preview it. */
103
+ image: {
104
+ mediaScope: string;
105
+ mediaId: string;
106
+ url: string | null;
107
+ } | null;
108
+ /** Whether the store has a Terminal Location yet (its first reader makes it). */
109
+ locationReady: boolean;
110
+ /** When the current choice last reached Stripe. */
111
+ appliedAtMs: number | null;
112
+ /** Why the current choice could not be applied, when it could not. */
113
+ problem: string | null;
114
+ }
@@ -0,0 +1,141 @@
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 CARD READER'S START-UP SCREEN (AGL-4048).
18
+ *
19
+ * Stripe's smart readers show a "splash screen" while they wait for a
20
+ * payment. Stripe sets it per device type on a Terminal Configuration, from a
21
+ * File uploaded with purpose `terminal_reader_splashscreen`, and a
22
+ * Configuration is assigned per Location, so each store's readers can carry
23
+ * their own. The rules Stripe states for the image, and the only ones this
24
+ * module enforces:
25
+ *
26
+ * - JPEG or PNG under 2 MB (a GIF may be 4 MB, but it is re-encoded here,
27
+ * so it is not offered);
28
+ * - cropped to the reader's own screen: 1080 × 1920 on the Stripe Reader
29
+ * S700 and S710, 720 × 1280 on the BBPOS WisePOS E.
30
+ *
31
+ * What a store shows is one of three CHOICES:
32
+ *
33
+ * - `platform` the platform's default screen, which staff upload;
34
+ * - `logo` the workspace's own logo, centered on a white screen;
35
+ * - `image` a full-screen portrait image the merchant picked from
36
+ * their media library.
37
+ *
38
+ * A white-label workspace defaults to `logo` and can never be given
39
+ * `platform`: its readers never show the platform's brand.
40
+ *
41
+ * Shared by the console and the server, so this module imports nothing.
42
+ *=========================================*/ export const POS_READER_SPLASH_CHOICES = [
43
+ 'platform',
44
+ 'logo',
45
+ 'image'
46
+ ];
47
+ export function isPosReaderSplashChoice(value) {
48
+ return typeof value === 'string' && POS_READER_SPLASH_CHOICES.includes(value);
49
+ }
50
+ export const POS_READER_SPLASH_FAMILIES = [
51
+ {
52
+ id: 'portrait-large',
53
+ label: 'Stripe Reader S700 and S710',
54
+ width: 1080,
55
+ height: 1920,
56
+ deviceTypes: [
57
+ 'stripe_s700',
58
+ 'stripe_s710'
59
+ ]
60
+ },
61
+ {
62
+ id: 'portrait-small',
63
+ label: 'BBPOS WisePOS E',
64
+ width: 720,
65
+ height: 1280,
66
+ deviceTypes: [
67
+ 'bbpos_wisepos_e'
68
+ ]
69
+ }
70
+ ];
71
+ export function posReaderSplashFamily(id) {
72
+ var _POS_READER_SPLASH_FAMILIES_find;
73
+ return (_POS_READER_SPLASH_FAMILIES_find = POS_READER_SPLASH_FAMILIES.find((family)=>family.id === id)) != null ? _POS_READER_SPLASH_FAMILIES_find : null;
74
+ }
75
+ /** Every Terminal Configuration device key a start-up screen is set on. */ export const POS_READER_SPLASH_DEVICE_TYPES = POS_READER_SPLASH_FAMILIES.flatMap((family)=>family.deviceTypes);
76
+ /** Stripe's limit is "less than 2 MB"; read as decimal megabytes, the smaller. */ export const POS_READER_SPLASH_MAX_BYTES = 2000000;
77
+ /** What Stripe takes for a still start-up screen. */ export const POS_READER_SPLASH_CONTENT_TYPES = [
78
+ 'image/png',
79
+ 'image/jpeg'
80
+ ];
81
+ /** What a merchant's own full-screen image may be picked as; it is re-encoded. */ export const POS_READER_SPLASH_SOURCE_TYPES = [
82
+ 'image/png',
83
+ 'image/jpeg',
84
+ 'image/webp'
85
+ ];
86
+ /** The largest source read to draw a screen from: a logo or a photo, never a video. */ export const POS_READER_SPLASH_SOURCE_MAX_BYTES = 15000000;
87
+ /** The choice a store gets before anyone picks one. */ export function posReaderSplashDefaultChoice(whiteLabel) {
88
+ return whiteLabel ? 'logo' : 'platform';
89
+ }
90
+ /**
91
+ * The choice that is applied: the stored one when it is valid, the default
92
+ * otherwise, and never `platform` for a white-label workspace (one that
93
+ * stored it before it was white-labeled gets its logo instead).
94
+ */ export function posReaderSplashEffectiveChoice(stored, whiteLabel) {
95
+ const choice = isPosReaderSplashChoice(stored) ? stored : posReaderSplashDefaultChoice(whiteLabel);
96
+ return whiteLabel && choice === 'platform' ? 'logo' : choice;
97
+ }
98
+ /**
99
+ * Why an image cannot be sent to Stripe as `family`'s start-up screen, or
100
+ * null when it can: the exact screen size, JPEG or PNG, under 2 MB. Staff's
101
+ * default screens are checked with this as they are uploaded, and every
102
+ * rendered screen again before it leaves.
103
+ */ export function posReaderSplashImageProblem(facts, family) {
104
+ if (!POS_READER_SPLASH_CONTENT_TYPES.includes(facts.contentType)) {
105
+ return 'Use a PNG or JPEG image.';
106
+ }
107
+ if (facts.width !== family.width || facts.height !== family.height) {
108
+ return `The ${family.label} screen is ${family.width} × ${family.height} pixels; this image is ${facts.width} × ${facts.height}.`;
109
+ }
110
+ if (!(facts.bytes > 0) || facts.bytes >= POS_READER_SPLASH_MAX_BYTES) {
111
+ return 'Use an image under 2 MB.';
112
+ }
113
+ return null;
114
+ }
115
+ /** The largest screen, which a merchant's full-screen image must cover. */ export const POS_READER_SPLASH_LARGEST = POS_READER_SPLASH_FAMILIES[0];
116
+ /**
117
+ * Why a merchant's own image cannot be a full-screen start-up screen, or null
118
+ * when it can: a PNG, JPEG or WebP, portrait 9:16 (a 1% slack, since a
119
+ * designer's export is often a pixel off), at least as large as the largest
120
+ * screen so no reader shows it enlarged.
121
+ */ export function posReaderSplashArtworkProblem(facts) {
122
+ const largest = POS_READER_SPLASH_LARGEST;
123
+ if (facts.contentType && !POS_READER_SPLASH_SOURCE_TYPES.includes(facts.contentType)) {
124
+ return 'Choose a PNG, JPEG or WebP image.';
125
+ }
126
+ if (typeof facts.bytes === 'number' && facts.bytes > POS_READER_SPLASH_SOURCE_MAX_BYTES) {
127
+ return 'Choose an image under 15 MB.';
128
+ }
129
+ if (!(facts.width > 0) || !(facts.height > 0)) return 'That image could not be read.';
130
+ const ratio = facts.width / facts.height;
131
+ const wanted = largest.width / largest.height;
132
+ if (Math.abs(ratio - wanted) / wanted > 0.01) {
133
+ return `Choose a portrait image shaped 9:16, such as ${largest.width} × ${largest.height} pixels.`;
134
+ }
135
+ if (facts.width < largest.width || facts.height < largest.height) {
136
+ return `Choose an image at least ${largest.width} × ${largest.height} pixels.`;
137
+ }
138
+ return null;
139
+ }
140
+
141
+ //# sourceMappingURL=pos-reader-splash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/pos-reader-splash.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * A CARD READER'S START-UP SCREEN (AGL-4048).\n *\n * Stripe's smart readers show a \"splash screen\" while they wait for a\n * payment. Stripe sets it per device type on a Terminal Configuration, from a\n * File uploaded with purpose `terminal_reader_splashscreen`, and a\n * Configuration is assigned per Location, so each store's readers can carry\n * their own. The rules Stripe states for the image, and the only ones this\n * module enforces:\n *\n * - JPEG or PNG under 2 MB (a GIF may be 4 MB, but it is re-encoded here,\n * so it is not offered);\n * - cropped to the reader's own screen: 1080 × 1920 on the Stripe Reader\n * S700 and S710, 720 × 1280 on the BBPOS WisePOS E.\n *\n * What a store shows is one of three CHOICES:\n *\n * - `platform` the platform's default screen, which staff upload;\n * - `logo` the workspace's own logo, centered on a white screen;\n * - `image` a full-screen portrait image the merchant picked from\n * their media library.\n *\n * A white-label workspace defaults to `logo` and can never be given\n * `platform`: its readers never show the platform's brand.\n *\n * Shared by the console and the server, so this module imports nothing.\n *=========================================*/\n\nexport type PosReaderSplashChoice = 'platform' | 'logo' | 'image'\n\nexport const POS_READER_SPLASH_CHOICES: readonly PosReaderSplashChoice[] = ['platform', 'logo', 'image']\n\nexport function isPosReaderSplashChoice(value: unknown): value is PosReaderSplashChoice {\n return typeof value === 'string' && (POS_READER_SPLASH_CHOICES as readonly string[]).includes(value)\n}\n\n/** A group of readers that share one screen size, so one image. */\nexport type PosReaderSplashFamilyId = 'portrait-large' | 'portrait-small'\n\nexport interface PosReaderSplashFamily {\n id: PosReaderSplashFamilyId\n /** The readers in it, for a person. */\n label: string\n width: number\n height: number\n /** The Terminal Configuration keys that take this family's image. */\n deviceTypes: readonly string[]\n}\n\nexport const POS_READER_SPLASH_FAMILIES: readonly PosReaderSplashFamily[] = [\n {\n id: 'portrait-large',\n label: 'Stripe Reader S700 and S710',\n width: 1080,\n height: 1920,\n deviceTypes: ['stripe_s700', 'stripe_s710'],\n },\n {\n id: 'portrait-small',\n label: 'BBPOS WisePOS E',\n width: 720,\n height: 1280,\n deviceTypes: ['bbpos_wisepos_e'],\n },\n]\n\nexport function posReaderSplashFamily(id: unknown): PosReaderSplashFamily | null {\n return POS_READER_SPLASH_FAMILIES.find((family) => family.id === id) ?? null\n}\n\n/** Every Terminal Configuration device key a start-up screen is set on. */\nexport const POS_READER_SPLASH_DEVICE_TYPES: readonly string[] = POS_READER_SPLASH_FAMILIES.flatMap(\n (family) => family.deviceTypes,\n)\n\n/** Stripe's limit is \"less than 2 MB\"; read as decimal megabytes, the smaller. */\nexport const POS_READER_SPLASH_MAX_BYTES = 2_000_000\n\n/** What Stripe takes for a still start-up screen. */\nexport const POS_READER_SPLASH_CONTENT_TYPES: readonly string[] = ['image/png', 'image/jpeg']\n\n/** What a merchant's own full-screen image may be picked as; it is re-encoded. */\nexport const POS_READER_SPLASH_SOURCE_TYPES: readonly string[] = ['image/png', 'image/jpeg', 'image/webp']\n\n/** The largest source read to draw a screen from: a logo or a photo, never a video. */\nexport const POS_READER_SPLASH_SOURCE_MAX_BYTES = 15_000_000\n\n/** The choice a store gets before anyone picks one. */\nexport function posReaderSplashDefaultChoice(whiteLabel: boolean): PosReaderSplashChoice {\n return whiteLabel ? 'logo' : 'platform'\n}\n\n/**\n * The choice that is applied: the stored one when it is valid, the default\n * otherwise, and never `platform` for a white-label workspace (one that\n * stored it before it was white-labeled gets its logo instead).\n */\nexport function posReaderSplashEffectiveChoice(stored: unknown, whiteLabel: boolean): PosReaderSplashChoice {\n const choice = isPosReaderSplashChoice(stored) ? stored : posReaderSplashDefaultChoice(whiteLabel)\n return whiteLabel && choice === 'platform' ? 'logo' : choice\n}\n\nexport interface PosReaderSplashImageFacts {\n contentType: string\n bytes: number\n width: number\n height: number\n}\n\n/**\n * Why an image cannot be sent to Stripe as `family`'s start-up screen, or\n * null when it can: the exact screen size, JPEG or PNG, under 2 MB. Staff's\n * default screens are checked with this as they are uploaded, and every\n * rendered screen again before it leaves.\n */\nexport function posReaderSplashImageProblem(\n facts: PosReaderSplashImageFacts,\n family: PosReaderSplashFamily,\n): string | null {\n if (!POS_READER_SPLASH_CONTENT_TYPES.includes(facts.contentType)) {\n return 'Use a PNG or JPEG image.'\n }\n if (facts.width !== family.width || facts.height !== family.height) {\n return `The ${family.label} screen is ${family.width} × ${family.height} pixels; this image is ${facts.width} × ${facts.height}.`\n }\n if (!(facts.bytes > 0) || facts.bytes >= POS_READER_SPLASH_MAX_BYTES) {\n return 'Use an image under 2 MB.'\n }\n return null\n}\n\n/** The largest screen, which a merchant's full-screen image must cover. */\nexport const POS_READER_SPLASH_LARGEST = POS_READER_SPLASH_FAMILIES[0]\n\n/**\n * Why a merchant's own image cannot be a full-screen start-up screen, or null\n * when it can: a PNG, JPEG or WebP, portrait 9:16 (a 1% slack, since a\n * designer's export is often a pixel off), at least as large as the largest\n * screen so no reader shows it enlarged.\n */\nexport function posReaderSplashArtworkProblem(facts: {\n contentType?: string | null\n bytes?: number | null\n width: number\n height: number\n}): string | null {\n const largest = POS_READER_SPLASH_LARGEST\n if (facts.contentType && !POS_READER_SPLASH_SOURCE_TYPES.includes(facts.contentType)) {\n return 'Choose a PNG, JPEG or WebP image.'\n }\n if (typeof facts.bytes === 'number' && facts.bytes > POS_READER_SPLASH_SOURCE_MAX_BYTES) {\n return 'Choose an image under 15 MB.'\n }\n if (!(facts.width > 0) || !(facts.height > 0)) return 'That image could not be read.'\n const ratio = facts.width / facts.height\n const wanted = largest.width / largest.height\n if (Math.abs(ratio - wanted) / wanted > 0.01) {\n return `Choose a portrait image shaped 9:16, such as ${largest.width} × ${largest.height} pixels.`\n }\n if (facts.width < largest.width || facts.height < largest.height) {\n return `Choose an image at least ${largest.width} × ${largest.height} pixels.`\n }\n return null\n}\n\n/** `hosts/{hostId}/terminal/config.splash`: the store's choice. */\nexport interface PosReaderSplashSetting {\n choice: PosReaderSplashChoice\n /** For `image`: the picked asset, as the media library names it. */\n mediaScope?: string\n mediaId?: string\n updatedAtMs: number\n updatedBy?: string\n}\n\n/** What the POS devices card shows about the store's start-up screen. */\nexport interface PosReaderSplashStatus {\n /** Whether card readers are offered at all; the setting is hidden otherwise. */\n available: boolean\n /** The stored choice, or null when none was made. */\n choice: PosReaderSplashChoice | null\n /** What is applied, after the default and the white-label rule. */\n effectiveChoice: PosReaderSplashChoice\n whiteLabel: boolean\n /** The brand a white-label workspace or the platform goes by, for copy. */\n productName: string\n /** Whether staff have uploaded the platform's default screen. */\n platformReady: boolean\n /** The workspace logo that `logo` would use, or null when there is none. */\n logoUrl: string | null\n /** The asset `image` uses, when one was picked, with an address to preview it. */\n image: { mediaScope: string; mediaId: string; url: string | null } | null\n /** Whether the store has a Terminal Location yet (its first reader makes it). */\n locationReady: boolean\n /** When the current choice last reached Stripe. */\n appliedAtMs: number | null\n /** Why the current choice could not be applied, when it could not. */\n problem: string | null\n}\n"],"names":["POS_READER_SPLASH_CHOICES","isPosReaderSplashChoice","value","includes","POS_READER_SPLASH_FAMILIES","id","label","width","height","deviceTypes","posReaderSplashFamily","find","family","POS_READER_SPLASH_DEVICE_TYPES","flatMap","POS_READER_SPLASH_MAX_BYTES","POS_READER_SPLASH_CONTENT_TYPES","POS_READER_SPLASH_SOURCE_TYPES","POS_READER_SPLASH_SOURCE_MAX_BYTES","posReaderSplashDefaultChoice","whiteLabel","posReaderSplashEffectiveChoice","stored","choice","posReaderSplashImageProblem","facts","contentType","bytes","POS_READER_SPLASH_LARGEST","posReaderSplashArtworkProblem","largest","ratio","wanted","Math","abs"],"mappings":"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"}
@@ -88,6 +88,8 @@ export declare function productSaveFields(current: EditedProduct, nowMs: number)
88
88
  lowStockThreshold?: number;
89
89
  modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
90
90
  posQuickKey?: boolean;
91
+ sellOnline?: boolean;
92
+ checkIn?: import("./commerce-check-in-product").ProductCheckIn;
91
93
  createdAtMs?: number;
92
94
  deletedAt?: number | null;
93
95
  stockHolds?: Record<string, unknown>;
@@ -163,6 +165,8 @@ export declare function productEditWrite(fields: ReturnType<typeof productSaveFi
163
165
  lowStockThreshold?: number;
164
166
  modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
165
167
  posQuickKey?: boolean;
168
+ sellOnline?: boolean;
169
+ checkIn?: import("./commerce-check-in-product").ProductCheckIn;
166
170
  createdAtMs?: number;
167
171
  };
168
172
  /** A duplicate's name: the source's, marked as a copy. */
@@ -232,6 +236,8 @@ export declare function productDuplicateFields(product: HostProduct & {
232
236
  lowStockThreshold?: number;
233
237
  modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
234
238
  posQuickKey?: boolean;
239
+ sellOnline?: boolean;
240
+ checkIn?: import("./commerce-check-in-product").ProductCheckIn;
235
241
  deletedAt?: number | null;
236
242
  priceUsd?: number;
237
243
  imageUrl?: string;
@@ -73,3 +73,13 @@ export declare function posRegisterSettings(config: Record<string, unknown> | nu
73
73
  * `getPluginConfig` returns the raw doc untouched.
74
74
  */
75
75
  export declare function posMaxDiscountPct(config: Record<string, unknown> | null | undefined): number;
76
+ /** The store's rules for cards taken offline on the app's own reader (AGL-4039), in cents. */
77
+ export interface PosOfflineCardSettings {
78
+ enabled: boolean;
79
+ /** The store's limit per sale, or Stripe's own ceiling when the store set none. */
80
+ maxSaleCents: number;
81
+ /** The store's daily limit per device; 0 when it set none (no daily limit). */
82
+ maxDeviceDailyCents: number;
83
+ }
84
+ /** {@link PosOfflineCardSettings} out of a merged commerce config, typed and bounded. */
85
+ export declare function posOfflineCardSettings(config: Record<string, unknown> | null | undefined): PosOfflineCardSettings;
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  import { posTipPercentages } from "./model/commerce-pos.js";
18
+ import { POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT } from "./model/pos-offline-card-limits.js";
18
19
  import { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from "./pos-ops-config.js";
19
20
  import { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from "./pos-kiosk-config.js";
20
21
  /**
@@ -60,6 +61,31 @@ import { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from "./pos-kiosk-
60
61
  max: 100,
61
62
  description: 'The largest discount the point of sale will accept on a sale. ' + 'A register that asks for more is refused, and the sale is not rung ' + 'up. Set to 0 to stop staff discounting at the register entirely.'
62
63
  },
64
+ // CARDS WITH NO CONNECTION (AGL-4039): the app's own card reader stores a
65
+ // card while offline and charges it when the connection returns. Off until
66
+ // the store turns it on, then within its two limits (as Shopify POS does).
67
+ {
68
+ key: 'posOfflineCardSales',
69
+ label: 'Take cards offline in the point of sale app',
70
+ type: 'boolean',
71
+ description: 'With no connection, the app\u2019s card reader saves each card and charges it once back online.'
72
+ },
73
+ {
74
+ key: 'posOfflineCardMaxSale',
75
+ label: 'Limit per sale ($)',
76
+ type: 'number',
77
+ min: 1,
78
+ max: 10000,
79
+ description: 'Empty: up to $10,000, the most one card can be charged offline.'
80
+ },
81
+ {
82
+ key: 'posOfflineCardDailyLimit',
83
+ label: 'Daily limit per device ($)',
84
+ type: 'number',
85
+ min: 1,
86
+ max: 1000000,
87
+ description: 'Empty: no daily limit.'
88
+ },
63
89
  // THE REGISTER'S TIPS, RECEIPTS AND CUSTOMER DISPLAY (AGL-3607, AGL-3608).
64
90
  // Off by default: a store that never asked for tips does not start
65
91
  // asking its customers for one on the day this ships.
@@ -116,6 +142,7 @@ import { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from "./pos-kiosk-
116
142
  ],
117
143
  defaults: _extends({
118
144
  posMaxDiscountPct: POS_MAX_DISCOUNT_PCT_DEFAULT,
145
+ posOfflineCardSales: false,
119
146
  posTippingEnabled: false,
120
147
  posTipPercentages: '15, 18, 20, 25',
121
148
  posReceiptDefault: 'ask',
@@ -150,5 +177,18 @@ import { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from "./pos-kiosk-
150
177
  if (!Number.isFinite(raw)) return POS_MAX_DISCOUNT_PCT_DEFAULT;
151
178
  return Math.min(100, Math.max(0, raw));
152
179
  }
180
+ /** {@link PosOfflineCardSettings} out of a merged commerce config, typed and bounded. */ export function posOfflineCardSettings(config) {
181
+ const dollars = (key, fallbackCents, maxCents)=>{
182
+ const raw = config == null ? void 0 : config[key];
183
+ const value = raw === '' || raw === null || raw === undefined ? NaN : Number(raw);
184
+ if (!Number.isFinite(value) || value <= 0) return fallbackCents;
185
+ return Math.min(maxCents, Math.round(value * 100));
186
+ };
187
+ return {
188
+ enabled: (config == null ? void 0 : config['posOfflineCardSales']) === true,
189
+ maxSaleCents: dollars('posOfflineCardMaxSale', POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT, POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT),
190
+ maxDeviceDailyCents: dollars('posOfflineCardDailyLimit', 0, 100000000)
191
+ };
192
+ }
153
193
 
154
194
  //# sourceMappingURL=plugin-config.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin-config.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 { PluginConfigSchema } from '@aglyn/aglyn'\nimport { posTipPercentages } from './model/commerce-pos'\nimport { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from './pos-ops-config'\nimport { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from './pos-kiosk-config'\n\n/**\n * The ceiling a register may discount to, when the merchant has not set one\n * (AGL-2161).\n *\n * 100, i.e. today's behaviour exactly. That is deliberate and is the one\n * number in this fix that is NOT a safety judgement:\n *\n * - A full comp is a legitimate register operation — a damaged item, a\n * goodwill gesture, a staff meal — and every merchant on the platform has\n * had it since POS shipped.\n * - Any lower default would change what merchants charge their own customers\n * on the day it deployed, without them asking. Picking that number is a\n * merchant policy decision, not a bug fix, and inventing one here would be\n * the same class of mistake as inventing a billing rate.\n *\n * What AGL-2161 actually fixes is that there was no ceiling to set and no\n * refusal when one was exceeded: the request body's `discountPct` was clamped\n * into range and rung up, so a register sending `150` silently comped the\n * whole sale. The ceiling is now real, enforced server-side, refused rather\n * than clamped, and one config value away from being lower.\n */\nexport const POS_MAX_DISCOUNT_PCT_DEFAULT = 100\n\n/**\n * Commerce plugin config schema (AGL-428 framework, AGL-2161 adopter).\n *\n * PURE DATA (type-only aglyn import), the same shape as bookings': the client\n * barrel registers it via '@aglyn/aglyn' and the /server entry via\n * '@aglyn/aglyn/server', so neither bundle drags in the other's barrel.\n *\n * `min`/`max` are not decoration — `mergePluginConfig` coerces a stored value\n * into the declared range and falls back to the default on junk, so the\n * ceiling itself cannot be forged out of bounds by a manager editing the\n * settings doc directly.\n */\nexport const COMMERCE_CONFIG_SCHEMA: PluginConfigSchema = {\n pluginId: 'commerce',\n fields: [\n {\n key: 'posMaxDiscountPct',\n label: 'Maximum register discount (%)',\n type: 'number',\n min: 0,\n max: 100,\n description:\n 'The largest discount the point of sale will accept on a sale. ' +\n 'A register that asks for more is refused, and the sale is not rung ' +\n 'up. Set to 0 to stop staff discounting at the register entirely.',\n },\n // THE REGISTER'S TIPS, RECEIPTS AND CUSTOMER DISPLAY (AGL-3607, AGL-3608).\n // Off by default: a store that never asked for tips does not start\n // asking its customers for one on the day this ships.\n {\n key: 'posTippingEnabled',\n label: 'Ask for tips at the register',\n type: 'boolean',\n description:\n 'Offers the customer a tip on the card reader and on the customer ' +\n 'display. Tips are yours: they are not counted as sales and carry no ' +\n 'platform fee.',\n },\n {\n key: 'posTipPercentages',\n label: 'Tip choices (%)',\n type: 'string',\n description:\n 'Up to four percentages, separated by commas, shown to the customer ' +\n 'as tip buttons. The customer can also enter an amount or choose no tip.',\n },\n {\n key: 'posReceiptDefault',\n label: 'Receipts at the register',\n type: 'select',\n options: [\n { value: 'ask', label: 'Ask the customer' },\n { value: 'print', label: 'Always print' },\n { value: 'none', label: 'No receipt unless asked' },\n ],\n description:\n 'With a customer display paired, \"Ask the customer\" lets them choose ' +\n 'email, print or no receipt on their own screen.',\n },\n {\n key: 'posDisplayMessage',\n label: 'Customer display welcome',\n type: 'string',\n description:\n 'The line the customer display shows under your store name between ' +\n 'sales. Leave blank for \"Welcome\".',\n },\n {\n key: 'posDisplayMarketingOptIn',\n label: 'Offer email sign-up on the customer display',\n type: 'boolean',\n description:\n 'Shows an unticked \"Email me news and offers\" box when a customer ' +\n 'asks for an email receipt. Only a ticked box adds them to your ' +\n 'marketing audience.',\n },\n // RUNNING THE REGISTER (AGL-3609): shifts, refund limits, the idle lock\n // and what a printed receipt carries.\n ...POS_OPS_CONFIG_FIELDS,\n // THE SELF-SERVICE KIOSK (AGL-3623): pay at counter, the idle reset and\n // its welcome line.\n ...POS_KIOSK_CONFIG_FIELDS,\n ],\n defaults: {\n posMaxDiscountPct: POS_MAX_DISCOUNT_PCT_DEFAULT,\n posTippingEnabled: false,\n posTipPercentages: '15, 18, 20, 25',\n posReceiptDefault: 'ask',\n posDisplayMessage: '',\n posDisplayMarketingOptIn: true,\n ...POS_OPS_CONFIG_DEFAULTS,\n ...POS_KIOSK_CONFIG_DEFAULTS,\n },\n}\n\n/** The register's tip, receipt and display settings, resolved for one site. */\nexport interface PosRegisterSettings {\n tippingEnabled: boolean\n tipPercentages: number[]\n receiptDefault: 'ask' | 'print' | 'none'\n displayMessage: string\n displayMarketingOptIn: boolean\n}\n\n/**\n * The register settings out of a merged commerce config, with every value\n * typed and bounded so the register, the display and the server agree.\n */\nexport function posRegisterSettings(\n config: Record<string, unknown> | null | undefined,\n): PosRegisterSettings {\n const receipt = String(config?.['posReceiptDefault'] ?? 'ask')\n return {\n tippingEnabled: config?.['posTippingEnabled'] === true,\n tipPercentages: posTipPercentages(config?.['posTipPercentages']),\n receiptDefault:\n receipt === 'print' || receipt === 'none' ? receipt : 'ask',\n displayMessage: String(config?.['posDisplayMessage'] ?? '').trim().slice(0, 120),\n displayMarketingOptIn: config?.['posDisplayMarketingOptIn'] !== false,\n }\n}\n\n/**\n * The ceiling for one org, read defensively (AGL-2161).\n *\n * Spelled here rather than at the call site so the POS route and any later\n * reader cannot disagree about what a missing or malformed value means. The\n * framework has already clamped and defaulted by the time this runs; the\n * second `Number.isFinite` guard is for the schema-less case, where\n * `getPluginConfig` returns the raw doc untouched.\n */\nexport function posMaxDiscountPct(\n config: Record<string, unknown> | null | undefined,\n): number {\n const raw = Number(config?.['posMaxDiscountPct'])\n if (!Number.isFinite(raw)) return POS_MAX_DISCOUNT_PCT_DEFAULT\n return Math.min(100, Math.max(0, raw))\n}\n"],"names":["posTipPercentages","POS_OPS_CONFIG_DEFAULTS","POS_OPS_CONFIG_FIELDS","POS_KIOSK_CONFIG_DEFAULTS","POS_KIOSK_CONFIG_FIELDS","POS_MAX_DISCOUNT_PCT_DEFAULT","COMMERCE_CONFIG_SCHEMA","pluginId","fields","key","label","type","min","max","description","options","value","defaults","posMaxDiscountPct","posTippingEnabled","posReceiptDefault","posDisplayMessage","posDisplayMarketingOptIn","posRegisterSettings","config","receipt","String","tippingEnabled","tipPercentages","receiptDefault","displayMessage","trim","slice","displayMarketingOptIn","raw","Number","isFinite","Math"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,iBAAiB,QAAQ,0BAAsB;AACxD,SAASC,uBAAuB,EAAEC,qBAAqB,QAAQ,sBAAkB;AACjF,SAASC,yBAAyB,EAAEC,uBAAuB,QAAQ,wBAAoB;AAEvF;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,+BAA+B,IAAG;AAE/C;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,yBAA6C;IACxDC,UAAU;IACVC,QAAQ;QACN;YACEC,KAAK;YACLC,OAAO;YACPC,MAAM;YACNC,KAAK;YACLC,KAAK;YACLC,aACE,mEACA,wEACA;QACJ;QACA,2EAA2E;QAC3E,mEAAmE;QACnE,sDAAsD;QACtD;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,sEACA,yEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,wEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNI,SAAS;gBACP;oBAAEC,OAAO;oBAAON,OAAO;gBAAmB;gBAC1C;oBAAEM,OAAO;oBAASN,OAAO;gBAAe;gBACxC;oBAAEM,OAAO;oBAAQN,OAAO;gBAA0B;aACnD;YACDI,aACE,yEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,uEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,sEACA,oEACA;QACJ;QACA,wEAAwE;QACxE,sCAAsC;WACnCZ;QACH,wEAAwE;QACxE,oBAAoB;WACjBE;KACJ;IACDa,UAAU;QACRC,mBAAmBb;QACnBc,mBAAmB;QACnBnB,mBAAmB;QACnBoB,mBAAmB;QACnBC,mBAAmB;QACnBC,0BAA0B;OACvBrB,yBACAE;AAEP,EAAC;AAWD;;;CAGC,GACD,OAAO,SAASoB,oBACdC,MAAkD;;IAElD,MAAMC,UAAUC,eAAOF,0BAAAA,MAAQ,CAAC,oBAAoB,mBAAI;IACxD,OAAO;QACLG,gBAAgBH,CAAAA,0BAAAA,MAAQ,CAAC,oBAAoB,MAAK;QAClDI,gBAAgB5B,kBAAkBwB,0BAAAA,MAAQ,CAAC,oBAAoB;QAC/DK,gBACEJ,YAAY,WAAWA,YAAY,SAASA,UAAU;QACxDK,gBAAgBJ,gBAAOF,0BAAAA,MAAQ,CAAC,oBAAoB,oBAAI,IAAIO,IAAI,GAAGC,KAAK,CAAC,GAAG;QAC5EC,uBAAuBT,CAAAA,0BAAAA,MAAQ,CAAC,2BAA2B,MAAK;IAClE;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASN,kBACdM,MAAkD;IAElD,MAAMU,MAAMC,OAAOX,0BAAAA,MAAQ,CAAC,oBAAoB;IAChD,IAAI,CAACW,OAAOC,QAAQ,CAACF,MAAM,OAAO7B;IAClC,OAAOgC,KAAKzB,GAAG,CAAC,KAAKyB,KAAKxB,GAAG,CAAC,GAAGqB;AACnC"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin-config.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 { PluginConfigSchema } from '@aglyn/aglyn'\nimport { posTipPercentages } from './model/commerce-pos'\nimport {\n POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT,\n} from './model/pos-offline-card-limits'\nimport { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from './pos-ops-config'\nimport { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from './pos-kiosk-config'\n\n/**\n * The ceiling a register may discount to, when the merchant has not set one\n * (AGL-2161).\n *\n * 100, i.e. today's behaviour exactly. That is deliberate and is the one\n * number in this fix that is NOT a safety judgement:\n *\n * - A full comp is a legitimate register operation — a damaged item, a\n * goodwill gesture, a staff meal — and every merchant on the platform has\n * had it since POS shipped.\n * - Any lower default would change what merchants charge their own customers\n * on the day it deployed, without them asking. Picking that number is a\n * merchant policy decision, not a bug fix, and inventing one here would be\n * the same class of mistake as inventing a billing rate.\n *\n * What AGL-2161 actually fixes is that there was no ceiling to set and no\n * refusal when one was exceeded: the request body's `discountPct` was clamped\n * into range and rung up, so a register sending `150` silently comped the\n * whole sale. The ceiling is now real, enforced server-side, refused rather\n * than clamped, and one config value away from being lower.\n */\nexport const POS_MAX_DISCOUNT_PCT_DEFAULT = 100\n\n/**\n * Commerce plugin config schema (AGL-428 framework, AGL-2161 adopter).\n *\n * PURE DATA (type-only aglyn import), the same shape as bookings': the client\n * barrel registers it via '@aglyn/aglyn' and the /server entry via\n * '@aglyn/aglyn/server', so neither bundle drags in the other's barrel.\n *\n * `min`/`max` are not decoration — `mergePluginConfig` coerces a stored value\n * into the declared range and falls back to the default on junk, so the\n * ceiling itself cannot be forged out of bounds by a manager editing the\n * settings doc directly.\n */\nexport const COMMERCE_CONFIG_SCHEMA: PluginConfigSchema = {\n pluginId: 'commerce',\n fields: [\n {\n key: 'posMaxDiscountPct',\n label: 'Maximum register discount (%)',\n type: 'number',\n min: 0,\n max: 100,\n description:\n 'The largest discount the point of sale will accept on a sale. ' +\n 'A register that asks for more is refused, and the sale is not rung ' +\n 'up. Set to 0 to stop staff discounting at the register entirely.',\n },\n // CARDS WITH NO CONNECTION (AGL-4039): the app's own card reader stores a\n // card while offline and charges it when the connection returns. Off until\n // the store turns it on, then within its two limits (as Shopify POS does).\n {\n key: 'posOfflineCardSales',\n label: 'Take cards offline in the point of sale app',\n type: 'boolean',\n description: 'With no connection, the app\\u2019s card reader saves each card and charges it once back online.',\n },\n {\n key: 'posOfflineCardMaxSale',\n label: 'Limit per sale ($)',\n type: 'number',\n min: 1,\n max: 10000,\n description: 'Empty: up to $10,000, the most one card can be charged offline.',\n },\n {\n key: 'posOfflineCardDailyLimit',\n label: 'Daily limit per device ($)',\n type: 'number',\n min: 1,\n max: 1000000,\n description: 'Empty: no daily limit.',\n },\n // THE REGISTER'S TIPS, RECEIPTS AND CUSTOMER DISPLAY (AGL-3607, AGL-3608).\n // Off by default: a store that never asked for tips does not start\n // asking its customers for one on the day this ships.\n {\n key: 'posTippingEnabled',\n label: 'Ask for tips at the register',\n type: 'boolean',\n description:\n 'Offers the customer a tip on the card reader and on the customer ' +\n 'display. Tips are yours: they are not counted as sales and carry no ' +\n 'platform fee.',\n },\n {\n key: 'posTipPercentages',\n label: 'Tip choices (%)',\n type: 'string',\n description:\n 'Up to four percentages, separated by commas, shown to the customer ' +\n 'as tip buttons. The customer can also enter an amount or choose no tip.',\n },\n {\n key: 'posReceiptDefault',\n label: 'Receipts at the register',\n type: 'select',\n options: [\n { value: 'ask', label: 'Ask the customer' },\n { value: 'print', label: 'Always print' },\n { value: 'none', label: 'No receipt unless asked' },\n ],\n description:\n 'With a customer display paired, \"Ask the customer\" lets them choose ' +\n 'email, print or no receipt on their own screen.',\n },\n {\n key: 'posDisplayMessage',\n label: 'Customer display welcome',\n type: 'string',\n description:\n 'The line the customer display shows under your store name between ' +\n 'sales. Leave blank for \"Welcome\".',\n },\n {\n key: 'posDisplayMarketingOptIn',\n label: 'Offer email sign-up on the customer display',\n type: 'boolean',\n description:\n 'Shows an unticked \"Email me news and offers\" box when a customer ' +\n 'asks for an email receipt. Only a ticked box adds them to your ' +\n 'marketing audience.',\n },\n // RUNNING THE REGISTER (AGL-3609): shifts, refund limits, the idle lock\n // and what a printed receipt carries.\n ...POS_OPS_CONFIG_FIELDS,\n // THE SELF-SERVICE KIOSK (AGL-3623): pay at counter, the idle reset and\n // its welcome line.\n ...POS_KIOSK_CONFIG_FIELDS,\n ],\n defaults: {\n posMaxDiscountPct: POS_MAX_DISCOUNT_PCT_DEFAULT,\n posOfflineCardSales: false,\n posTippingEnabled: false,\n posTipPercentages: '15, 18, 20, 25',\n posReceiptDefault: 'ask',\n posDisplayMessage: '',\n posDisplayMarketingOptIn: true,\n ...POS_OPS_CONFIG_DEFAULTS,\n ...POS_KIOSK_CONFIG_DEFAULTS,\n },\n}\n\n/** The register's tip, receipt and display settings, resolved for one site. */\nexport interface PosRegisterSettings {\n tippingEnabled: boolean\n tipPercentages: number[]\n receiptDefault: 'ask' | 'print' | 'none'\n displayMessage: string\n displayMarketingOptIn: boolean\n}\n\n/**\n * The register settings out of a merged commerce config, with every value\n * typed and bounded so the register, the display and the server agree.\n */\nexport function posRegisterSettings(\n config: Record<string, unknown> | null | undefined,\n): PosRegisterSettings {\n const receipt = String(config?.['posReceiptDefault'] ?? 'ask')\n return {\n tippingEnabled: config?.['posTippingEnabled'] === true,\n tipPercentages: posTipPercentages(config?.['posTipPercentages']),\n receiptDefault:\n receipt === 'print' || receipt === 'none' ? receipt : 'ask',\n displayMessage: String(config?.['posDisplayMessage'] ?? '').trim().slice(0, 120),\n displayMarketingOptIn: config?.['posDisplayMarketingOptIn'] !== false,\n }\n}\n\n/**\n * The ceiling for one org, read defensively (AGL-2161).\n *\n * Spelled here rather than at the call site so the POS route and any later\n * reader cannot disagree about what a missing or malformed value means. The\n * framework has already clamped and defaulted by the time this runs; the\n * second `Number.isFinite` guard is for the schema-less case, where\n * `getPluginConfig` returns the raw doc untouched.\n */\nexport function posMaxDiscountPct(\n config: Record<string, unknown> | null | undefined,\n): number {\n const raw = Number(config?.['posMaxDiscountPct'])\n if (!Number.isFinite(raw)) return POS_MAX_DISCOUNT_PCT_DEFAULT\n return Math.min(100, Math.max(0, raw))\n}\n\n/** The store's rules for cards taken offline on the app's own reader (AGL-4039), in cents. */\nexport interface PosOfflineCardSettings {\n enabled: boolean\n /** The store's limit per sale, or Stripe's own ceiling when the store set none. */\n maxSaleCents: number\n /** The store's daily limit per device; 0 when it set none (no daily limit). */\n maxDeviceDailyCents: number\n}\n\n/** {@link PosOfflineCardSettings} out of a merged commerce config, typed and bounded. */\nexport function posOfflineCardSettings(\n config: Record<string, unknown> | null | undefined,\n): PosOfflineCardSettings {\n const dollars = (key: string, fallbackCents: number, maxCents: number) => {\n const raw = config?.[key]\n const value = raw === '' || raw === null || raw === undefined ? NaN : Number(raw)\n if (!Number.isFinite(value) || value <= 0) return fallbackCents\n return Math.min(maxCents, Math.round(value * 100))\n }\n return {\n enabled: config?.['posOfflineCardSales'] === true,\n maxSaleCents: dollars('posOfflineCardMaxSale', POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT, POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT),\n maxDeviceDailyCents: dollars('posOfflineCardDailyLimit', 0, 100_000_000),\n }\n}\n"],"names":["posTipPercentages","POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT","POS_OPS_CONFIG_DEFAULTS","POS_OPS_CONFIG_FIELDS","POS_KIOSK_CONFIG_DEFAULTS","POS_KIOSK_CONFIG_FIELDS","POS_MAX_DISCOUNT_PCT_DEFAULT","COMMERCE_CONFIG_SCHEMA","pluginId","fields","key","label","type","min","max","description","options","value","defaults","posMaxDiscountPct","posOfflineCardSales","posTippingEnabled","posReceiptDefault","posDisplayMessage","posDisplayMarketingOptIn","posRegisterSettings","config","receipt","String","tippingEnabled","tipPercentages","receiptDefault","displayMessage","trim","slice","displayMarketingOptIn","raw","Number","isFinite","Math","posOfflineCardSettings","dollars","fallbackCents","maxCents","undefined","NaN","round","enabled","maxSaleCents","maxDeviceDailyCents"],"mappings":"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"}
package/src/lib/plugin.js CHANGED
@@ -19,6 +19,8 @@ import { lazy } from "react";
19
19
  const PosConsolePage = lazy(()=>import("./components/console/pos-page.component.js"));
20
20
  /** The customer display (AGL-3608): loads only on the public page that shows it. */ const PosDisplayPage = lazy(()=>import("./components/console/pos-display-page.component.js"));
21
21
  const PosKioskPage = lazy(()=>import("./components/console/pos-kiosk-page.component.js"));
22
+ /** Staff → Hardware orders (AGL-4038): loads only on its staff page. */ const PosHardwareOrdersStaffPage = lazy(()=>import("./components/console/pos-hardware-orders-staff-page.component.js"));
23
+ /** Staff → Reader start-up screen (AGL-4048): loads only on its staff page. */ const PosReaderSplashStaffPage = lazy(()=>import("./components/console/pos-reader-splash-staff-page.component.js"));
22
24
  const CommerceGlanceCard = lazy(()=>import("./components/console/commerce-glance-card.component.js"));
23
25
  const NewestSiteUsersCard = lazy(()=>import("./components/console/newest-site-users-card.component.js"));
24
26
  const SiteMemberPurchases = lazy(()=>import("./components/console/site-member-purchases.component.js"));
@@ -80,6 +82,32 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
80
82
  // `/kiosk/commerce/pos-display`. No staff session: the device proves
81
83
  // itself with the display token it got by pairing, and
82
84
  // `server/pos-display.ts` refuses every read without one.
85
+ // Every workspace's orders from the POS hardware shop (AGL-4038), where
86
+ // staff record Stripe's order id and the tracking, move each order on,
87
+ // and cancel or refund one that has not shipped. The staff area loads
88
+ // this registrar through commerce's `staff` surface.
89
+ staffPages: [
90
+ {
91
+ id: 'hardware-orders',
92
+ label: 'Hardware orders',
93
+ header: {
94
+ title: 'Hardware Orders',
95
+ docsTopic: 'posHardware'
96
+ },
97
+ Component: PosHardwareOrdersStaffPage
98
+ },
99
+ // The platform's default card reader start-up screen (AGL-4048), which
100
+ // every store that has not picked its own logo or image shows.
101
+ {
102
+ id: 'reader-start-up-screen',
103
+ label: 'Reader start-up screen',
104
+ header: {
105
+ title: 'Reader Start-up Screen',
106
+ docsTopic: 'posHardware'
107
+ },
108
+ Component: PosReaderSplashStaffPage
109
+ }
110
+ ],
83
111
  publicPages: [
84
112
  {
85
113
  path: '/pos-display',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiStorefrontOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nconst PosConsolePage = lazy(() => import('./components/console/pos-page.component'))\n/** The customer display (AGL-3608): loads only on the public page that shows it. */\nconst PosDisplayPage = lazy(() => import('./components/console/pos-display-page.component'))\nconst PosKioskPage = lazy(() => import('./components/console/pos-kiosk-page.component'))\nconst CommerceGlanceCard = lazy(\n () => import('./components/console/commerce-glance-card.component'),\n)\nconst NewestSiteUsersCard = lazy(\n () => import('./components/console/newest-site-users-card.component'),\n)\nconst SiteMemberPurchases = lazy(\n () => import('./components/console/site-member-purchases.component'),\n)\nimport {\n COMMERCE_CONSOLE_SECTIONS,\n COMMERCE_RETIRED_SECTIONS,\n} from './components/commerce-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { registerCommerceRecordLists } from './model/commerce-record-lists'\nimport { registerCommerceRecordRoutes } from './model/commerce-record-routes'\nimport { registerCommerceZones } from './components/console/product-zones'\nimport { registerCommerceOrderZones } from './components/console/order-zones'\nimport { registerCommerceReturnZones } from './components/console/return-zones'\nimport { registerCommerceStoreZones } from './components/console/store-zones'\nimport { registerCommercePosZones } from './components/console/pos-zones'\nimport { registerCommerceTransferUi } from './transfer/register-transfer-ui'\nimport { COMMERCE_SEARCH_SOURCES } from './model/commerce-search-sources'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\n\n/** Code-split: the Products console page only loads when opened. */\nconst CommerceConsolePage = lazy(() => import('./components/commerce-console-page'))\n\n/**\n * Console half (AGL-395): registers the Products nav item in the\n * ConsoleExtension registry so the commerce console surface is owned by the\n * plugin, like events and email. The page body still lives in the console\n * app (`/[hostId]/products`): its product editor depends on the app's media\n * browser, which transitively pulls in the org/session context, so a full\n * body relocation needs a plugin-consumable media-picker context first —\n * tracked as follow-up. The nav item omits a `Component`, so the shell's\n * nav renders the link and the existing named route serves the page.\n */\nexport function registerCommerceConsole(): void {\n // Where a product is read, for surfaces outside this plugin (AGL-3080).\n registerCommerceRecordRoutes()\n registerCommerceRecordLists()\n // The zones its product editor, products hub and CSV import dialog host,\n // with the props each hands a widget (AGL-2916, AGL-3080).\n registerCommerceZones()\n // …and the order dialog's two (AGL-3611), where a shipping plugin's widget\n // buys a label and records the shipment through the dialog's own route.\n registerCommerceOrderZones()\n // …and the return dialog's (AGL-3611), where a shipping plugin's widget\n // buys the buyer a return label and attaches it through this plugin's route.\n registerCommerceReturnZones()\n // The orders list's bulk actions and the store's Settings (AGL-3612).\n registerCommerceStoreZones()\n // The register's orders from other channels — a delivery app's (AGL-3644).\n registerCommercePosZones()\n // Its import and export resources' names and the products wizard's own\n // step (AGL-3531); the server halves register from the console's server\n // declarations.\n registerCommerceTransferUi()\n // Plugin-declared permissions (AGL-435): tier defaults ride every\n // resolved role set; custom roles override key-by-key.\n Aglyn.registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Register discount ceiling (AGL-2161) — the generic settings card renders\n // it from this schema, so there is no bespoke UI to keep in sync.\n Aglyn.registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Commerce',\n searchSources: COMMERCE_SEARCH_SOURCES,\n // The screen a register turns toward its customer (AGL-3608), at\n // `/kiosk/commerce/pos-display`. No staff session: the device proves\n // itself with the display token it got by pairing, and\n // `server/pos-display.ts` refuses every read without one.\n publicPages: [\n { path: '/pos-display', title: 'Customer display', Component: PosDisplayPage },\n // The self-service kiosk (AGL-3623), paired the same way with a kiosk\n // code; `server/pos-kiosk.ts` refuses every call without its token.\n { path: '/pos-kiosk', title: 'Self-service kiosk', Component: PosKioskPage },\n ],\n // Dashboard/analytics glance card (AGL-419): rendered through the\n // shell's 'commerceGlance' widget slot.\n widgets: [\n {\n slot: 'commerceGlance',\n widgetId: 'commerce-glance',\n title: 'Commerce',\n Component: CommerceGlanceCard,\n },\n // What a site user bought — lifetime total, orders and subscriptions —\n // in their drawer on the site's Users page: the orders and\n // subscriptions are this plugin's.\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.siteMember,\n widgetId: 'commerce-site-member-purchases',\n title: 'Purchases',\n Component: SiteMemberPurchases,\n },\n ],\n navItems: [\n {\n label: 'Products',\n href: '/products',\n icon: { path: mdiStorefrontOutline.path },\n // Sections as ROUTES (AGL-2501): `/products/orders` and friends are\n // real URLs the shell resolves and gates, so the page mounts the one\n // being read instead of subscribing all six.\n sections: COMMERCE_CONSOLE_SECTIONS,\n // `/products/settings` was split into a page per setting (AGL-3713);\n // links to it open the first of them.\n retiredSections: COMMERCE_RETIRED_SECTIONS,\n header: {\n title: 'Products',\n icon: { path: mdiStorefrontOutline.path },\n // The overview, not `catalog`: this surface is the whole commerce\n // console — Catalog, Orders, Promotions and Reservations.\n docsTopic: 'commerce',\n },\n Component: CommerceConsolePage,\n },\n {\n // POS register (AGL-312/419): relocated from the app's /pos route —\n // the generic [pluginSlug] shell now serves it at the same URL.\n label: 'POS',\n href: '/pos',\n /*\n * WHO may open the register, declared on THIS nav item rather than on\n * the extension.\n *\n * A key here narrows one surface; a key on the extension would gate\n * every surface it registers, and Products is the sibling — a catalog\n * anyone who works on the site may open. The register is the one that\n * takes money and lists checked-in guests by name, so the two\n * genuinely have different answers and only this one is narrowed.\n *\n * `managePos` rather than a dotted catalog key because it is the key\n * the sale itself is refused by: `server/pos-order.ts` resolves the\n * member's permission map and returns 403 unless `managePos` holds.\n * The shell answers this declaration out of that same resolved map, so\n * the console offers a register exactly to the people the route will\n * accept a sale from — the gate and its server twin cannot disagree,\n * because they are reading one value.\n *\n * `pos-order.ts` stays the enforcement point regardless. This decides\n * whether the surface is built at all, which is the part a check\n * inside the page cannot do: by the time the page can refuse itself it\n * has mounted and its product, register, location and reservation\n * listeners are already open.\n */\n permission: 'managePos',\n icon: { path: mdiStorefrontOutline.path },\n header: { title: 'Point of Sale', docsTopic: 'pos' },\n Component: PosConsolePage,\n },\n ],\n })\n /*\n * The USER ACCOUNTS half, registered under its own id.\n *\n * `accounts` is a switch rather than a bundle: the member blocks and every\n * `membership/*` handler ship inside this package, which is why the id\n * carries no loader manifest entry of its own. A second extension is how a\n * card about visitor accounts is gated on the switch that decides whether\n * the site serves them — `listConsoleWidgets` filters by the workspace's\n * effective plugin ids, and `accounts` is default-OFF per site.\n *\n * A separate call rather than more widgets on the commerce extension: the\n * registry is keyed by `pluginId` and that key IS the gate, so folding this\n * card in above would have kept the dashboard defect it fixes — `Newest\n * site users` on a site whose /signin returns 404.\n */\n Aglyn.registerConsoleExtension({\n pluginId: Aglyn.ACCOUNTS_PLUGIN_ID,\n displayName: 'User Accounts',\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'accounts-newest-site-users',\n title: 'Newest site users',\n Component: NewestSiteUsersCard,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","mdiStorefrontOutline","lazy","PosConsolePage","PosDisplayPage","PosKioskPage","CommerceGlanceCard","NewestSiteUsersCard","SiteMemberPurchases","COMMERCE_CONSOLE_SECTIONS","COMMERCE_RETIRED_SECTIONS","BUNDLE_ID","COMMERCE_PERMISSIONS","registerCommerceRecordLists","registerCommerceRecordRoutes","registerCommerceZones","registerCommerceOrderZones","registerCommerceReturnZones","registerCommerceStoreZones","registerCommercePosZones","registerCommerceTransferUi","COMMERCE_SEARCH_SOURCES","COMMERCE_CONFIG_SCHEMA","CommerceConsolePage","registerCommerceConsole","registerPluginPermissions","registerPluginConfigSchema","registerConsoleExtension","pluginId","displayName","searchSources","publicPages","path","title","Component","widgets","slot","widgetId","CONSOLE_WIDGET_SLOTS","siteMember","navItems","label","href","icon","sections","retiredSections","header","docsTopic","permission","ACCOUNTS_PLUGIN_ID","hostDashboard"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiStorefrontOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nconst PosConsolePage = lazy(() => import('./components/console/pos-page.component'))\n/** The customer display (AGL-3608): loads only on the public page that shows it. */\nconst PosDisplayPage = lazy(() => import('./components/console/pos-display-page.component'))\nconst PosKioskPage = lazy(() => import('./components/console/pos-kiosk-page.component'))\n/** Staff → Hardware orders (AGL-4038): loads only on its staff page. */\nconst PosHardwareOrdersStaffPage = lazy(\n () => import('./components/console/pos-hardware-orders-staff-page.component'),\n)\n/** Staff → Reader start-up screen (AGL-4048): loads only on its staff page. */\nconst PosReaderSplashStaffPage = lazy(\n () => import('./components/console/pos-reader-splash-staff-page.component'),\n)\nconst CommerceGlanceCard = lazy(\n () => import('./components/console/commerce-glance-card.component'),\n)\nconst NewestSiteUsersCard = lazy(\n () => import('./components/console/newest-site-users-card.component'),\n)\nconst SiteMemberPurchases = lazy(\n () => import('./components/console/site-member-purchases.component'),\n)\nimport {\n COMMERCE_CONSOLE_SECTIONS,\n COMMERCE_RETIRED_SECTIONS,\n} from './components/commerce-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { registerCommerceRecordLists } from './model/commerce-record-lists'\nimport { registerCommerceRecordRoutes } from './model/commerce-record-routes'\nimport { registerCommerceZones } from './components/console/product-zones'\nimport { registerCommerceOrderZones } from './components/console/order-zones'\nimport { registerCommerceReturnZones } from './components/console/return-zones'\nimport { registerCommerceStoreZones } from './components/console/store-zones'\nimport { registerCommercePosZones } from './components/console/pos-zones'\nimport { registerCommerceTransferUi } from './transfer/register-transfer-ui'\nimport { COMMERCE_SEARCH_SOURCES } from './model/commerce-search-sources'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\n\n/** Code-split: the Products console page only loads when opened. */\nconst CommerceConsolePage = lazy(() => import('./components/commerce-console-page'))\n\n/**\n * Console half (AGL-395): registers the Products nav item in the\n * ConsoleExtension registry so the commerce console surface is owned by the\n * plugin, like events and email. The page body still lives in the console\n * app (`/[hostId]/products`): its product editor depends on the app's media\n * browser, which transitively pulls in the org/session context, so a full\n * body relocation needs a plugin-consumable media-picker context first —\n * tracked as follow-up. The nav item omits a `Component`, so the shell's\n * nav renders the link and the existing named route serves the page.\n */\nexport function registerCommerceConsole(): void {\n // Where a product is read, for surfaces outside this plugin (AGL-3080).\n registerCommerceRecordRoutes()\n registerCommerceRecordLists()\n // The zones its product editor, products hub and CSV import dialog host,\n // with the props each hands a widget (AGL-2916, AGL-3080).\n registerCommerceZones()\n // …and the order dialog's two (AGL-3611), where a shipping plugin's widget\n // buys a label and records the shipment through the dialog's own route.\n registerCommerceOrderZones()\n // …and the return dialog's (AGL-3611), where a shipping plugin's widget\n // buys the buyer a return label and attaches it through this plugin's route.\n registerCommerceReturnZones()\n // The orders list's bulk actions and the store's Settings (AGL-3612).\n registerCommerceStoreZones()\n // The register's orders from other channels — a delivery app's (AGL-3644).\n registerCommercePosZones()\n // Its import and export resources' names and the products wizard's own\n // step (AGL-3531); the server halves register from the console's server\n // declarations.\n registerCommerceTransferUi()\n // Plugin-declared permissions (AGL-435): tier defaults ride every\n // resolved role set; custom roles override key-by-key.\n Aglyn.registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Register discount ceiling (AGL-2161) — the generic settings card renders\n // it from this schema, so there is no bespoke UI to keep in sync.\n Aglyn.registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Commerce',\n searchSources: COMMERCE_SEARCH_SOURCES,\n // The screen a register turns toward its customer (AGL-3608), at\n // `/kiosk/commerce/pos-display`. No staff session: the device proves\n // itself with the display token it got by pairing, and\n // `server/pos-display.ts` refuses every read without one.\n // Every workspace's orders from the POS hardware shop (AGL-4038), where\n // staff record Stripe's order id and the tracking, move each order on,\n // and cancel or refund one that has not shipped. The staff area loads\n // this registrar through commerce's `staff` surface.\n staffPages: [\n {\n id: 'hardware-orders',\n label: 'Hardware orders',\n header: { title: 'Hardware Orders', docsTopic: 'posHardware' },\n Component: PosHardwareOrdersStaffPage,\n },\n // The platform's default card reader start-up screen (AGL-4048), which\n // every store that has not picked its own logo or image shows.\n {\n id: 'reader-start-up-screen',\n label: 'Reader start-up screen',\n header: { title: 'Reader Start-up Screen', docsTopic: 'posHardware' },\n Component: PosReaderSplashStaffPage,\n },\n ],\n publicPages: [\n { path: '/pos-display', title: 'Customer display', Component: PosDisplayPage },\n // The self-service kiosk (AGL-3623), paired the same way with a kiosk\n // code; `server/pos-kiosk.ts` refuses every call without its token.\n { path: '/pos-kiosk', title: 'Self-service kiosk', Component: PosKioskPage },\n ],\n // Dashboard/analytics glance card (AGL-419): rendered through the\n // shell's 'commerceGlance' widget slot.\n widgets: [\n {\n slot: 'commerceGlance',\n widgetId: 'commerce-glance',\n title: 'Commerce',\n Component: CommerceGlanceCard,\n },\n // What a site user bought — lifetime total, orders and subscriptions —\n // in their drawer on the site's Users page: the orders and\n // subscriptions are this plugin's.\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.siteMember,\n widgetId: 'commerce-site-member-purchases',\n title: 'Purchases',\n Component: SiteMemberPurchases,\n },\n ],\n navItems: [\n {\n label: 'Products',\n href: '/products',\n icon: { path: mdiStorefrontOutline.path },\n // Sections as ROUTES (AGL-2501): `/products/orders` and friends are\n // real URLs the shell resolves and gates, so the page mounts the one\n // being read instead of subscribing all six.\n sections: COMMERCE_CONSOLE_SECTIONS,\n // `/products/settings` was split into a page per setting (AGL-3713);\n // links to it open the first of them.\n retiredSections: COMMERCE_RETIRED_SECTIONS,\n header: {\n title: 'Products',\n icon: { path: mdiStorefrontOutline.path },\n // The overview, not `catalog`: this surface is the whole commerce\n // console — Catalog, Orders, Promotions and Reservations.\n docsTopic: 'commerce',\n },\n Component: CommerceConsolePage,\n },\n {\n // POS register (AGL-312/419): relocated from the app's /pos route —\n // the generic [pluginSlug] shell now serves it at the same URL.\n label: 'POS',\n href: '/pos',\n /*\n * WHO may open the register, declared on THIS nav item rather than on\n * the extension.\n *\n * A key here narrows one surface; a key on the extension would gate\n * every surface it registers, and Products is the sibling — a catalog\n * anyone who works on the site may open. The register is the one that\n * takes money and lists checked-in guests by name, so the two\n * genuinely have different answers and only this one is narrowed.\n *\n * `managePos` rather than a dotted catalog key because it is the key\n * the sale itself is refused by: `server/pos-order.ts` resolves the\n * member's permission map and returns 403 unless `managePos` holds.\n * The shell answers this declaration out of that same resolved map, so\n * the console offers a register exactly to the people the route will\n * accept a sale from — the gate and its server twin cannot disagree,\n * because they are reading one value.\n *\n * `pos-order.ts` stays the enforcement point regardless. This decides\n * whether the surface is built at all, which is the part a check\n * inside the page cannot do: by the time the page can refuse itself it\n * has mounted and its product, register, location and reservation\n * listeners are already open.\n */\n permission: 'managePos',\n icon: { path: mdiStorefrontOutline.path },\n header: { title: 'Point of Sale', docsTopic: 'pos' },\n Component: PosConsolePage,\n },\n ],\n })\n /*\n * The USER ACCOUNTS half, registered under its own id.\n *\n * `accounts` is a switch rather than a bundle: the member blocks and every\n * `membership/*` handler ship inside this package, which is why the id\n * carries no loader manifest entry of its own. A second extension is how a\n * card about visitor accounts is gated on the switch that decides whether\n * the site serves them — `listConsoleWidgets` filters by the workspace's\n * effective plugin ids, and `accounts` is default-OFF per site.\n *\n * A separate call rather than more widgets on the commerce extension: the\n * registry is keyed by `pluginId` and that key IS the gate, so folding this\n * card in above would have kept the dashboard defect it fixes — `Newest\n * site users` on a site whose /signin returns 404.\n */\n Aglyn.registerConsoleExtension({\n pluginId: Aglyn.ACCOUNTS_PLUGIN_ID,\n displayName: 'User Accounts',\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'accounts-newest-site-users',\n title: 'Newest site users',\n Component: NewestSiteUsersCard,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","mdiStorefrontOutline","lazy","PosConsolePage","PosDisplayPage","PosKioskPage","PosHardwareOrdersStaffPage","PosReaderSplashStaffPage","CommerceGlanceCard","NewestSiteUsersCard","SiteMemberPurchases","COMMERCE_CONSOLE_SECTIONS","COMMERCE_RETIRED_SECTIONS","BUNDLE_ID","COMMERCE_PERMISSIONS","registerCommerceRecordLists","registerCommerceRecordRoutes","registerCommerceZones","registerCommerceOrderZones","registerCommerceReturnZones","registerCommerceStoreZones","registerCommercePosZones","registerCommerceTransferUi","COMMERCE_SEARCH_SOURCES","COMMERCE_CONFIG_SCHEMA","CommerceConsolePage","registerCommerceConsole","registerPluginPermissions","registerPluginConfigSchema","registerConsoleExtension","pluginId","displayName","searchSources","staffPages","id","label","header","title","docsTopic","Component","publicPages","path","widgets","slot","widgetId","CONSOLE_WIDGET_SLOTS","siteMember","navItems","href","icon","sections","retiredSections","permission","ACCOUNTS_PLUGIN_ID","hostDashboard"],"mappings":"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"}
@@ -191,7 +191,9 @@ import { boundedCreditCents, checkoutCreditProviderForCode, encodeCheckoutCredit
191
191
  cart.lines.forEach((line, index)=>{
192
192
  var _product_type, _variant_id, _variant_sku, _variant_weightGrams, _product_type1, _product_type2, _variant_options, _variant_options1;
193
193
  const product = productsById.get(line.productId);
194
- if (!product || product.deletedAt || product.status !== 'active') {
194
+ // Sold in person only (AGL-4060) is not for sale here, whatever the
195
+ // cart was holding when the switch went off.
196
+ if (!product || !CommerceModel.productListedOnline(product)) {
195
197
  var _ref;
196
198
  throw Object.assign(new Error('unavailable'), {
197
199
  visible: `"${(_ref = product == null ? void 0 : product.name) != null ? _ref : 'A product'}" is no longer available`