@aglyn/plugins-commerce 1.0.0-beta.219 → 1.0.0-beta.221

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 (83) hide show
  1. package/README.md +5 -0
  2. package/package.json +14 -14
  3. package/src/lib/components/console/catalog-organization-card.component.js +626 -446
  4. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  5. package/src/lib/components/console/payments-settings-card.component.js +1 -1
  6. package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
  7. package/src/lib/components/console/product-editor-dialog.component.js +1127 -846
  8. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  9. package/src/lib/components/console/site-member-purchases.component.d.ts +24 -0
  10. package/src/lib/components/console/site-member-purchases.component.js +339 -0
  11. package/src/lib/components/console/site-member-purchases.component.js.map +1 -0
  12. package/src/lib/components/member-signup.js +2 -2
  13. package/src/lib/components/member-signup.js.map +1 -1
  14. package/src/lib/components/newsletter-signup.js +2 -2
  15. package/src/lib/components/newsletter-signup.js.map +1 -1
  16. package/src/lib/constants/operator-alerts.js +5 -3
  17. package/src/lib/constants/operator-alerts.js.map +1 -1
  18. package/src/lib/model/commerce-io.js +1 -1
  19. package/src/lib/model/commerce-io.js.map +1 -1
  20. package/src/lib/model/commerce-record-lists.d.ts +31 -0
  21. package/src/lib/model/commerce-record-lists.js +45 -0
  22. package/src/lib/model/commerce-record-lists.js.map +1 -0
  23. package/src/lib/model/commerce-record-routes.d.ts +18 -0
  24. package/src/lib/model/commerce-record-routes.js +1 -1
  25. package/src/lib/model/commerce-record-routes.js.map +1 -1
  26. package/src/lib/model/commerce-search-sources.js +4 -2
  27. package/src/lib/model/commerce-search-sources.js.map +1 -1
  28. package/src/lib/model/commerce-topics.d.ts +20 -0
  29. package/src/lib/model/commerce-topics.js +33 -0
  30. package/src/lib/model/commerce-topics.js.map +1 -0
  31. package/src/lib/model/index.d.ts +1 -0
  32. package/src/lib/model/index.js +1 -0
  33. package/src/lib/model/index.js.map +1 -1
  34. package/src/lib/model/product-record.d.ts +32 -0
  35. package/src/lib/model/product-record.js +82 -0
  36. package/src/lib/model/product-record.js.map +1 -0
  37. package/src/lib/model/site-member-purchases.d.ts +48 -0
  38. package/src/lib/model/site-member-purchases.js +50 -0
  39. package/src/lib/model/site-member-purchases.js.map +1 -0
  40. package/src/lib/plugin.js +12 -0
  41. package/src/lib/plugin.js.map +1 -1
  42. package/src/lib/server/billing-webhook.js +410 -208
  43. package/src/lib/server/billing-webhook.js.map +1 -1
  44. package/src/lib/server/cart-checkout.js +1 -1
  45. package/src/lib/server/cart-checkout.js.map +1 -1
  46. package/src/lib/server/gift-cards.js +7 -3
  47. package/src/lib/server/gift-cards.js.map +1 -1
  48. package/src/lib/server/low-stock.js +3 -1
  49. package/src/lib/server/low-stock.js.map +1 -1
  50. package/src/lib/server/member-post.js +2 -2
  51. package/src/lib/server/member-post.js.map +1 -1
  52. package/src/lib/server/membership-admin-password.js +4 -1
  53. package/src/lib/server/membership-admin-password.js.map +1 -1
  54. package/src/lib/server/membership-register.js +13 -2
  55. package/src/lib/server/membership-register.js.map +1 -1
  56. package/src/lib/server/newsletter.js +32 -19
  57. package/src/lib/server/newsletter.js.map +1 -1
  58. package/src/lib/server/pos-order.js +18 -10
  59. package/src/lib/server/pos-order.js.map +1 -1
  60. package/src/lib/server/process-abandoned.js +4 -2
  61. package/src/lib/server/process-abandoned.js.map +1 -1
  62. package/src/lib/server/process-restock.js +7 -5
  63. package/src/lib/server/process-restock.js.map +1 -1
  64. package/src/lib/server/product-record-index.js +5 -37
  65. package/src/lib/server/product-record-index.js.map +1 -1
  66. package/src/lib/server/reconcile-stock.js +1 -1
  67. package/src/lib/server/reconcile-stock.js.map +1 -1
  68. package/src/lib/server/reserve-stock.js +6 -1
  69. package/src/lib/server/reserve-stock.js.map +1 -1
  70. package/src/lib/server/revenue-source.js +3 -3
  71. package/src/lib/server/revenue-source.js.map +1 -1
  72. package/src/lib/server/supplier-outbox.js +16 -1
  73. package/src/lib/server/supplier-outbox.js.map +1 -1
  74. package/src/lib/server/supplier-update.js +19 -9
  75. package/src/lib/server/supplier-update.js.map +1 -1
  76. package/src/lib/starter-templates.d.ts +19 -0
  77. package/src/lib/starter-templates.js +157 -0
  78. package/src/lib/starter-templates.js.map +1 -0
  79. package/src/lib/tenant-emails.js +54 -14
  80. package/src/lib/tenant-emails.js.map +1 -1
  81. package/src/lib/testing/stand-in-record-system.d.ts +11 -2
  82. package/src/lib/testing/stand-in-record-system.js +5 -3
  83. package/src/lib/testing/stand-in-record-system.js.map +1 -1
@@ -0,0 +1,19 @@
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 StarterTemplate } from '@aglyn/aglyn/app-utils/starter-template-nodes';
18
+ /** This plugin's starters, as the manifest generator compiles them. */
19
+ export declare function commerceStarterTemplates(): StarterTemplate[];
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { buildStarterNodes, starterHeroSection, starterSection, starterText } from "@aglyn/aglyn/app-utils/starter-template-nodes";
17
+ import { SCREEN_ROOT_PATH } from "@aglyn/aglyn/app-utils/screen-route";
18
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
19
+ /**
20
+ * The shop starters (AGL-300): storefront sites built around this plugin's
21
+ * elements — the product grid, the product page, the cart and the customer
22
+ * account.
23
+ *
24
+ * Declared under `starterTemplates` in plugins.config.json; the manifest
25
+ * generator calls {@link commerceStarterTemplates} and compiles what it
26
+ * returns into core's `plugin-starter-templates.generated.ts`, after the
27
+ * platform's own starters. The gallery, the seed route and the AI plugin's
28
+ * examples read that list, so none of them loads this plugin to offer a shop.
29
+ *
30
+ * PERSISTED IDENTIFIERS: the starter ids, the screen keys and every node id
31
+ * below appear in stored documents — seeded template ids are derived from
32
+ * the first two — so none is ever renamed.
33
+ */ /** One of this plugin's elements, placed on a starter page. */ const commerceBlock = (id, componentId, props)=>({
34
+ id,
35
+ componentId,
36
+ pluginId: BUNDLE_ID,
37
+ props
38
+ });
39
+ /** The pages both shop starters share. */ function shopScreens(prefix, digital) {
40
+ return [
41
+ {
42
+ key: 'home',
43
+ displayName: 'Home',
44
+ // The site root, spelled the way the routing map spells it. `''` here
45
+ // read as "no address" everywhere downstream and put the shop's home
46
+ // page at `/home` (AGL-1575).
47
+ slug: SCREEN_ROOT_PATH,
48
+ seo: {
49
+ title: digital ? 'Digital shop' : 'Shop',
50
+ description: 'Browse our products.'
51
+ },
52
+ nodes: buildStarterNodes([
53
+ starterHeroSection(`${prefix}h_`, digital ? 'Downloads that level you up' : 'Gear you can trust', digital ? 'Instant delivery. Lifetime updates.' : 'Quality parts, shipped fast.'),
54
+ starterSection(`${prefix}h_gridSection`, 'xl', 6, [
55
+ commerceBlock(`${prefix}h_grid`, 'product-grid', {
56
+ source: 'all',
57
+ sort: 'newest',
58
+ columns: 3,
59
+ maxItems: 6
60
+ })
61
+ ]),
62
+ // A newsletter capture is a short text-led band, not a gallery.
63
+ starterSection(`${prefix}h_newsSection`, 'md', 6, [
64
+ commerceBlock(`${prefix}h_news`, 'newsletter-signup', {
65
+ heading: 'Get updates and offers'
66
+ })
67
+ ])
68
+ ])
69
+ },
70
+ {
71
+ key: 'shop',
72
+ displayName: 'Shop',
73
+ slug: 'shop',
74
+ seo: {
75
+ title: 'All products'
76
+ },
77
+ nodes: buildStarterNodes([
78
+ starterSection(`${prefix}s_section`, 'xl', 6, [
79
+ starterText(`${prefix}s_title`, 'h3', 'All products'),
80
+ commerceBlock(`${prefix}s_grid`, 'product-grid', {
81
+ source: 'all',
82
+ columns: 4,
83
+ showFilters: true
84
+ })
85
+ ])
86
+ ])
87
+ },
88
+ {
89
+ key: 'product',
90
+ displayName: 'Product page',
91
+ slug: 'product',
92
+ seo: {
93
+ title: 'Product'
94
+ },
95
+ nodes: buildStarterNodes([
96
+ starterSection(`${prefix}p_section`, 'xl', 6, [
97
+ commerceBlock(`${prefix}p_detail`, 'product-detail', {})
98
+ ])
99
+ ])
100
+ },
101
+ {
102
+ key: 'cart',
103
+ displayName: 'Cart',
104
+ slug: 'cart',
105
+ seo: {
106
+ title: 'Your cart'
107
+ },
108
+ // LG, the deliberate middle case: a cart is a wide table but it is read
109
+ // line by line, so a full XL band spreads it further than the eye
110
+ // tracks. Same for the account screen below.
111
+ nodes: buildStarterNodes([
112
+ starterSection(`${prefix}c_section`, 'lg', 6, [
113
+ starterText(`${prefix}c_title`, 'h3', 'Your cart'),
114
+ commerceBlock(`${prefix}c_cart`, 'cart', {
115
+ variant: 'inline',
116
+ showCoupon: true
117
+ })
118
+ ])
119
+ ])
120
+ },
121
+ {
122
+ key: 'account',
123
+ displayName: 'Account',
124
+ slug: 'account',
125
+ seo: {
126
+ title: 'Your account'
127
+ },
128
+ nodes: buildStarterNodes([
129
+ starterSection(`${prefix}a_section`, 'lg', 6, [
130
+ commerceBlock(`${prefix}a_account`, 'customer-account', {
131
+ signedOutHeading: 'Your account'
132
+ })
133
+ ])
134
+ ])
135
+ }
136
+ ];
137
+ }
138
+ /** This plugin's starters, as the manifest generator compiles them. */ export function commerceStarterTemplates() {
139
+ return [
140
+ {
141
+ id: 'physical-shop',
142
+ displayName: 'Shop (physical products)',
143
+ description: 'Storefront starter: home with featured products, filterable shop, ' + 'product page, cart, and customer accounts. After applying, set the ' + 'Product page as the product template in Store settings.',
144
+ category: 'Commerce',
145
+ screens: shopScreens('ps_', false)
146
+ },
147
+ {
148
+ id: 'digital-shop',
149
+ displayName: 'Shop (digital products)',
150
+ description: 'Digital storefront starter: downloads-focused home, shop, product ' + 'page, cart, and accounts with a newsletter capture. Set the Product ' + 'page as the product template in Store settings after applying.',
151
+ category: 'Commerce',
152
+ screens: shopScreens('ds_', true)
153
+ }
154
+ ];
155
+ }
156
+
157
+ //# sourceMappingURL=starter-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/starter-templates.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n buildStarterNodes,\n starterHeroSection,\n starterSection,\n starterText,\n type StarterNodeSpec,\n type StarterTemplate,\n type StarterTemplateScreen,\n} from '@aglyn/aglyn/app-utils/starter-template-nodes'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/**\n * The shop starters (AGL-300): storefront sites built around this plugin's\n * elements — the product grid, the product page, the cart and the customer\n * account.\n *\n * Declared under `starterTemplates` in plugins.config.json; the manifest\n * generator calls {@link commerceStarterTemplates} and compiles what it\n * returns into core's `plugin-starter-templates.generated.ts`, after the\n * platform's own starters. The gallery, the seed route and the AI plugin's\n * examples read that list, so none of them loads this plugin to offer a shop.\n *\n * PERSISTED IDENTIFIERS: the starter ids, the screen keys and every node id\n * below appear in stored documents — seeded template ids are derived from\n * the first two — so none is ever renamed.\n */\n\n/** One of this plugin's elements, placed on a starter page. */\nconst commerceBlock = (\n id: string,\n componentId: string,\n props?: Record<string, unknown>,\n): StarterNodeSpec => ({ id, componentId, pluginId: BUNDLE_ID, props })\n\n/** The pages both shop starters share. */\nfunction shopScreens(prefix: string, digital: boolean): StarterTemplateScreen[] {\n return [\n {\n key: 'home',\n displayName: 'Home',\n // The site root, spelled the way the routing map spells it. `''` here\n // read as \"no address\" everywhere downstream and put the shop's home\n // page at `/home` (AGL-1575).\n slug: SCREEN_ROOT_PATH,\n seo: {\n title: digital ? 'Digital shop' : 'Shop',\n description: 'Browse our products.',\n },\n nodes: buildStarterNodes([\n starterHeroSection(\n `${prefix}h_`,\n digital ? 'Downloads that level you up' : 'Gear you can trust',\n digital\n ? 'Instant delivery. Lifetime updates.'\n : 'Quality parts, shipped fast.',\n ),\n starterSection(`${prefix}h_gridSection`, 'xl', 6, [\n commerceBlock(`${prefix}h_grid`, 'product-grid', {\n source: 'all',\n sort: 'newest',\n columns: 3,\n maxItems: 6,\n }),\n ]),\n // A newsletter capture is a short text-led band, not a gallery.\n starterSection(`${prefix}h_newsSection`, 'md', 6, [\n commerceBlock(`${prefix}h_news`, 'newsletter-signup', {\n heading: 'Get updates and offers',\n }),\n ]),\n ]),\n },\n {\n key: 'shop',\n displayName: 'Shop',\n slug: 'shop',\n seo: { title: 'All products' },\n nodes: buildStarterNodes([\n starterSection(`${prefix}s_section`, 'xl', 6, [\n starterText(`${prefix}s_title`, 'h3', 'All products'),\n commerceBlock(`${prefix}s_grid`, 'product-grid', {\n source: 'all',\n columns: 4,\n showFilters: true,\n }),\n ]),\n ]),\n },\n {\n key: 'product',\n displayName: 'Product page',\n slug: 'product',\n seo: { title: 'Product' },\n nodes: buildStarterNodes([\n starterSection(`${prefix}p_section`, 'xl', 6, [\n commerceBlock(`${prefix}p_detail`, 'product-detail', {}),\n ]),\n ]),\n },\n {\n key: 'cart',\n displayName: 'Cart',\n slug: 'cart',\n seo: { title: 'Your cart' },\n // LG, the deliberate middle case: a cart is a wide table but it is read\n // line by line, so a full XL band spreads it further than the eye\n // tracks. Same for the account screen below.\n nodes: buildStarterNodes([\n starterSection(`${prefix}c_section`, 'lg', 6, [\n starterText(`${prefix}c_title`, 'h3', 'Your cart'),\n commerceBlock(`${prefix}c_cart`, 'cart', {\n variant: 'inline',\n showCoupon: true,\n }),\n ]),\n ]),\n },\n {\n key: 'account',\n displayName: 'Account',\n slug: 'account',\n seo: { title: 'Your account' },\n nodes: buildStarterNodes([\n starterSection(`${prefix}a_section`, 'lg', 6, [\n commerceBlock(`${prefix}a_account`, 'customer-account', {\n signedOutHeading: 'Your account',\n }),\n ]),\n ]),\n },\n ]\n}\n\n/** This plugin's starters, as the manifest generator compiles them. */\nexport function commerceStarterTemplates(): StarterTemplate[] {\n return [\n {\n id: 'physical-shop',\n displayName: 'Shop (physical products)',\n description:\n 'Storefront starter: home with featured products, filterable shop, ' +\n 'product page, cart, and customer accounts. After applying, set the ' +\n 'Product page as the product template in Store settings.',\n category: 'Commerce',\n screens: shopScreens('ps_', false),\n },\n {\n id: 'digital-shop',\n displayName: 'Shop (digital products)',\n description:\n 'Digital storefront starter: downloads-focused home, shop, product ' +\n 'page, cart, and accounts with a newsletter capture. Set the Product ' +\n 'page as the product template in Store settings after applying.',\n category: 'Commerce',\n screens: shopScreens('ds_', true),\n },\n ]\n}\n"],"names":["buildStarterNodes","starterHeroSection","starterSection","starterText","SCREEN_ROOT_PATH","BUNDLE_ID","commerceBlock","id","componentId","props","pluginId","shopScreens","prefix","digital","key","displayName","slug","seo","title","description","nodes","source","sort","columns","maxItems","heading","showFilters","variant","showCoupon","signedOutHeading","commerceStarterTemplates","category","screens"],"mappings":"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"}
@@ -52,6 +52,11 @@
52
52
  name: 'order.ref',
53
53
  description: 'Order reference id',
54
54
  sample: 'cs_test_123'
55
+ },
56
+ {
57
+ name: 'store.receiptFooter',
58
+ description: 'The Receipt footer from the store settings; empty when none is set',
59
+ sample: 'Returns are accepted within 30 days.'
55
60
  }
56
61
  ],
57
62
  defaultBody: [
@@ -60,6 +65,11 @@
60
65
  text: 'Thanks for your purchase!',
61
66
  variant: 'heading'
62
67
  },
68
+ {
69
+ block: 'text',
70
+ text: 'Here is the receipt for your order from {{host.businessName}}.',
71
+ variant: 'body'
72
+ },
63
73
  {
64
74
  block: 'text',
65
75
  text: '{{order.summary}}',
@@ -67,7 +77,12 @@
67
77
  },
68
78
  {
69
79
  block: 'text',
70
- text: 'Total: {{order.total}}',
80
+ text: 'Total charged: {{order.total}}',
81
+ variant: 'body'
82
+ },
83
+ {
84
+ block: 'text',
85
+ text: '{{store.receiptFooter}}',
71
86
  variant: 'body'
72
87
  },
73
88
  {
@@ -163,9 +178,14 @@
163
178
  },
164
179
  {
165
180
  name: 'reservation.paid',
166
- description: 'Amount paid today',
181
+ description: 'What the guest was charged today, lodging tax included; a ' + 'charge that carried lodging tax names it, as in “$254.40, ' + 'including $14.40 lodging tax”',
167
182
  sample: '$240.00'
168
183
  },
184
+ {
185
+ name: 'reservation.balance',
186
+ description: 'What is still owed for the stay and where it is paid, as a ' + 'sentence; empty when the stay is paid in full',
187
+ sample: 'Still to pay: $360.00, at the property. It has not been charged.'
188
+ },
169
189
  {
170
190
  name: 'reservation.ref',
171
191
  description: 'Reservation reference id',
@@ -180,7 +200,7 @@
180
200
  },
181
201
  {
182
202
  block: 'text',
183
- text: 'Your stay is confirmed!',
203
+ text: 'Your stay at {{host.businessName}} is confirmed.',
184
204
  variant: 'body'
185
205
  },
186
206
  {
@@ -198,6 +218,11 @@
198
218
  text: 'Paid today: {{reservation.paid}}',
199
219
  variant: 'body'
200
220
  },
221
+ {
222
+ block: 'text',
223
+ text: '{{reservation.balance}}',
224
+ variant: 'body'
225
+ },
201
226
  {
202
227
  block: 'text',
203
228
  text: 'Reference: {{reservation.ref}}',
@@ -213,7 +238,7 @@
213
238
  pluginId: 'commerce',
214
239
  plugin: 'Commerce',
215
240
  control: 'besigner',
216
- defaultSubject: 'Your gift card',
241
+ defaultSubject: 'Your gift card for {{host.businessName}}',
217
242
  mergeTokens: [
218
243
  {
219
244
  name: 'giftcard.code',
@@ -224,6 +249,11 @@
224
249
  name: 'giftcard.value',
225
250
  description: 'The gift card value',
226
251
  sample: '$25.00'
252
+ },
253
+ {
254
+ name: 'giftcard.note',
255
+ description: 'The note written when the card was issued by hand; empty for a ' + 'card bought at checkout',
256
+ sample: 'Happy birthday, Sam!'
227
257
  }
228
258
  ],
229
259
  defaultBody: [
@@ -234,18 +264,28 @@
234
264
  },
235
265
  {
236
266
  block: 'text',
237
- text: 'Gift card code: {{giftcard.code}}',
267
+ text: 'You have a {{giftcard.value}} gift card for {{host.businessName}}.',
268
+ variant: 'body'
269
+ },
270
+ {
271
+ block: 'text',
272
+ text: '{{giftcard.note}}',
238
273
  variant: 'body'
239
274
  },
240
275
  {
241
276
  block: 'text',
242
- text: 'Value: {{giftcard.value}}',
277
+ text: 'Gift card code: {{giftcard.code}}',
243
278
  variant: 'body'
244
279
  },
245
280
  {
246
281
  block: 'text',
247
- text: 'Enter it at checkout to apply the balance.',
282
+ text: 'Enter the code at checkout on {{host.url}} to use its balance.',
248
283
  variant: 'body'
284
+ },
285
+ {
286
+ block: 'button',
287
+ label: 'Visit {{host.businessName}}',
288
+ href: '{{host.url}}'
249
289
  }
250
290
  ],
251
291
  footerReason: 'You’re receiving this because a gift card from {{host.businessName}} was sent to this address.'
@@ -275,13 +315,13 @@
275
315
  {
276
316
  name: 'product.url',
277
317
  description: 'Link to the product page',
278
- sample: '/products/house-blend'
318
+ sample: 'https://shop.example.com/products/house-blend'
279
319
  }
280
320
  ],
281
321
  defaultBody: [
282
322
  {
283
323
  block: 'text',
284
- text: '{{product.name}} is available again — grab it before it sells out.',
324
+ text: '{{product.name}} is back in stock at {{host.businessName}}.',
285
325
  variant: 'body'
286
326
  },
287
327
  {
@@ -313,7 +353,7 @@
313
353
  defaultBody: [
314
354
  {
315
355
  block: 'text',
316
- text: 'Your cart is still waiting — pick up where you left off.',
356
+ text: 'You left items in your cart at {{host.businessName}}. Pick up ' + 'where you left off.',
317
357
  variant: 'body'
318
358
  },
319
359
  {
@@ -323,7 +363,7 @@
323
363
  },
324
364
  {
325
365
  block: 'text',
326
- text: 'Your items are held but not reserved, so they may sell out.',
366
+ text: 'Nothing in your cart is held for you.',
327
367
  variant: 'caption'
328
368
  }
329
369
  ],
@@ -445,7 +485,7 @@
445
485
  },
446
486
  {
447
487
  block: 'text',
448
- text: 'An administrator of {{site.name}} set a new password on your ' + 'account. You have been signed out on every device and will need ' + 'the new password to sign back in.',
488
+ text: 'An administrator of {{site.name}} set a new password on your ' + 'account and signed you out on every device. To sign back in, ' + 'get the new password from them, or choose "Forgot password?" on ' + 'the sign-in page to set your own.',
449
489
  variant: 'body'
450
490
  },
451
491
  {
@@ -473,7 +513,7 @@
473
513
  {
474
514
  name: 'stream.name',
475
515
  description: 'What they signed up for',
476
- sample: 'our newsletter'
516
+ sample: 'Newsletter'
477
517
  },
478
518
  {
479
519
  name: 'confirmUrl',
@@ -489,7 +529,7 @@
489
529
  },
490
530
  {
491
531
  block: 'text',
492
- text: 'Please confirm that you want to receive {{stream.name}} at this address.',
532
+ text: 'Please confirm that you want to get {{stream.name}} emails from ' + '{{host.businessName}} at this address.',
493
533
  variant: 'body'
494
534
  },
495
535
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/tenant-emails.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 { TenantEmailEntry } from '@aglyn/shared-util-email'\n\n/**\n * The emails a site sends its own customers through Commerce (AGL-769/770).\n *\n * Declared here, by the plugin that sends them, and compiled into the\n * platform's tenant email catalog (`TENANT_EMAILS` in\n * `@aglyn/shared-util-email`) by `tools/scripts/generate-plugin-manifests.mjs`\n * (AGL-3080): the catalog's readers include the send path, which loads no\n * plugin code, so a runtime registry it had not filled would quietly send\n * the text card in place of the site's designed email. Regenerate after\n * changing an entry; `--check` refuses a stale catalog.\n *\n * Most commerce sends are designable in the email besigner; a `fixed` entry\n * flips to `besigner` when its send site is wired to `renderHostEmail`\n * (AGL-770).\n */\nexport function commerceTenantEmails(): readonly TenantEmailEntry[] {\n return [\n {\n key: 'order-receipt',\n name: 'Order receipt',\n description: 'Sent to the buyer after a successful order or checkout.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Receipt for your order',\n mergeTokens: [\n {\n name: 'order.summary',\n description: 'The ordered items, one per line (with any license/download links)',\n sample: 'House Blend × 2 — $24.00',\n },\n {\n name: 'order.total',\n description: 'Order total',\n sample: '$24.00',\n },\n {\n name: 'order.ref',\n description: 'Order reference id',\n sample: 'cs_test_123',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Thanks for your purchase!', variant: 'heading' },\n { block: 'text', text: '{{order.summary}}', variant: 'body' },\n { block: 'text', text: 'Total: {{order.total}}', variant: 'body' },\n {\n block: 'text',\n text: 'Order reference: {{order.ref}}',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you placed an order with {{host.businessName}}.',\n },\n {\n key: 'sale-notification',\n name: 'New sale',\n description: 'Notifies the seller when an order is placed.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'New order on {{site.name}}',\n mergeTokens: [\n {\n name: 'site.name',\n description: 'The site the sale happened on',\n sample: 'Northwind Coffee',\n },\n {\n name: 'order.summary',\n description: 'The ordered items',\n sample: 'House Blend — $12.00',\n },\n { name: 'order.total', description: 'Order total', sample: '$12.00' },\n {\n name: 'buyer.email',\n description: \"The buyer's email\",\n sample: 'buyer@example.com',\n },\n {\n name: 'order.ref',\n description: 'Order reference id',\n sample: 'cs_test_123',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'You made a sale', variant: 'heading' },\n {\n block: 'text',\n text: 'A new order came in on {{site.name}}.',\n variant: 'body',\n },\n { block: 'text', text: '{{order.summary}}', variant: 'body' },\n { block: 'text', text: 'Total: {{order.total}}', variant: 'body' },\n {\n block: 'text',\n text: 'Buyer: {{buyer.email}} · Order {{order.ref}}',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you manage {{host.businessName}}.',\n },\n {\n key: 'reservation-confirmed',\n name: 'Reservation confirmed',\n description: 'Confirms a paid reservation to the customer.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Reservation confirmed',\n mergeTokens: [\n {\n name: 'reservation.checkIn',\n description: 'Check-in date',\n sample: 'Mon, 01 Jun 2026',\n },\n {\n name: 'reservation.nights',\n description: 'Number of nights',\n sample: '2',\n },\n {\n name: 'reservation.paid',\n description: 'Amount paid today',\n sample: '$240.00',\n },\n {\n name: 'reservation.ref',\n description: 'Reservation reference id',\n sample: 'resv_123',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Reservation confirmed', variant: 'heading' },\n { block: 'text', text: 'Your stay is confirmed!', variant: 'body' },\n {\n block: 'text',\n text: 'Check-in: {{reservation.checkIn}}',\n variant: 'body',\n },\n { block: 'text', text: 'Nights: {{reservation.nights}}', variant: 'body' },\n {\n block: 'text',\n text: 'Paid today: {{reservation.paid}}',\n variant: 'body',\n },\n {\n block: 'text',\n text: 'Reference: {{reservation.ref}}',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you made a reservation with {{host.businessName}}.',\n },\n {\n key: 'gift-card',\n name: 'Gift card delivery',\n description: 'Delivers a purchased gift card to its recipient.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Your gift card',\n mergeTokens: [\n {\n name: 'giftcard.code',\n description: 'The gift card code',\n sample: 'GC-ABC123DEF456',\n },\n {\n name: 'giftcard.value',\n description: 'The gift card value',\n sample: '$25.00',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Your gift card', variant: 'heading' },\n {\n block: 'text',\n text: 'Gift card code: {{giftcard.code}}',\n variant: 'body',\n },\n { block: 'text', text: 'Value: {{giftcard.value}}', variant: 'body' },\n {\n block: 'text',\n text: 'Enter it at checkout to apply the balance.',\n variant: 'body',\n },\n ],\n footerReason:\n 'You’re receiving this because a gift card from {{host.businessName}} was sent to this address.',\n },\n {\n key: 'supplier-fulfillment',\n name: 'Supplier fulfillment',\n description: 'Tells a dropship supplier there is a new order to fulfill.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'fixed',\n },\n {\n key: 'back-in-stock',\n name: 'Back in stock',\n description: 'Tells a customer a product they watched is available again.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Back in stock: {{product.name}}',\n mergeTokens: [\n {\n name: 'product.name',\n description: 'The product that restocked',\n sample: 'House Blend',\n },\n {\n name: 'product.url',\n description: 'Link to the product page',\n sample: '/products/house-blend',\n },\n ],\n defaultBody: [\n {\n block: 'text',\n text:\n '{{product.name}} is available again — grab it before it sells out.',\n variant: 'body',\n },\n { block: 'button', label: 'View product', href: '{{product.url}}' },\n ],\n footerReason:\n 'You’re receiving this because you asked {{host.businessName}} to tell you when this was back.',\n },\n {\n key: 'abandoned-cart',\n name: 'Abandoned cart',\n description: 'Reminds a shopper of items left in their cart.',\n // The cron refuses to send this without the entitlement\n // (`process-abandoned.ts:92`), so the console has to say so (AGL-2081).\n requiresFeature: 'abandonedCart',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'You left something in your cart',\n mergeTokens: [\n {\n name: 'cart.url',\n description: 'Link back to the shopper cart',\n sample: 'https://shop.example.com/cart',\n },\n ],\n defaultBody: [\n {\n block: 'text',\n text: 'Your cart is still waiting — pick up where you left off.',\n variant: 'body',\n },\n { block: 'button', label: 'Return to cart', href: '{{cart.url}}' },\n {\n block: 'text',\n text:\n 'Your items are held but not reserved, so they may sell out.',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you started a checkout with {{host.businessName}}.',\n },\n {\n key: 'member-post',\n name: 'New member post',\n description: 'Notifies members when new members-only content is posted.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: '{{post.title}}',\n mergeTokens: [\n { name: 'post.title', description: 'The post’s title', sample: 'This month in the studio' },\n {\n name: 'post.body',\n description: 'The post, as its author wrote it',\n sample: 'Three new pieces are up for members first.',\n },\n ],\n defaultBody: [\n { block: 'text', text: '{{post.title}}', variant: 'heading' },\n { block: 'text', text: '{{post.body}}', variant: 'body' },\n ],\n footerReason: 'You’re receiving this because you’re a member of {{host.businessName}}.',\n },\n {\n key: 'member-password-reset',\n name: 'Member password reset',\n description:\n \"Resets a site member's password — a store-member account, separate \" +\n 'from a console login. Sent when the member asks, and when a site ' +\n 'administrator starts a reset for them.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Reset your {{site.name}} password',\n mergeTokens: [\n { name: 'site.name', description: 'The site’s name', sample: 'Northwind Coffee' },\n {\n name: 'reset.intro',\n description: 'Who started the reset',\n sample: 'Someone asked to reset the password for your Northwind Coffee account.',\n },\n {\n name: 'resetUrl',\n description: 'The one-time reset link',\n sample: 'https://shop.example.com/recover?token=…',\n },\n {\n name: 'reset.note',\n description: 'How long the link lasts, and what to do if unexpected',\n sample:\n 'The link works once and expires in 1 hour. If you did not ask for ' +\n 'this, you can safely ignore this email — your password is unchanged.',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Reset your password', variant: 'heading' },\n { block: 'text', text: '{{reset.intro}}', variant: 'body' },\n { block: 'button', label: 'Set a new password', href: '{{resetUrl}}' },\n { block: 'text', text: '{{reset.note}}', variant: 'caption' },\n ],\n footerReason:\n 'You’re receiving this because a password reset was started for your ' +\n '{{host.businessName}} account.',\n },\n {\n key: 'member-password-changed',\n name: 'Member password changed',\n description:\n 'Tells a site member that a site administrator set a new password on ' +\n 'their account.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Your {{site.name}} password was changed',\n mergeTokens: [\n { name: 'site.name', description: 'The site’s name', sample: 'Northwind Coffee' },\n {\n name: 'signInUrl',\n description: 'Where the member signs back in',\n sample: 'https://shop.example.com',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Your password was changed', variant: 'heading' },\n {\n block: 'text',\n text:\n 'An administrator of {{site.name}} set a new password on your ' +\n 'account. You have been signed out on every device and will need ' +\n 'the new password to sign back in.',\n variant: 'body',\n },\n { block: 'button', label: 'Sign in', href: '{{signInUrl}}' },\n {\n block: 'text',\n text: 'If you did not expect this, contact the site owner.',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because the password on your ' +\n '{{host.businessName}} account was changed.',\n },\n {\n key: 'newsletter-confirmation',\n name: 'Subscription confirmation',\n description:\n 'Asks someone who signed up for a site’s emails to confirm the address ' +\n 'before anything is sent to it.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Confirm your subscription',\n mergeTokens: [\n {\n name: 'stream.name',\n description: 'What they signed up for',\n sample: 'our newsletter',\n },\n {\n name: 'confirmUrl',\n description: 'The confirmation link',\n sample: 'https://shop.example.com/subscribe/confirm?token=…',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Confirm your subscription', variant: 'heading' },\n {\n block: 'text',\n text: 'Please confirm that you want to receive {{stream.name}} at this address.',\n variant: 'body',\n },\n { block: 'button', label: 'Confirm my subscription', href: '{{confirmUrl}}' },\n {\n block: 'text',\n text:\n 'The link works for three days. If you did not sign up, ignore this ' +\n 'message — nothing will be sent.',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because this address was entered on ' +\n '{{host.businessName}}’s signup form.',\n },\n ]\n}\n"],"names":["commerceTenantEmails","key","name","description","pluginId","plugin","control","defaultSubject","mergeTokens","sample","defaultBody","block","text","variant","footerReason","label","href","requiresFeature"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/tenant-emails.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 { TenantEmailEntry } from '@aglyn/shared-util-email'\n\n/**\n * The emails a site sends its own customers through Commerce (AGL-769/770).\n *\n * Declared here, by the plugin that sends them, and compiled into the\n * platform's tenant email catalog (`TENANT_EMAILS` in\n * `@aglyn/shared-util-email`) by `tools/scripts/generate-plugin-manifests.mjs`\n * (AGL-3080): the catalog's readers include the send path, which loads no\n * plugin code, so a runtime registry it had not filled would quietly send\n * the text card in place of the site's designed email. Regenerate after\n * changing an entry; `--check` refuses a stale catalog.\n *\n * Most commerce sends are designable in the email besigner; a `fixed` entry\n * flips to `besigner` when its send site is wired to `renderHostEmail`\n * (AGL-770).\n */\nexport function commerceTenantEmails(): readonly TenantEmailEntry[] {\n return [\n {\n key: 'order-receipt',\n name: 'Order receipt',\n description: 'Sent to the buyer after a successful order or checkout.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Receipt for your order',\n mergeTokens: [\n {\n name: 'order.summary',\n description: 'The ordered items, one per line (with any license/download links)',\n sample: 'House Blend × 2 — $24.00',\n },\n {\n name: 'order.total',\n description: 'Order total',\n sample: '$24.00',\n },\n {\n name: 'order.ref',\n description: 'Order reference id',\n sample: 'cs_test_123',\n },\n {\n name: 'store.receiptFooter',\n description:\n 'The Receipt footer from the store settings; empty when none is set',\n sample: 'Returns are accepted within 30 days.',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Thanks for your purchase!', variant: 'heading' },\n {\n block: 'text',\n text: 'Here is the receipt for your order from {{host.businessName}}.',\n variant: 'body',\n },\n { block: 'text', text: '{{order.summary}}', variant: 'body' },\n { block: 'text', text: 'Total charged: {{order.total}}', variant: 'body' },\n { block: 'text', text: '{{store.receiptFooter}}', variant: 'body' },\n {\n block: 'text',\n text: 'Order reference: {{order.ref}}',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you placed an order with {{host.businessName}}.',\n },\n {\n key: 'sale-notification',\n name: 'New sale',\n description: 'Notifies the seller when an order is placed.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'New order on {{site.name}}',\n mergeTokens: [\n {\n name: 'site.name',\n description: 'The site the sale happened on',\n sample: 'Northwind Coffee',\n },\n {\n name: 'order.summary',\n description: 'The ordered items',\n sample: 'House Blend — $12.00',\n },\n { name: 'order.total', description: 'Order total', sample: '$12.00' },\n {\n name: 'buyer.email',\n description: \"The buyer's email\",\n sample: 'buyer@example.com',\n },\n {\n name: 'order.ref',\n description: 'Order reference id',\n sample: 'cs_test_123',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'You made a sale', variant: 'heading' },\n {\n block: 'text',\n text: 'A new order came in on {{site.name}}.',\n variant: 'body',\n },\n { block: 'text', text: '{{order.summary}}', variant: 'body' },\n { block: 'text', text: 'Total: {{order.total}}', variant: 'body' },\n {\n block: 'text',\n text: 'Buyer: {{buyer.email}} · Order {{order.ref}}',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you manage {{host.businessName}}.',\n },\n {\n key: 'reservation-confirmed',\n name: 'Reservation confirmed',\n description: 'Confirms a paid reservation to the customer.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Reservation confirmed',\n mergeTokens: [\n {\n name: 'reservation.checkIn',\n description: 'Check-in date',\n sample: 'Mon, 01 Jun 2026',\n },\n {\n name: 'reservation.nights',\n description: 'Number of nights',\n sample: '2',\n },\n {\n name: 'reservation.paid',\n description:\n 'What the guest was charged today, lodging tax included; a ' +\n 'charge that carried lodging tax names it, as in “$254.40, ' +\n 'including $14.40 lodging tax”',\n sample: '$240.00',\n },\n {\n name: 'reservation.balance',\n description:\n 'What is still owed for the stay and where it is paid, as a ' +\n 'sentence; empty when the stay is paid in full',\n sample: 'Still to pay: $360.00, at the property. It has not been charged.',\n },\n {\n name: 'reservation.ref',\n description: 'Reservation reference id',\n sample: 'resv_123',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Reservation confirmed', variant: 'heading' },\n {\n block: 'text',\n text: 'Your stay at {{host.businessName}} is confirmed.',\n variant: 'body',\n },\n {\n block: 'text',\n text: 'Check-in: {{reservation.checkIn}}',\n variant: 'body',\n },\n { block: 'text', text: 'Nights: {{reservation.nights}}', variant: 'body' },\n {\n block: 'text',\n text: 'Paid today: {{reservation.paid}}',\n variant: 'body',\n },\n { block: 'text', text: '{{reservation.balance}}', variant: 'body' },\n {\n block: 'text',\n text: 'Reference: {{reservation.ref}}',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you made a reservation with {{host.businessName}}.',\n },\n {\n key: 'gift-card',\n name: 'Gift card delivery',\n description: 'Delivers a purchased gift card to its recipient.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Your gift card for {{host.businessName}}',\n mergeTokens: [\n {\n name: 'giftcard.code',\n description: 'The gift card code',\n sample: 'GC-ABC123DEF456',\n },\n {\n name: 'giftcard.value',\n description: 'The gift card value',\n sample: '$25.00',\n },\n {\n name: 'giftcard.note',\n description:\n 'The note written when the card was issued by hand; empty for a ' +\n 'card bought at checkout',\n sample: 'Happy birthday, Sam!',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Your gift card', variant: 'heading' },\n {\n block: 'text',\n text: 'You have a {{giftcard.value}} gift card for {{host.businessName}}.',\n variant: 'body',\n },\n { block: 'text', text: '{{giftcard.note}}', variant: 'body' },\n {\n block: 'text',\n text: 'Gift card code: {{giftcard.code}}',\n variant: 'body',\n },\n {\n block: 'text',\n text: 'Enter the code at checkout on {{host.url}} to use its balance.',\n variant: 'body',\n },\n {\n block: 'button',\n label: 'Visit {{host.businessName}}',\n href: '{{host.url}}',\n },\n ],\n footerReason:\n 'You’re receiving this because a gift card from {{host.businessName}} was sent to this address.',\n },\n {\n key: 'supplier-fulfillment',\n name: 'Supplier fulfillment',\n description: 'Tells a dropship supplier there is a new order to fulfill.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'fixed',\n },\n {\n key: 'back-in-stock',\n name: 'Back in stock',\n description: 'Tells a customer a product they watched is available again.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Back in stock: {{product.name}}',\n mergeTokens: [\n {\n name: 'product.name',\n description: 'The product that restocked',\n sample: 'House Blend',\n },\n {\n name: 'product.url',\n description: 'Link to the product page',\n sample: 'https://shop.example.com/products/house-blend',\n },\n ],\n defaultBody: [\n {\n block: 'text',\n text: '{{product.name}} is back in stock at {{host.businessName}}.',\n variant: 'body',\n },\n { block: 'button', label: 'View product', href: '{{product.url}}' },\n ],\n footerReason:\n 'You’re receiving this because you asked {{host.businessName}} to tell you when this was back.',\n },\n {\n key: 'abandoned-cart',\n name: 'Abandoned cart',\n description: 'Reminds a shopper of items left in their cart.',\n // The cron refuses to send this without the entitlement\n // (`process-abandoned.ts:92`), so the console has to say so (AGL-2081).\n requiresFeature: 'abandonedCart',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'You left something in your cart',\n mergeTokens: [\n {\n name: 'cart.url',\n description: 'Link back to the shopper cart',\n sample: 'https://shop.example.com/cart',\n },\n ],\n defaultBody: [\n {\n block: 'text',\n text:\n 'You left items in your cart at {{host.businessName}}. Pick up ' +\n 'where you left off.',\n variant: 'body',\n },\n { block: 'button', label: 'Return to cart', href: '{{cart.url}}' },\n {\n block: 'text',\n text: 'Nothing in your cart is held for you.',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because you started a checkout with {{host.businessName}}.',\n },\n {\n key: 'member-post',\n name: 'New member post',\n description: 'Notifies members when new members-only content is posted.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: '{{post.title}}',\n mergeTokens: [\n { name: 'post.title', description: 'The post’s title', sample: 'This month in the studio' },\n {\n name: 'post.body',\n description: 'The post, as its author wrote it',\n sample: 'Three new pieces are up for members first.',\n },\n ],\n defaultBody: [\n { block: 'text', text: '{{post.title}}', variant: 'heading' },\n { block: 'text', text: '{{post.body}}', variant: 'body' },\n ],\n footerReason: 'You’re receiving this because you’re a member of {{host.businessName}}.',\n },\n {\n key: 'member-password-reset',\n name: 'Member password reset',\n description:\n \"Resets a site member's password — a store-member account, separate \" +\n 'from a console login. Sent when the member asks, and when a site ' +\n 'administrator starts a reset for them.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Reset your {{site.name}} password',\n mergeTokens: [\n { name: 'site.name', description: 'The site’s name', sample: 'Northwind Coffee' },\n {\n name: 'reset.intro',\n description: 'Who started the reset',\n sample: 'Someone asked to reset the password for your Northwind Coffee account.',\n },\n {\n name: 'resetUrl',\n description: 'The one-time reset link',\n sample: 'https://shop.example.com/recover?token=…',\n },\n {\n name: 'reset.note',\n description: 'How long the link lasts, and what to do if unexpected',\n sample:\n 'The link works once and expires in 1 hour. If you did not ask for ' +\n 'this, you can safely ignore this email — your password is unchanged.',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Reset your password', variant: 'heading' },\n { block: 'text', text: '{{reset.intro}}', variant: 'body' },\n { block: 'button', label: 'Set a new password', href: '{{resetUrl}}' },\n { block: 'text', text: '{{reset.note}}', variant: 'caption' },\n ],\n footerReason:\n 'You’re receiving this because a password reset was started for your ' +\n '{{host.businessName}} account.',\n },\n {\n key: 'member-password-changed',\n name: 'Member password changed',\n description:\n 'Tells a site member that a site administrator set a new password on ' +\n 'their account.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Your {{site.name}} password was changed',\n mergeTokens: [\n { name: 'site.name', description: 'The site’s name', sample: 'Northwind Coffee' },\n {\n name: 'signInUrl',\n description: 'Where the member signs back in',\n sample: 'https://shop.example.com',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Your password was changed', variant: 'heading' },\n {\n block: 'text',\n text:\n 'An administrator of {{site.name}} set a new password on your ' +\n 'account and signed you out on every device. To sign back in, ' +\n 'get the new password from them, or choose \"Forgot password?\" on ' +\n 'the sign-in page to set your own.',\n variant: 'body',\n },\n { block: 'button', label: 'Sign in', href: '{{signInUrl}}' },\n {\n block: 'text',\n text: 'If you did not expect this, contact the site owner.',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because the password on your ' +\n '{{host.businessName}} account was changed.',\n },\n {\n key: 'newsletter-confirmation',\n name: 'Subscription confirmation',\n description:\n 'Asks someone who signed up for a site’s emails to confirm the address ' +\n 'before anything is sent to it.',\n pluginId: 'commerce',\n plugin: 'Commerce',\n control: 'besigner',\n defaultSubject: 'Confirm your subscription',\n mergeTokens: [\n {\n name: 'stream.name',\n description: 'What they signed up for',\n sample: 'Newsletter',\n },\n {\n name: 'confirmUrl',\n description: 'The confirmation link',\n sample: 'https://shop.example.com/subscribe/confirm?token=…',\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Confirm your subscription', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Please confirm that you want to get {{stream.name}} emails from ' +\n '{{host.businessName}} at this address.',\n variant: 'body',\n },\n { block: 'button', label: 'Confirm my subscription', href: '{{confirmUrl}}' },\n {\n block: 'text',\n text:\n 'The link works for three days. If you did not sign up, ignore this ' +\n 'message — nothing will be sent.',\n variant: 'caption',\n },\n ],\n footerReason:\n 'You’re receiving this because this address was entered on ' +\n '{{host.businessName}}’s signup form.',\n },\n ]\n}\n"],"names":["commerceTenantEmails","key","name","description","pluginId","plugin","control","defaultSubject","mergeTokens","sample","defaultBody","block","text","variant","footerReason","label","href","requiresFeature"],"mappings":"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"}
@@ -18,7 +18,9 @@ import type { PluginContactCaptureRequest } from '@aglyn/aglyn/plugin-manager/pl
18
18
  /**
19
19
  * A plugin that keeps people, standing in for the one that does (AGL-3080).
20
20
  *
21
- * Commerce reports the people it meets — a newsletter signup, a membership registration and an order — through the contact-capture
21
+ * Commerce reports the people it meets — a newsletter signup, a membership
22
+ * registration, an order, a paid draft, a stay, a renewal and a sale at the
23
+ * register — through the contact-capture
22
24
  * contract and imports no record system, so its specs stand a writer up the
23
25
  * way the loader would. One plugin may not import another, which is why this
24
26
  * is here rather than borrowed from the CRM.
@@ -31,4 +33,11 @@ import type { PluginContactCaptureRequest } from '@aglyn/aglyn/plugin-manager/pl
31
33
  *
32
34
  * Returns the list the writer fills, in the order the doors reported them.
33
35
  */
34
- export declare function standInRecordSystem(): PluginContactCaptureRequest[];
36
+ export declare function standInRecordSystem(options?: {
37
+ /**
38
+ * Start from no plugin services at all — the default. `false` keeps
39
+ * whatever a spec already stood in beside it, because the registry is
40
+ * one slot per service and a reset takes every slot.
41
+ */
42
+ reset?: boolean;
43
+ }): PluginContactCaptureRequest[];
@@ -18,7 +18,9 @@ import { resetPluginServicesForTests } from "@aglyn/aglyn/plugin-manager/plugin-
18
18
  /**
19
19
  * A plugin that keeps people, standing in for the one that does (AGL-3080).
20
20
  *
21
- * Commerce reports the people it meets — a newsletter signup, a membership registration and an order — through the contact-capture
21
+ * Commerce reports the people it meets — a newsletter signup, a membership
22
+ * registration, an order, a paid draft, a stay, a renewal and a sale at the
23
+ * register — through the contact-capture
22
24
  * contract and imports no record system, so its specs stand a writer up the
23
25
  * way the loader would. One plugin may not import another, which is why this
24
26
  * is here rather than borrowed from the CRM.
@@ -30,8 +32,8 @@ import { resetPluginServicesForTests } from "@aglyn/aglyn/plugin-manager/plugin-
30
32
  * specs and the forms-door specs in `apps/tenant`.
31
33
  *
32
34
  * Returns the list the writer fills, in the order the doors reported them.
33
- */ export function standInRecordSystem() {
34
- resetPluginServicesForTests();
35
+ */ export function standInRecordSystem(options = {}) {
36
+ if (options.reset !== false) resetPluginServicesForTests();
35
37
  const captured = [];
36
38
  registerPluginContactCaptureWriter({
37
39
  capture: async (request)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/testing/stand-in-record-system.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 { registerPluginContactCaptureWriter } from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport type { PluginContactCaptureRequest } from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport { resetPluginServicesForTests } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * A plugin that keeps people, standing in for the one that does (AGL-3080).\n *\n * Commerce reports the people it meets — a newsletter signup, a membership registration and an order — through the contact-capture\n * contract and imports no record system, so its specs stand a writer up the\n * way the loader would. One plugin may not import another, which is why this\n * is here rather than borrowed from the CRM.\n *\n * What these specs certify is that the door REPORTS the right capture: the\n * address, the door's own word for itself, the stage floor and the consent.\n * That the real record system turns that into the same contact it always\n * wrote is held where both plugins can be reached — the CRM's own adapter\n * specs and the forms-door specs in `apps/tenant`.\n *\n * Returns the list the writer fills, in the order the doors reported them.\n */\nexport function standInRecordSystem(): PluginContactCaptureRequest[] {\n resetPluginServicesForTests()\n const captured: PluginContactCaptureRequest[] = []\n registerPluginContactCaptureWriter(\n {\n capture: async (request) => {\n captured.push(request)\n return { ok: true, record: 'contact', contactId: `contact-${captured.length}`, created: true }\n },\n },\n { pluginId: 'record-system' },\n )\n return captured\n}\n"],"names":["registerPluginContactCaptureWriter","resetPluginServicesForTests","standInRecordSystem","captured","capture","request","push","ok","record","contactId","length","created","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,kCAAkC,QAAQ,qDAAoD;AAEvG,SAASC,2BAA2B,QAAQ,8CAA6C;AAEzF;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC;IACdD;IACA,MAAME,WAA0C,EAAE;IAClDH,mCACE;QACEI,SAAS,OAAOC;YACdF,SAASG,IAAI,CAACD;YACd,OAAO;gBAAEE,IAAI;gBAAMC,QAAQ;gBAAWC,WAAW,CAAC,QAAQ,EAAEN,SAASO,MAAM,EAAE;gBAAEC,SAAS;YAAK;QAC/F;IACF,GACA;QAAEC,UAAU;IAAgB;IAE9B,OAAOT;AACT"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/testing/stand-in-record-system.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 { registerPluginContactCaptureWriter } from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport type { PluginContactCaptureRequest } from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport { resetPluginServicesForTests } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * A plugin that keeps people, standing in for the one that does (AGL-3080).\n *\n * Commerce reports the people it meets — a newsletter signup, a membership\n * registration, an order, a paid draft, a stay, a renewal and a sale at the\n * register — through the contact-capture\n * contract and imports no record system, so its specs stand a writer up the\n * way the loader would. One plugin may not import another, which is why this\n * is here rather than borrowed from the CRM.\n *\n * What these specs certify is that the door REPORTS the right capture: the\n * address, the door's own word for itself, the stage floor and the consent.\n * That the real record system turns that into the same contact it always\n * wrote is held where both plugins can be reached — the CRM's own adapter\n * specs and the forms-door specs in `apps/tenant`.\n *\n * Returns the list the writer fills, in the order the doors reported them.\n */\nexport function standInRecordSystem(\n options: {\n /**\n * Start from no plugin services at all — the default. `false` keeps\n * whatever a spec already stood in beside it, because the registry is\n * one slot per service and a reset takes every slot.\n */\n reset?: boolean\n } = {},\n): PluginContactCaptureRequest[] {\n if (options.reset !== false) resetPluginServicesForTests()\n const captured: PluginContactCaptureRequest[] = []\n registerPluginContactCaptureWriter(\n {\n capture: async (request) => {\n captured.push(request)\n return { ok: true, record: 'contact', contactId: `contact-${captured.length}`, created: true }\n },\n },\n { pluginId: 'record-system' },\n )\n return captured\n}\n"],"names":["registerPluginContactCaptureWriter","resetPluginServicesForTests","standInRecordSystem","options","reset","captured","capture","request","push","ok","record","contactId","length","created","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,kCAAkC,QAAQ,qDAAoD;AAEvG,SAASC,2BAA2B,QAAQ,8CAA6C;AAEzF;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASC,oBACdC,UAOI,CAAC,CAAC;IAEN,IAAIA,QAAQC,KAAK,KAAK,OAAOH;IAC7B,MAAMI,WAA0C,EAAE;IAClDL,mCACE;QACEM,SAAS,OAAOC;YACdF,SAASG,IAAI,CAACD;YACd,OAAO;gBAAEE,IAAI;gBAAMC,QAAQ;gBAAWC,WAAW,CAAC,QAAQ,EAAEN,SAASO,MAAM,EAAE;gBAAEC,SAAS;YAAK;QAC/F;IACF,GACA;QAAEC,UAAU;IAAgB;IAE9B,OAAOT;AACT"}