platform-types 1.10.835 → 1.10.837

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CUSTOMER_DISCOUNT_MAX = exports.CUSTOMER_ACQUISITION_CHANNELS = exports.CUSTOMER_LIFECYCLE_STAGES = exports.CUSTOMER_KINDS = void 0;
3
+ exports.isWhatsappNoticesOptedIn = exports.CUSTOMER_DISCOUNT_MAX = exports.CUSTOMER_ACQUISITION_CHANNELS = exports.CUSTOMER_LIFECYCLE_STAGES = exports.CUSTOMER_KINDS = void 0;
4
4
  exports.CUSTOMER_KINDS = ['person', 'company'];
5
5
  exports.CUSTOMER_LIFECYCLE_STAGES = ['lead', 'active', 'inactive'];
6
6
  exports.CUSTOMER_ACQUISITION_CHANNELS = ['storefront', 'pos', 'marketplace', 'import', 'manual'];
@@ -11,3 +11,6 @@ exports.CUSTOMER_ACQUISITION_CHANNELS = ['storefront', 'pos', 'marketplace', 'im
11
11
  * `discount` unchanged (or omits it) still saves.
12
12
  */
13
13
  exports.CUSTOMER_DISCOUNT_MAX = 50;
14
+ /** Whether `notices` records a standing transactional WhatsApp opt-in: `optIn` present and `optOut` absent. */
15
+ const isWhatsappNoticesOptedIn = (notices) => notices?.optIn !== undefined && notices.optOut === undefined;
16
+ exports.isWhatsappNoticesOptedIn = isWhatsappNoticesOptedIn;
@@ -1,13 +1,16 @@
1
1
  "use strict";
2
2
  /**
3
- * Guest order status link — the read-only page a buyer with no account opens
4
- * from the order email (`/pedido/{token}`), and the route that mails a fresh
5
- * link to the address stored on the order.
3
+ * Guest order status link — the page a buyer with no account opens from the
4
+ * order email (`/pedido/{token}`), the one consent it can give there, and the
5
+ * route that mails a fresh link to the address stored on the order.
6
6
  *
7
- * The token (`gs1.…`) is a bearer credential over exactly ONE order, read-only:
8
- * it authorises no cart write, payment, cancellation, withdrawal or export. It
9
- * carries no expiry; the read is refused once the order was delivered (or
10
- * cancelled) more than `guestStatusLinkDaysAfterDelivery` days ago — unless a
7
+ * The token (`gs1.…`) is a bearer credential over exactly ONE order. It
8
+ * authorises the read and exactly ONE write: the transactional WhatsApp consent
9
+ * grant for the order's own customer in that store
10
+ * (`POST /orders/status/whatsapp-opt-in`, {@link GuestOrderWhatsappOptInRequest}).
11
+ * Nothing else — no cart write, payment, cancellation, withdrawal or export.
12
+ * Both routes authorise it identically. It carries no expiry; it is refused
13
+ * once the order was delivered (or cancelled) more than `guestStatusLinkDaysAfterDelivery` days ago — unless a
11
14
  * purchase withdrawal on it is not final or a refund on it is not closed — and
12
15
  * every earlier link dies when a recovery or an operator revokes it.
13
16
  *
@@ -16,7 +19,7 @@
16
19
  * code mailed to the order's address, so the token still authorises none.
17
20
  */
18
21
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.GUEST_ORDER_STATUS_LINK_TEMPLATE = exports.GUEST_ORDER_STATUS_ERROR_CODES = void 0;
22
+ exports.GUEST_ORDER_STATUS_LINK_TEMPLATE = exports.GUEST_ORDER_WHATSAPP_OPT_IN_ERROR_CODES = exports.GUEST_ORDER_STATUS_ERROR_CODES = void 0;
20
23
  /**
21
24
  * The refusals the guest status routes answer themselves.
22
25
  *
@@ -28,5 +31,15 @@ exports.GUEST_ORDER_STATUS_LINK_TEMPLATE = exports.GUEST_ORDER_STATUS_ERROR_CODE
28
31
  * `POST /orders/status/recover`.
29
32
  */
30
33
  exports.GUEST_ORDER_STATUS_ERROR_CODES = ['GUEST_STATUS_TOKEN_INVALID'];
34
+ /**
35
+ * The refusals `POST /orders/status/whatsapp-opt-in` answers itself, past the
36
+ * token (which refuses `GUEST_STATUS_TOKEN_INVALID` exactly as the read does).
37
+ *
38
+ * - WHATSAPP_OPT_IN_UNAVAILABLE (409): the offer does not hold for this order —
39
+ * the store's messaging is unavailable, the order is cancelled, delivered or
40
+ * disabled, carries no phone, or its customer row is no longer live. Nothing
41
+ * is written. One code for every reason.
42
+ */
43
+ exports.GUEST_ORDER_WHATSAPP_OPT_IN_ERROR_CODES = ['WHATSAPP_OPT_IN_UNAVAILABLE'];
31
44
  /** The transactional email template that mails a guest their (new) status link. */
32
45
  exports.GUEST_ORDER_STATUS_LINK_TEMPLATE = 'guest-order-status-link';
package/dist/cjs/store.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CHANGE_OF_MIND_WINDOW_SOURCE = exports.CHANGE_OF_MIND_RETURN_DAYS_MAX = exports.CHANGE_OF_MIND_REFUND_AS = exports.CHANGE_OF_MIND_RETURN_SHIPPING_PAYERS = exports.STORE_PATCH_ERROR_CODES = exports.STORE_MODULE_ERROR_CODES = exports.isStoreModuleOn = exports.isStoreModuleKey = exports.STORE_MODULE_KEYS = exports.STOREFRONT_METHOD_KINDS = exports.STORE_ONBOARDING_REVIEW_AREAS = exports.CUSTOM_DOMAIN_ROW_LIMIT = exports.CUSTOM_DOMAIN_SLOT_LIMIT = void 0;
3
+ exports.resolveStoreMessaging = exports.STORE_MESSAGING_SIGNATURE_MAX = exports.STORE_MESSAGING_DEFAULTS = exports.STORE_MESSAGE_NOTICES = exports.CHANGE_OF_MIND_WINDOW_SOURCE = exports.CHANGE_OF_MIND_RETURN_DAYS_MAX = exports.CHANGE_OF_MIND_REFUND_AS = exports.CHANGE_OF_MIND_RETURN_SHIPPING_PAYERS = exports.STORE_PATCH_ERROR_CODES = exports.STORE_MODULE_ERROR_CODES = exports.isStoreModuleOn = exports.isStoreModuleKey = exports.STORE_MODULE_KEYS = exports.STOREFRONT_METHOD_KINDS = exports.STORE_ONBOARDING_REVIEW_AREAS = exports.CUSTOM_DOMAIN_ROW_LIMIT = exports.CUSTOM_DOMAIN_SLOT_LIMIT = void 0;
4
4
  /**
5
5
  * How many custom-domain rows a store may hold that count: one active domain
6
6
  * and one spare, so a replacement can be verified and provisioned before the
@@ -112,13 +112,14 @@ exports.STORE_MODULE_ERROR_CODES = [
112
112
  * Role refusals, `403` — a plain USER writing a privileged section. Each is
113
113
  * judged before any other check on that section:
114
114
  * - `STORE_CONFIG_FORBIDDEN` — any privileged `config` leaf (`modules`,
115
- * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, …).
115
+ * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, `autoPrint`, …).
116
116
  * - `STORE_ECOMMERCE_FORBIDDEN` — any `ecommerce` block.
117
117
  * - `STORE_FISCAL_FIELD_FORBIDDEN` — a fiscal field of the store.
118
118
  * - `STORE_JURISDICTION_FORBIDDEN` — `jurisdiction`.
119
119
  * - `AFIP_RELACION_FLAG_FORBIDDEN` — `afip.wscdcEnabled` / `afip.fceEnabled`.
120
120
  * - `FX_TABLE_FORBIDDEN` — the `currencies` exchange-rate table.
121
121
  * - `FX_AUTO_UPDATE_FORBIDDEN` — `fxAutoUpdate`.
122
+ * - `STORE_MESSAGING_FORBIDDEN` — the `messaging` customer-messaging settings.
122
123
  *
123
124
  * Request refusals, `400`:
124
125
  * - `FIELD_NOT_WRITABLE_HERE` — a field written through `POST /store`, not
@@ -197,6 +198,7 @@ exports.STORE_PATCH_ERROR_CODES = [
197
198
  'NET_PLUS_IVA_REQUIRES_RI',
198
199
  'DATA_FISCAL_URL_INVALID',
199
200
  'STORE_PUBLIC_HOST_MISSING',
201
+ 'STORE_MESSAGING_FORBIDDEN',
200
202
  'FORBIDDEN',
201
203
  'STORE_NOT_FOUND',
202
204
  ];
@@ -208,3 +210,63 @@ exports.CHANGE_OF_MIND_REFUND_AS = ['refund', 'store-credit'];
208
210
  exports.CHANGE_OF_MIND_RETURN_DAYS_MAX = 365;
209
211
  /** The source of a change-of-mind deadline: the store's own setting, not a statutory row. */
210
212
  exports.CHANGE_OF_MIND_WINDOW_SOURCE = 'store:changeOfMindReturns';
213
+ /**
214
+ * Every `StoreMessageNotice`, in the order the settings screen lists them —
215
+ * the keys `Store.integrations.messaging` switches. `UncoveredStoreMessageNotices`
216
+ * fails `typecheck` naming a notice the union gains and this tuple lacks.
217
+ */
218
+ exports.STORE_MESSAGE_NOTICES = [
219
+ 'orderReady',
220
+ 'orderShipped',
221
+ 'serviceOrderReceived',
222
+ 'serviceOrderQuoted',
223
+ 'serviceOrderApproved',
224
+ 'serviceOrderReady',
225
+ 'serviceOrderCancelled',
226
+ 'serviceOrderReturnedUnrepaired',
227
+ 'serviceOrderAbandoned',
228
+ 'serviceOrderOnHold',
229
+ 'serviceOrderResumed',
230
+ 'appointmentReminder',
231
+ 'paymentReminder',
232
+ ];
233
+ /**
234
+ * What an ABSENT `messaging` key reads: status notices and appointment
235
+ * reminders on, payment reminders off, the SMS fallback on.
236
+ */
237
+ exports.STORE_MESSAGING_DEFAULTS = {
238
+ orderReady: true,
239
+ orderShipped: true,
240
+ serviceOrderReceived: true,
241
+ serviceOrderQuoted: true,
242
+ serviceOrderApproved: true,
243
+ serviceOrderReady: true,
244
+ serviceOrderCancelled: true,
245
+ serviceOrderReturnedUnrepaired: true,
246
+ serviceOrderAbandoned: true,
247
+ serviceOrderOnHold: true,
248
+ serviceOrderResumed: true,
249
+ appointmentReminder: true,
250
+ paymentReminder: false,
251
+ smsFallback: true,
252
+ };
253
+ /** The firma cap — the same one `sms.signature` has. */
254
+ exports.STORE_MESSAGING_SIGNATURE_MAX = 80;
255
+ /**
256
+ * Every switch of a store's messaging settings, decided: per key, the stored
257
+ * boolean, else its `STORE_MESSAGING_DEFAULTS` value (a stored non-boolean
258
+ * reads as absent). `signature` falls back to `sms.signature` only when
259
+ * `messaging.signature` is absent; a stored `''` is kept and means no firma.
260
+ */
261
+ const resolveStoreMessaging = (integrations) => {
262
+ const leaf = integrations?.messaging;
263
+ const resolved = { ...exports.STORE_MESSAGING_DEFAULTS };
264
+ for (const key of [...exports.STORE_MESSAGE_NOTICES, 'smsFallback']) {
265
+ const stored = leaf?.[key];
266
+ if (typeof stored === 'boolean')
267
+ resolved[key] = stored;
268
+ }
269
+ const signature = typeof leaf?.signature === 'string' ? leaf.signature : integrations?.sms?.signature;
270
+ return typeof signature === 'string' ? { ...resolved, signature } : resolved;
271
+ };
272
+ exports.resolveStoreMessaging = resolveStoreMessaging;
@@ -132,4 +132,5 @@ exports.FEATURE_KEYS = [
132
132
  'webhooks',
133
133
  'maxOrdersMonth',
134
134
  'maxInvoicesMonth',
135
+ 'monthlyMessageAllowance',
135
136
  ];
@@ -8,6 +8,8 @@ export declare const CUSTOMER_ACQUISITION_CHANNELS: readonly ["storefront", "pos
8
8
  * `discount` unchanged (or omits it) still saves.
9
9
  */
10
10
  export declare const CUSTOMER_DISCOUNT_MAX = 50;
11
+ /** Whether `notices` records a standing transactional WhatsApp opt-in: `optIn` present and `optOut` absent. */
12
+ export declare const isWhatsappNoticesOptedIn: (notices: CustomerWhatsappNotices | undefined) => boolean;
11
13
  declare global {
12
14
  /** Exact identity matches within the token's store; no stored identity fields are returned. */
13
15
  interface CustomerDuplicateCandidate {
@@ -95,8 +97,62 @@ declare global {
95
97
  /** Per-customer, not per-channel — one unsubscribe link covers every channel. */
96
98
  unsubscribeToken?: string;
97
99
  }
98
- /** Write shape for customer create/update — carries the transient photo controls. */
99
- type CustomerUpsertInput = Partial<Customer> & PhotoUploadControls;
100
+ /**
101
+ * Where a transactional WhatsApp grant or withdrawal was given:
102
+ * `back-office` (staff, `POST /customers`), `storefront-post-purchase` (the
103
+ * one-tap offer after an order is placed — the signed-in banner through web
104
+ * `POST /user` with `whatsappOptInOrigin: 'post-purchase'`, or a guest's
105
+ * `POST /orders/status/whatsapp-opt-in`) and `storefront-profile` (the
106
+ * shopper's own profile toggle, web `POST /user`).
107
+ */
108
+ type WhatsappNoticeSource = 'back-office' | 'storefront-post-purchase' | 'storefront-profile';
109
+ /**
110
+ * Provenance of one transactional WhatsApp grant or withdrawal.
111
+ * Server-written only. Carries NO phone number and NO IP, unlike
112
+ * `ChannelConsentStamp` (deliberately not extended), and not who acted.
113
+ */
114
+ interface WhatsappNoticeStamp {
115
+ /** ms epoch. */
116
+ ts: number;
117
+ source: WhatsappNoticeSource;
118
+ }
119
+ /**
120
+ * `Customer.whatsappNotices`: the customer's consent to the store's
121
+ * transactional WhatsApp notices (order and service-order status,
122
+ * reminders). Per customer per store, since the CUSTOMER row is
123
+ * store-scoped. Separate from `CustomerMarketing`, and never marketing
124
+ * consent.
125
+ *
126
+ * Opted in ⇔ `optIn` present AND `optOut` absent; read it through
127
+ * `isWhatsappNoticesOptedIn`. A grant SETs `optIn` and REMOVEs `optOut`; a
128
+ * withdrawal SETs `optOut` and keeps `optIn`. Resending the current state
129
+ * changes nothing and does not re-stamp; a withdrawal from a customer who
130
+ * never opted in is recorded. Not a history: it holds the latest grant and
131
+ * the latest withdrawal only. ⚠️ Absent is NOT consent.
132
+ */
133
+ interface CustomerWhatsappNotices {
134
+ optIn?: WhatsappNoticeStamp;
135
+ optOut?: WhatsappNoticeStamp;
136
+ }
137
+ /** Write shape: `whatsappNotices` is server-owned, and `whatsappOptIn` is the directive that moves it. */
138
+ type CustomerUpsertInput = Partial<Omit<Customer, 'whatsappNotices'>> & PhotoUploadControls & {
139
+ /**
140
+ * `POST /customers` (source `back-office`) and web `POST /user` (source
141
+ * `storefront-profile`): `true` grants, `false` withdraws, absent changes
142
+ * nothing. Never stored. Staff MAY grant, because this is not marketing
143
+ * consent. A storefront grant acts only while `Store.messagingAvailable`
144
+ * holds and is otherwise dropped; a withdrawal always acts.
145
+ */
146
+ whatsappOptIn?: boolean;
147
+ /**
148
+ * Web `POST /user` only: `'post-purchase'` names the grant as the one-tap
149
+ * offer after an order (`whatsappOptInOffer` on the order read), so the
150
+ * stamp's source is `storefront-post-purchase` instead of
151
+ * `storefront-profile`. A directive, never stored, and ignored unless
152
+ * `whatsappOptIn` is `true`.
153
+ */
154
+ whatsappOptInOrigin?: 'post-purchase';
155
+ };
100
156
  /**
101
157
  * The query string of the customers LIST read — `GET /customers` with no
102
158
  * `customerId`, `list` or `email`. Every present filter AND-composes: a row is
@@ -568,6 +624,8 @@ declare global {
568
624
  */
569
625
  mergeBalanceCarried?: boolean;
570
626
  marketing?: CustomerMarketing;
627
+ /** Transactional WhatsApp consent for this store — see `CustomerWhatsappNotices`. Server-written. */
628
+ whatsappNotices?: CustomerWhatsappNotices;
571
629
  /** Optional notification mail only; transactional mail cannot be disabled. */
572
630
  notificationPreferences?: CustomerNotificationPreferences;
573
631
  minBuy?: number;
package/dist/customer.js CHANGED
@@ -8,3 +8,5 @@ export const CUSTOMER_ACQUISITION_CHANNELS = ['storefront', 'pos', 'marketplace'
8
8
  * `discount` unchanged (or omits it) still saves.
9
9
  */
10
10
  export const CUSTOMER_DISCOUNT_MAX = 50;
11
+ /** Whether `notices` records a standing transactional WhatsApp opt-in: `optIn` present and `optOut` absent. */
12
+ export const isWhatsappNoticesOptedIn = (notices) => notices?.optIn !== undefined && notices.optOut === undefined;
@@ -1,12 +1,15 @@
1
1
  /**
2
- * Guest order status link — the read-only page a buyer with no account opens
3
- * from the order email (`/pedido/{token}`), and the route that mails a fresh
4
- * link to the address stored on the order.
2
+ * Guest order status link — the page a buyer with no account opens from the
3
+ * order email (`/pedido/{token}`), the one consent it can give there, and the
4
+ * route that mails a fresh link to the address stored on the order.
5
5
  *
6
- * The token (`gs1.…`) is a bearer credential over exactly ONE order, read-only:
7
- * it authorises no cart write, payment, cancellation, withdrawal or export. It
8
- * carries no expiry; the read is refused once the order was delivered (or
9
- * cancelled) more than `guestStatusLinkDaysAfterDelivery` days ago — unless a
6
+ * The token (`gs1.…`) is a bearer credential over exactly ONE order. It
7
+ * authorises the read and exactly ONE write: the transactional WhatsApp consent
8
+ * grant for the order's own customer in that store
9
+ * (`POST /orders/status/whatsapp-opt-in`, {@link GuestOrderWhatsappOptInRequest}).
10
+ * Nothing else — no cart write, payment, cancellation, withdrawal or export.
11
+ * Both routes authorise it identically. It carries no expiry; it is refused
12
+ * once the order was delivered (or cancelled) more than `guestStatusLinkDaysAfterDelivery` days ago — unless a
10
13
  * purchase withdrawal on it is not final or a refund on it is not closed — and
11
14
  * every earlier link dies when a recovery or an operator revokes it.
12
15
  *
@@ -26,6 +29,17 @@
26
29
  */
27
30
  export declare const GUEST_ORDER_STATUS_ERROR_CODES: readonly ["GUEST_STATUS_TOKEN_INVALID"];
28
31
  export type GuestOrderStatusErrorCode = (typeof GUEST_ORDER_STATUS_ERROR_CODES)[number];
32
+ /**
33
+ * The refusals `POST /orders/status/whatsapp-opt-in` answers itself, past the
34
+ * token (which refuses `GUEST_STATUS_TOKEN_INVALID` exactly as the read does).
35
+ *
36
+ * - WHATSAPP_OPT_IN_UNAVAILABLE (409): the offer does not hold for this order —
37
+ * the store's messaging is unavailable, the order is cancelled, delivered or
38
+ * disabled, carries no phone, or its customer row is no longer live. Nothing
39
+ * is written. One code for every reason.
40
+ */
41
+ export declare const GUEST_ORDER_WHATSAPP_OPT_IN_ERROR_CODES: readonly ["WHATSAPP_OPT_IN_UNAVAILABLE"];
42
+ export type GuestOrderWhatsappOptInErrorCode = (typeof GUEST_ORDER_WHATSAPP_OPT_IN_ERROR_CODES)[number];
29
43
  /** The transactional email template that mails a guest their (new) status link. */
30
44
  export declare const GUEST_ORDER_STATUS_LINK_TEMPLATE: "guest-order-status-link";
31
45
  declare global {
@@ -115,6 +129,17 @@ declare global {
115
129
  returnRequests?: OrderReturnRequestView[];
116
130
  /** Whether a return request can be opened now. Absent only when the return requests could not be read whole. */
117
131
  returnRequestEligibility?: OrderReturnRequestEligibility;
132
+ /**
133
+ * Present, and `true`, only when the post-purchase «Avisarme por WhatsApp
134
+ * sobre este pedido» may be offered: the store's messaging is available
135
+ * (`Store.messagingAvailable`), the order is not cancelled, delivered or
136
+ * disabled, it carries a phone (its customer's or the guest contact's),
137
+ * and its customer row is live and NOT opted in
138
+ * (`Customer.whatsappNotices`). Accept it with
139
+ * `POST /orders/status/whatsapp-opt-in`. Carries no phone. Absent too when
140
+ * the offer could not be computed — the read never fails for it.
141
+ */
142
+ whatsappOptInOffer?: true;
118
143
  contact: {
119
144
  /** The stored address, masked (e.g. `j***@d***.com`). */
120
145
  emailMasked: string;
@@ -131,4 +156,21 @@ declare global {
131
156
  orderId: string;
132
157
  email: string;
133
158
  }
159
+ /**
160
+ * `POST /orders/status/whatsapp-opt-in` body (strict): the guest status token
161
+ * of the order whose post-purchase WhatsApp offer (`whatsappOptInOffer`) is
162
+ * accepted. The token is authorised exactly as `GET /orders/status` does it
163
+ * (one `401 GUEST_STATUS_TOKEN_INVALID` for every refusal; a malformed body
164
+ * too). Then the grant: `200` {@link GuestOrderWhatsappOptInResult} when the
165
+ * customer is now opted in — freshly stamped `{ ts, source:
166
+ * 'storefront-post-purchase' }`, or already opted in (nothing written, never
167
+ * re-stamped) — else `409 WHATSAPP_OPT_IN_UNAVAILABLE`, nothing written.
168
+ */
169
+ interface GuestOrderWhatsappOptInRequest {
170
+ token: string;
171
+ }
172
+ /** `data` of a `200` `POST /orders/status/whatsapp-opt-in`. */
173
+ interface GuestOrderWhatsappOptInResult {
174
+ whatsappNoticesOptedIn: true;
175
+ }
134
176
  }
@@ -1,12 +1,15 @@
1
1
  /**
2
- * Guest order status link — the read-only page a buyer with no account opens
3
- * from the order email (`/pedido/{token}`), and the route that mails a fresh
4
- * link to the address stored on the order.
2
+ * Guest order status link — the page a buyer with no account opens from the
3
+ * order email (`/pedido/{token}`), the one consent it can give there, and the
4
+ * route that mails a fresh link to the address stored on the order.
5
5
  *
6
- * The token (`gs1.…`) is a bearer credential over exactly ONE order, read-only:
7
- * it authorises no cart write, payment, cancellation, withdrawal or export. It
8
- * carries no expiry; the read is refused once the order was delivered (or
9
- * cancelled) more than `guestStatusLinkDaysAfterDelivery` days ago — unless a
6
+ * The token (`gs1.…`) is a bearer credential over exactly ONE order. It
7
+ * authorises the read and exactly ONE write: the transactional WhatsApp consent
8
+ * grant for the order's own customer in that store
9
+ * (`POST /orders/status/whatsapp-opt-in`, {@link GuestOrderWhatsappOptInRequest}).
10
+ * Nothing else — no cart write, payment, cancellation, withdrawal or export.
11
+ * Both routes authorise it identically. It carries no expiry; it is refused
12
+ * once the order was delivered (or cancelled) more than `guestStatusLinkDaysAfterDelivery` days ago — unless a
10
13
  * purchase withdrawal on it is not final or a refund on it is not closed — and
11
14
  * every earlier link dies when a recovery or an operator revokes it.
12
15
  *
@@ -25,5 +28,15 @@
25
28
  * `POST /orders/status/recover`.
26
29
  */
27
30
  export const GUEST_ORDER_STATUS_ERROR_CODES = ['GUEST_STATUS_TOKEN_INVALID'];
31
+ /**
32
+ * The refusals `POST /orders/status/whatsapp-opt-in` answers itself, past the
33
+ * token (which refuses `GUEST_STATUS_TOKEN_INVALID` exactly as the read does).
34
+ *
35
+ * - WHATSAPP_OPT_IN_UNAVAILABLE (409): the offer does not hold for this order —
36
+ * the store's messaging is unavailable, the order is cancelled, delivered or
37
+ * disabled, carries no phone, or its customer row is no longer live. Nothing
38
+ * is written. One code for every reason.
39
+ */
40
+ export const GUEST_ORDER_WHATSAPP_OPT_IN_ERROR_CODES = ['WHATSAPP_OPT_IN_UNAVAILABLE'];
28
41
  /** The transactional email template that mails a guest their (new) status link. */
29
42
  export const GUEST_ORDER_STATUS_LINK_TEMPLATE = 'guest-order-status-link';
@@ -145,6 +145,18 @@ declare global {
145
145
  returnRequests?: OrderReturnRequestView[];
146
146
  /** Whether a return request can be opened now. Absent only when the return requests could not be read whole. */
147
147
  returnRequestEligibility?: OrderReturnRequestEligibility;
148
+ /**
149
+ * Point read (`?orderId=`) only, never the list. Present, and `true`, only
150
+ * when the post-purchase «Avisarme por WhatsApp sobre este pedido» may be
151
+ * offered: the store's messaging is available (`Store.messagingAvailable`),
152
+ * the order is not cancelled, delivered or disabled, it carries a phone
153
+ * (`customer.phone` or the guest contact's), and the order's customer row
154
+ * is live and NOT opted in (`Customer.whatsappNotices`). Accept it with web
155
+ * `POST /user` `{ whatsappOptIn: true, whatsappOptInOrigin: 'post-purchase' }`.
156
+ * Carries no phone. Absent too when the offer could not be computed — the
157
+ * order read never fails for it.
158
+ */
159
+ whatsappOptInOffer?: true;
148
160
  };
149
161
  /**
150
162
  * `data` of an `orderPaymentAttempt` socket frame, sent to the order's own
package/dist/print.d.ts CHANGED
@@ -560,8 +560,26 @@ declare global {
560
560
  * sorted and joined by `+`; an agent `ACK_FAILED` can classify to `PRINTER_PAUSED` /
561
561
  * `NO_PRINTER_ASSIGNED` / `PRINTER_NOT_FOUND`. Unrecognised agent
562
562
  * text stays in `detail` only, so absence of a code does NOT mean success.
563
+ *
564
+ * An automatic job (`Store.config.autoPrint`) adds three BE-side codes on
565
+ * `error`: `PRINT_QUEUE_EXPIRED` — it was held for want of a connected
566
+ * agent and its `heldUntil` passed — `AUTO_PRINT_SOURCE_GONE` — the
567
+ * order or invoice it was printing no longer exists when it was replayed —
568
+ * and `AUTO_PRINT_NO_LONGER_ELIGIBLE` — at replay it no longer passed the
569
+ * checks it was minted under (the store's switch for its event is off, the
570
+ * order was cancelled or its ready / delivered mark cleared, the factura is
571
+ * a credit note), so it was never sent.
572
+ * A HELD job is `queued` with `NO_PRINTER_CONNECTED` and `heldUntil`.
563
573
  */
564
574
  errorCode?: string;
575
+ /**
576
+ * ms epoch until which an automatic job with no connected agent is HELD —
577
+ * present only while the summary is `queued` waiting for an agent. The
578
+ * job is replayed when an agent of the store registers its printers, and
579
+ * ends `error` / `PRINT_QUEUE_EXPIRED` once this passes. Every later
580
+ * transition removes it. A manual print is never held.
581
+ */
582
+ heldUntil?: number;
565
583
  }
566
584
  /**
567
585
  * `data` payload of the server → agent `printers_active` WSS frame,
package/dist/store.d.ts CHANGED
@@ -87,6 +87,28 @@ declare global {
87
87
  /** MercadoLibre orders. */
88
88
  mercadolibre?: string;
89
89
  }
90
+ /**
91
+ * The automatic-print switches — see `Store.config.autoPrint`. Every switch is
92
+ * OFF unless it is `true`: an absent leaf, an absent key and `false` all mean
93
+ * "do not print this automatically".
94
+ */
95
+ interface StoreAutoPrintConfig {
96
+ /** A factura authorised by ARCA (CAE, CAEA stamp, or the pending drain settling it). Credit notes never. */
97
+ invoiceAuthorized?: boolean;
98
+ /** A new order, per sales channel. A `service` channel order has no switch. */
99
+ orderCreated?: {
100
+ /** `POST /orders` on the App, including a presupuesto conversion. */
101
+ counter?: boolean;
102
+ /** The public storefront checkout. */
103
+ storefront?: boolean;
104
+ /** An order imported from MercadoLibre. */
105
+ meli?: boolean;
106
+ };
107
+ /** An order marked ready (its `readyAt` moving from unset to set). */
108
+ orderReady?: boolean;
109
+ /** The shipping tag, when an order with a delivery address is marked delivered. */
110
+ deliveryTag?: boolean;
111
+ }
90
112
  /**
91
113
  * One store-wide module a store can switch off for every user — the
92
114
  * catalogue is the shared `domain/STORE_MODULES.md`. Enumerable at runtime as
@@ -276,7 +298,8 @@ declare global {
276
298
  * Platform switch for store-paid customer messaging (SMS today). `false`
277
299
  * or absent: no store-paid message is sent, and the app hides every store
278
300
  * messaging surface. App-only (`forwardToTenants: 'app'`), so absent from
279
- * the storefront's unauthenticated response.
301
+ * the storefront's unauthenticated response: the storefront reads
302
+ * `Store.messagingAvailable` instead, which folds this switch in.
280
303
  */
281
304
  tenantMessagingEnabled?: boolean;
282
305
  }
@@ -612,6 +635,16 @@ declare global {
612
635
  * first such removal. No request body may set it.
613
636
  */
614
637
  adminRemovalGeneration?: number;
638
+ /**
639
+ * Web `GET /store` only, always a boolean on a 200: `true` when the
640
+ * `tenantMessagingEnabled` platform global is on and at least one
641
+ * `StoreMessageNotice` resolves on (`resolveStoreMessaging`). Computed when
642
+ * the route is read, never stored or writable. Not on `stores` frames or the
643
+ * preview frame, where absent reads `false`. Checkout hides «Avisarme por
644
+ * WhatsApp» when it is `false`, and a storefront opt-in sent while it is
645
+ * `false` is dropped.
646
+ */
647
+ messagingAvailable?: boolean;
615
648
  storeId: string;
616
649
  createdAt: number;
617
650
  /** Unix ms — BE-stamped on every `POST`/`PATCH /store` write. Always present on written rows. */
@@ -1135,6 +1168,24 @@ declare global {
1135
1168
  * whatever this holds.
1136
1169
  */
1137
1170
  channelWarehouses?: StoreChannelWarehouses;
1171
+ /**
1172
+ * Automatic printing — which events print a document with no operator
1173
+ * pressing «Imprimir». Written through `PATCH /store` `config` at the
1174
+ * PRIVILEGED tier (ADMIN/SUPERVISOR/MANAGER; a plain USER is refused
1175
+ * `STORE_CONFIG_FORBIDDEN`). No plan gate.
1176
+ *
1177
+ * ⚠️ The merge REPLACES the whole leaf, like every other `config` leaf: a
1178
+ * body carrying `autoPrint: { orderReady: true }` switches every other
1179
+ * event off. Send the full leaf.
1180
+ *
1181
+ * An automatic job fans out exactly as «Imprimir» does (the store's print
1182
+ * rules decide). When no print agent is connected it is HELD as `queued`
1183
+ * with `heldUntil` on its `PrintJobSummary`, replayed when an agent of the
1184
+ * store registers, and ended `error` / `PRINT_QUEUE_EXPIRED` at the end
1185
+ * of the store's Buenos Aires day or 12 h after the event, whichever is
1186
+ * first. Absent means nothing prints automatically.
1187
+ */
1188
+ autoPrint?: StoreAutoPrintConfig;
1138
1189
  /**
1139
1190
  * Store-wide module switches — see `StoreModules`. Written through
1140
1191
  * `PATCH /store` `config` at the PRIVILEGED tier (ADMIN/SUPERVISOR/MANAGER;
@@ -1377,7 +1428,15 @@ declare global {
1377
1428
  interface StoreIntegrations {
1378
1429
  afip?: Afip;
1379
1430
  mercadopago?: Mercadopago;
1431
+ /**
1432
+ * Per-store SMS entitlement and firma. Kept READABLE for migration: the
1433
+ * app writes `messaging` instead, and `PATCH /store` still accepts `sms`
1434
+ * for builds that have not moved. `messaging` never reads `sms.enabled`;
1435
+ * it falls back to `sms.signature` only while its own `signature` is absent.
1436
+ */
1380
1437
  sms?: SmsIntegration;
1438
+ /** The store's customer-messaging settings — see `StoreMessagingIntegration`. */
1439
+ messaging?: StoreMessagingIntegration;
1381
1440
  gmail?: Gmail;
1382
1441
  mercadolibre?: Mercadolibre;
1383
1442
  tiendanube?: Tiendanube;
@@ -1387,6 +1446,37 @@ declare global {
1387
1446
  enabled?: boolean;
1388
1447
  signature?: string;
1389
1448
  }
1449
+ /**
1450
+ * One store-paid customer message a store switches on or off — one of
1451
+ * `STORE_MESSAGE_NOTICES`. Each maps to exactly one notice the api emits.
1452
+ * `orderShipped` covers dispatch (shipped and delivered); `serviceOrderQuoted`
1453
+ * covers the quote-sent notice and the `quoted` status fallback alike.
1454
+ */
1455
+ type StoreMessageNotice = 'orderReady' | 'orderShipped' | 'serviceOrderReceived' | 'serviceOrderQuoted' | 'serviceOrderApproved' | 'serviceOrderReady' | 'serviceOrderCancelled' | 'serviceOrderReturnedUnrepaired' | 'serviceOrderAbandoned' | 'serviceOrderOnHold' | 'serviceOrderResumed' | 'appointmentReminder' | 'paymentReminder';
1456
+ /**
1457
+ * `Store.integrations.messaging`. Written by `PATCH /store` only (ADMIN,
1458
+ * SUPERVISOR, MANAGER; a PRINTER claim is refused), merged key by key, so a
1459
+ * body naming one switch leaves the others as stored. An absent key reads
1460
+ * its `STORE_MESSAGING_DEFAULTS` value: status notices and appointment
1461
+ * reminders on, payment reminders off, `smsFallback` on. Read it through
1462
+ * `resolveStoreMessaging`, never off the raw keys. Sending is gated by the
1463
+ * `tenantMessagingEnabled` platform global as well.
1464
+ */
1465
+ interface StoreMessagingIntegration extends Partial<Record<StoreMessageNotice, boolean>> {
1466
+ /** «Si WhatsApp falla, enviar por SMS». The SMS goes out only on a WhatsApp `failed`/`undelivered` status. */
1467
+ smsFallback?: boolean;
1468
+ /**
1469
+ * Firma, at most 80 characters after trimming (the cap `sms.signature`
1470
+ * has). Absent falls back to `sms.signature`; `''` means no firma.
1471
+ */
1472
+ signature?: string;
1473
+ }
1474
+ /** `resolveStoreMessaging`'s answer: every switch decided. */
1475
+ interface ResolvedStoreMessaging extends Record<StoreMessageNotice, boolean> {
1476
+ smsFallback: boolean;
1477
+ /** Absent when neither `messaging.signature` nor `sms.signature` is stored. */
1478
+ signature?: string;
1479
+ }
1390
1480
  interface Gmail {
1391
1481
  connected?: boolean;
1392
1482
  senderEmail?: string;
@@ -1922,6 +2012,13 @@ declare global {
1922
2012
  * still broadcasts the row as it stands.
1923
2013
  */
1924
2014
  onboardingReviewAdd?: StoreOnboardingReviewArea[];
2015
+ /**
2016
+ * `PATCH /store` only (the closed `POST /store` refuses it
2017
+ * `FIELD_NOT_WRITABLE_HERE`). Merged key by key over the stored leaf; an
2018
+ * undeclared key is a 400. A plain USER is refused 403
2019
+ * `STORE_MESSAGING_FORBIDDEN`.
2020
+ */
2021
+ messaging?: StoreMessagingIntegration;
1925
2022
  }
1926
2023
  /**
1927
2024
  * How the storefront offers a `paymentMethods` row — one of
@@ -2592,13 +2689,14 @@ export type StoreModuleRefusalBody = {
2592
2689
  * Role refusals, `403` — a plain USER writing a privileged section. Each is
2593
2690
  * judged before any other check on that section:
2594
2691
  * - `STORE_CONFIG_FORBIDDEN` — any privileged `config` leaf (`modules`,
2595
- * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, …).
2692
+ * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, `autoPrint`, …).
2596
2693
  * - `STORE_ECOMMERCE_FORBIDDEN` — any `ecommerce` block.
2597
2694
  * - `STORE_FISCAL_FIELD_FORBIDDEN` — a fiscal field of the store.
2598
2695
  * - `STORE_JURISDICTION_FORBIDDEN` — `jurisdiction`.
2599
2696
  * - `AFIP_RELACION_FLAG_FORBIDDEN` — `afip.wscdcEnabled` / `afip.fceEnabled`.
2600
2697
  * - `FX_TABLE_FORBIDDEN` — the `currencies` exchange-rate table.
2601
2698
  * - `FX_AUTO_UPDATE_FORBIDDEN` — `fxAutoUpdate`.
2699
+ * - `STORE_MESSAGING_FORBIDDEN` — the `messaging` customer-messaging settings.
2602
2700
  *
2603
2701
  * Request refusals, `400`:
2604
2702
  * - `FIELD_NOT_WRITABLE_HERE` — a field written through `POST /store`, not
@@ -2654,7 +2752,7 @@ export type StoreModuleRefusalBody = {
2654
2752
  * checks, the display-currency lock, the storefront fence) are not listed
2655
2753
  * either. Cross-cutting refusals: check `GATING_ERROR_CODES` as well.
2656
2754
  */
2657
- export declare const STORE_PATCH_ERROR_CODES: readonly ["STORE_CONFIG_FORBIDDEN", "STORE_ECOMMERCE_FORBIDDEN", "STORE_FISCAL_FIELD_FORBIDDEN", "STORE_JURISDICTION_FORBIDDEN", "AFIP_RELACION_FLAG_FORBIDDEN", "FX_TABLE_FORBIDDEN", "FX_AUTO_UPDATE_FORBIDDEN", "FIELD_NOT_WRITABLE_HERE", "AFIP_POINT_OF_SALE_NOT_CLEARABLE", "CURRENCY_CATALOG_ID_UNKNOWN", "ARS_RATE_MUST_BE_ONE", "CURRENCY_RATE_ABOVE_CEILING", "FX_CATALOG_ID_UNKNOWN", "FX_SOURCE_ID_UNKNOWN", "DISPLAY_CURRENCY_NOT_INVOICEABLE", "PRODUCT_CURRENCY_NOT_INVOICEABLE", "PRICE_LIST_NOT_FOUND", "DELIVERY_FEE_INVALID", "WHOLESALE_LIST_REQUIRED", "NET_PLUS_IVA_REQUIRES_RI", "DATA_FISCAL_URL_INVALID", "STORE_PUBLIC_HOST_MISSING", "FORBIDDEN", "STORE_NOT_FOUND"];
2755
+ export declare const STORE_PATCH_ERROR_CODES: readonly ["STORE_CONFIG_FORBIDDEN", "STORE_ECOMMERCE_FORBIDDEN", "STORE_FISCAL_FIELD_FORBIDDEN", "STORE_JURISDICTION_FORBIDDEN", "AFIP_RELACION_FLAG_FORBIDDEN", "FX_TABLE_FORBIDDEN", "FX_AUTO_UPDATE_FORBIDDEN", "FIELD_NOT_WRITABLE_HERE", "AFIP_POINT_OF_SALE_NOT_CLEARABLE", "CURRENCY_CATALOG_ID_UNKNOWN", "ARS_RATE_MUST_BE_ONE", "CURRENCY_RATE_ABOVE_CEILING", "FX_CATALOG_ID_UNKNOWN", "FX_SOURCE_ID_UNKNOWN", "DISPLAY_CURRENCY_NOT_INVOICEABLE", "PRODUCT_CURRENCY_NOT_INVOICEABLE", "PRICE_LIST_NOT_FOUND", "DELIVERY_FEE_INVALID", "WHOLESALE_LIST_REQUIRED", "NET_PLUS_IVA_REQUIRES_RI", "DATA_FISCAL_URL_INVALID", "STORE_PUBLIC_HOST_MISSING", "STORE_MESSAGING_FORBIDDEN", "FORBIDDEN", "STORE_NOT_FOUND"];
2658
2756
  export type StorePatchErrorCode = (typeof STORE_PATCH_ERROR_CODES)[number];
2659
2757
  /** Who pays the return shipping under a store's change-of-mind policy. */
2660
2758
  export declare const CHANGE_OF_MIND_RETURN_SHIPPING_PAYERS: readonly ["merchant", "shopper"];
@@ -2676,3 +2774,41 @@ declare global {
2676
2774
  refundAs: ChangeOfMindRefundAs;
2677
2775
  }
2678
2776
  }
2777
+ /**
2778
+ * Every `StoreMessageNotice`, in the order the settings screen lists them —
2779
+ * the keys `Store.integrations.messaging` switches. `UncoveredStoreMessageNotices`
2780
+ * fails `typecheck` naming a notice the union gains and this tuple lacks.
2781
+ */
2782
+ export declare const STORE_MESSAGE_NOTICES: readonly ["orderReady", "orderShipped", "serviceOrderReceived", "serviceOrderQuoted", "serviceOrderApproved", "serviceOrderReady", "serviceOrderCancelled", "serviceOrderReturnedUnrepaired", "serviceOrderAbandoned", "serviceOrderOnHold", "serviceOrderResumed", "appointmentReminder", "paymentReminder"];
2783
+ type AssertNoUncoveredStoreMessageNotice<T extends never> = T;
2784
+ export type UncoveredStoreMessageNotices = AssertNoUncoveredStoreMessageNotice<Exclude<StoreMessageNotice, (typeof STORE_MESSAGE_NOTICES)[number]>>;
2785
+ /**
2786
+ * What an ABSENT `messaging` key reads: status notices and appointment
2787
+ * reminders on, payment reminders off, the SMS fallback on.
2788
+ */
2789
+ export declare const STORE_MESSAGING_DEFAULTS: {
2790
+ readonly orderReady: true;
2791
+ readonly orderShipped: true;
2792
+ readonly serviceOrderReceived: true;
2793
+ readonly serviceOrderQuoted: true;
2794
+ readonly serviceOrderApproved: true;
2795
+ readonly serviceOrderReady: true;
2796
+ readonly serviceOrderCancelled: true;
2797
+ readonly serviceOrderReturnedUnrepaired: true;
2798
+ readonly serviceOrderAbandoned: true;
2799
+ readonly serviceOrderOnHold: true;
2800
+ readonly serviceOrderResumed: true;
2801
+ readonly appointmentReminder: true;
2802
+ readonly paymentReminder: false;
2803
+ readonly smsFallback: true;
2804
+ };
2805
+ /** The firma cap — the same one `sms.signature` has. */
2806
+ export declare const STORE_MESSAGING_SIGNATURE_MAX = 80;
2807
+ /**
2808
+ * Every switch of a store's messaging settings, decided: per key, the stored
2809
+ * boolean, else its `STORE_MESSAGING_DEFAULTS` value (a stored non-boolean
2810
+ * reads as absent). `signature` falls back to `sms.signature` only when
2811
+ * `messaging.signature` is absent; a stored `''` is kept and means no firma.
2812
+ */
2813
+ export declare const resolveStoreMessaging: (integrations: Pick<StoreIntegrations, "messaging" | "sms"> | undefined) => ResolvedStoreMessaging;
2814
+ export {};
package/dist/store.js CHANGED
@@ -107,13 +107,14 @@ export const STORE_MODULE_ERROR_CODES = [
107
107
  * Role refusals, `403` — a plain USER writing a privileged section. Each is
108
108
  * judged before any other check on that section:
109
109
  * - `STORE_CONFIG_FORBIDDEN` — any privileged `config` leaf (`modules`,
110
- * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, …).
110
+ * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, `autoPrint`, …).
111
111
  * - `STORE_ECOMMERCE_FORBIDDEN` — any `ecommerce` block.
112
112
  * - `STORE_FISCAL_FIELD_FORBIDDEN` — a fiscal field of the store.
113
113
  * - `STORE_JURISDICTION_FORBIDDEN` — `jurisdiction`.
114
114
  * - `AFIP_RELACION_FLAG_FORBIDDEN` — `afip.wscdcEnabled` / `afip.fceEnabled`.
115
115
  * - `FX_TABLE_FORBIDDEN` — the `currencies` exchange-rate table.
116
116
  * - `FX_AUTO_UPDATE_FORBIDDEN` — `fxAutoUpdate`.
117
+ * - `STORE_MESSAGING_FORBIDDEN` — the `messaging` customer-messaging settings.
117
118
  *
118
119
  * Request refusals, `400`:
119
120
  * - `FIELD_NOT_WRITABLE_HERE` — a field written through `POST /store`, not
@@ -192,6 +193,7 @@ export const STORE_PATCH_ERROR_CODES = [
192
193
  'NET_PLUS_IVA_REQUIRES_RI',
193
194
  'DATA_FISCAL_URL_INVALID',
194
195
  'STORE_PUBLIC_HOST_MISSING',
196
+ 'STORE_MESSAGING_FORBIDDEN',
195
197
  'FORBIDDEN',
196
198
  'STORE_NOT_FOUND',
197
199
  ];
@@ -203,3 +205,62 @@ export const CHANGE_OF_MIND_REFUND_AS = ['refund', 'store-credit'];
203
205
  export const CHANGE_OF_MIND_RETURN_DAYS_MAX = 365;
204
206
  /** The source of a change-of-mind deadline: the store's own setting, not a statutory row. */
205
207
  export const CHANGE_OF_MIND_WINDOW_SOURCE = 'store:changeOfMindReturns';
208
+ /**
209
+ * Every `StoreMessageNotice`, in the order the settings screen lists them —
210
+ * the keys `Store.integrations.messaging` switches. `UncoveredStoreMessageNotices`
211
+ * fails `typecheck` naming a notice the union gains and this tuple lacks.
212
+ */
213
+ export const STORE_MESSAGE_NOTICES = [
214
+ 'orderReady',
215
+ 'orderShipped',
216
+ 'serviceOrderReceived',
217
+ 'serviceOrderQuoted',
218
+ 'serviceOrderApproved',
219
+ 'serviceOrderReady',
220
+ 'serviceOrderCancelled',
221
+ 'serviceOrderReturnedUnrepaired',
222
+ 'serviceOrderAbandoned',
223
+ 'serviceOrderOnHold',
224
+ 'serviceOrderResumed',
225
+ 'appointmentReminder',
226
+ 'paymentReminder',
227
+ ];
228
+ /**
229
+ * What an ABSENT `messaging` key reads: status notices and appointment
230
+ * reminders on, payment reminders off, the SMS fallback on.
231
+ */
232
+ export const STORE_MESSAGING_DEFAULTS = {
233
+ orderReady: true,
234
+ orderShipped: true,
235
+ serviceOrderReceived: true,
236
+ serviceOrderQuoted: true,
237
+ serviceOrderApproved: true,
238
+ serviceOrderReady: true,
239
+ serviceOrderCancelled: true,
240
+ serviceOrderReturnedUnrepaired: true,
241
+ serviceOrderAbandoned: true,
242
+ serviceOrderOnHold: true,
243
+ serviceOrderResumed: true,
244
+ appointmentReminder: true,
245
+ paymentReminder: false,
246
+ smsFallback: true,
247
+ };
248
+ /** The firma cap — the same one `sms.signature` has. */
249
+ export const STORE_MESSAGING_SIGNATURE_MAX = 80;
250
+ /**
251
+ * Every switch of a store's messaging settings, decided: per key, the stored
252
+ * boolean, else its `STORE_MESSAGING_DEFAULTS` value (a stored non-boolean
253
+ * reads as absent). `signature` falls back to `sms.signature` only when
254
+ * `messaging.signature` is absent; a stored `''` is kept and means no firma.
255
+ */
256
+ export const resolveStoreMessaging = (integrations) => {
257
+ const leaf = integrations?.messaging;
258
+ const resolved = { ...STORE_MESSAGING_DEFAULTS };
259
+ for (const key of [...STORE_MESSAGE_NOTICES, 'smsFallback']) {
260
+ const stored = leaf?.[key];
261
+ if (typeof stored === 'boolean')
262
+ resolved[key] = stored;
263
+ }
264
+ const signature = typeof leaf?.signature === 'string' ? leaf.signature : integrations?.sms?.signature;
265
+ return typeof signature === 'string' ? { ...resolved, signature } : resolved;
266
+ };
@@ -100,7 +100,7 @@ declare global {
100
100
  * exhausts this union (a `switch`, a `Record<FeatureKey, …>`) still has to
101
101
  * handle the new member in the same change.
102
102
  */
103
- type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxWarehouses' | 'priceListsMax' | 'webhooks' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing' | 'mandatory2fa';
103
+ type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxWarehouses' | 'priceListsMax' | 'webhooks' | 'monthlyMessageAllowance' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing' | 'mandatory2fa';
104
104
  /** Resolved entitlements for a specific tenant (matrix + overrides applied). */
105
105
  type ResolvedEntitlements = Record<FeatureKey, Entitlement>;
106
106
  /**
@@ -1238,7 +1238,7 @@ export declare const isPaidPlanTier: (value: unknown) => value is PaidPlanTier;
1238
1238
  * metered / lifetime cap PASSES THROUGH — a new cap silently unbounded.
1239
1239
  * Checklist: `docs/PLANS_AND_ENTITLEMENTS.md` in the api repo.
1240
1240
  */
1241
- export declare const FEATURE_KEYS: readonly ["afipInvoicing", "suppliers", "advancedReports", "cashManagement", "importExport", "domesticPayments", "stripePayments", "aiFeatures", "mobileApp", "customBranding", "customDomain", "prioritySupport", "advancedPricing", "marketplaceChannels", "storefront", "loyalty", "marketing", "mandatory2fa", "maxUsers", "maxProducts", "maxCustomers", "maxWarehouses", "priceListsMax", "webhooks", "maxOrdersMonth", "maxInvoicesMonth"];
1241
+ export declare const FEATURE_KEYS: readonly ["afipInvoicing", "suppliers", "advancedReports", "cashManagement", "importExport", "domesticPayments", "stripePayments", "aiFeatures", "mobileApp", "customBranding", "customDomain", "prioritySupport", "advancedPricing", "marketplaceChannels", "storefront", "loyalty", "marketing", "mandatory2fa", "maxUsers", "maxProducts", "maxCustomers", "maxWarehouses", "priceListsMax", "webhooks", "maxOrdersMonth", "maxInvoicesMonth", "monthlyMessageAllowance"];
1242
1242
  type AssertNoUncoveredFeatureKey<T extends never> = T;
1243
1243
  export type UncoveredFeatureKeys = AssertNoUncoveredFeatureKey<Exclude<FeatureKey, (typeof FEATURE_KEYS)[number]>>;
1244
1244
  export {};
@@ -128,4 +128,5 @@ export const FEATURE_KEYS = [
128
128
  'webhooks',
129
129
  'maxOrdersMonth',
130
130
  'maxInvoicesMonth',
131
+ 'monthlyMessageAllowance',
131
132
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platform-types",
3
- "version": "1.10.835",
3
+ "version": "1.10.837",
4
4
  "description": "Shared wire and entity contracts for the platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",