sinfactura-types 1.10.449 → 1.10.451

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.
package/dist/afip.d.ts CHANGED
@@ -142,5 +142,46 @@ declare global {
142
142
  * certificate at the one moment the operator is uploading it.
143
143
  */
144
144
  type AfipCertUploadErrorCode = 'AFIP_CERT_NOT_A_CERTIFICATE' | 'AFIP_KEY_NOT_A_PRIVATE_KEY' | 'AFIP_CERT_CUIT_MISSING' | 'AFIP_CERT_CUIT_UNREADABLE' | 'AFIP_CERT_CUIT_MISMATCH' | 'AFIP_CERT_KEY_MISMATCH' | 'AFIP_CERT_EXPIRED';
145
+ /**
146
+ * The body of `POST /afip { mode: 'upload' }` — ONE half of the AFIP
147
+ * credential per request: the X.509 certificate (`cert`) or the private key
148
+ * (`key`), never both. The success answer is `{ message }` with no `data`;
149
+ * nothing uploaded is ever echoed back.
150
+ *
151
+ * A half counts as PRESENT only when its string is over 200 characters; a
152
+ * shorter one (an empty string included) is treated exactly as absent. So a
153
+ * body with neither half present is refused `400 AFIP_CERT_OR_KEY_MISSING` —
154
+ * a short `cert` of garbage gets that code, never
155
+ * `AFIP_CERT_NOT_A_CERTIFICATE` — and one with both present
156
+ * `400 AFIP_CERT_AND_KEY_TOGETHER`, while `{ cert: '', key: <PEM> }` is an
157
+ * ordinary key upload. A body that fails the schema (a non-string half, a
158
+ * non-boolean `rotate`) is refused `400 VALIDATION_FAILED` before any of
159
+ * that. Every other refusal comes from the upload pre-flight (file parse,
160
+ * store CUIT, keypair, expiry), coded by `AfipCertUploadErrorCode`.
161
+ *
162
+ * ⚠️ **A KEY upload blanks the stored certificate**, so a key that is not the
163
+ * stored certificate's keypair is refused `400 AFIP_CERT_KEY_MISMATCH` —
164
+ * UNLESS the body says `rotate: true`. With it the key is accepted, the stored
165
+ * certificate is blanked, and the next certificate upload is checked against
166
+ * the NEW key. That is how a tenant rotates an externally generated keypair:
167
+ * key first with `rotate: true`, then the certificate issued for it.
168
+ * Without the flag a rotation is impossible in either order.
169
+ *
170
+ * `rotate` is typed `boolean` because the endpoint accepts `false`, which
171
+ * means exactly what absent means. It is ignored on a certificate upload.
172
+ *
173
+ * Tied to the handler's Zod schema (minus `mode`, which the dispatch table
174
+ * consumes) by `AssertAfipCertUploadRequestMatchesSchema` in
175
+ * `stacks/lambdas/afip/_upload.ts`.
176
+ */
177
+ interface AfipCertUploadRequest {
178
+ mode: 'upload';
179
+ /** PEM private key. Encrypted at rest; never returned by any endpoint. */
180
+ key?: string;
181
+ /** PEM X.509 certificate. */
182
+ cert?: string;
183
+ /** Confirms a KEY upload as a deliberate keypair rotation. See above. */
184
+ rotate?: boolean;
185
+ }
145
186
  }
146
187
  export {};
package/dist/ai.d.ts CHANGED
@@ -71,6 +71,23 @@ declare global {
71
71
  * `0`, because `0` is a value an operator would accept without checking.
72
72
  */
73
73
  interface SupplierDocExtractItem {
74
+ /**
75
+ * The supplier's own product code for this line, as printed, trimmed.
76
+ * Absent when the line prints none — never `''`.
77
+ *
78
+ * A JOIN KEY that survives a misread `description`: match the line to a
79
+ * purchase-order line on `barcode`, then `sku`, then name. Additive — a
80
+ * line extracted before this field existed simply lacks it.
81
+ */
82
+ sku?: string;
83
+ /**
84
+ * The printed barcode (EAN/UPC), reduced to its DIGITS — spaces, dashes
85
+ * and any other character are removed server-side. Absent when the line
86
+ * prints none, or nothing numeric survives — never `''`. Its length is
87
+ * NOT validated as an EAN/UPC here: it is a join key, and a misread value
88
+ * simply fails to match, so fall back to `sku`, then name.
89
+ */
90
+ barcode?: string;
74
91
  /**
75
92
  * The line's description as printed.
76
93
  *
package/dist/api.d.ts CHANGED
@@ -25,8 +25,14 @@ declare global {
25
25
  * `Query`. Can be a NUMBER when the query rides a GSI with a numeric
26
26
  * sort key (e.g. `GET /invoices`' date branch yields `dated` as a number).
27
27
  * - **Opaque string** — a base64url cursor the caller round-trips back
28
- * unparsed as `?startKey=` (e.g. `GET /suppliers?mode=invoices`),
29
- * derived from the last RETURNED row, never a raw `LastEvaluatedKey`.
28
+ * unparsed as `?startKey=` (e.g. `GET /suppliers?mode=invoices`,
29
+ * `GET /customers`). Where it resumes is the endpoint's business and is
30
+ * NOT always the last returned row: a walk that stopped because its page
31
+ * filled resumes after the last RETURNED row, while one that stopped on
32
+ * its read budget resumes from DynamoDB's own `LastEvaluatedKey` — the
33
+ * read head, past rows its filter dropped (`deriveResumeKey`,
34
+ * `stacks/helpers/opaqueCursor.ts`). Some cursors are also bound to the
35
+ * filters they were issued under and refused `INVALID_CURSOR` otherwise.
30
36
  *
31
37
  * Consumers must not assume this is enumerable — treat the string form
32
38
  * as opaque and pass it straight back.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
- * Published WhatsApp surface — the tenant-facing connection config only.
3
+ * Published WhatsApp surface — the tenant-facing connection config and the
4
+ * tenant's own template send (`POST /whatsapp/send`).
4
5
  *
5
6
  * ⚠️ **Meta's inbound webhook payload is NOT here and must not come back.**
6
7
  * The twelve shapes Meta POSTs to `/whatsapp` — the entry/change/value
@@ -14,3 +15,56 @@
14
15
  * against `lib.dom`'s `Document`, returning `undefined` at runtime.
15
16
  */
16
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.WHATSAPP_SEND_ERROR_CODES = void 0;
19
+ /**
20
+ * `POST /whatsapp/send` refusals, beyond the cross-cutting `GATING_ERROR_CODES`
21
+ * (notably `PLAN_LIMIT_EXCEEDED` for the `whatsappMessages` monthly cap).
22
+ *
23
+ * - `VALIDATION_FAILED` · 400 — including a body naming both or neither of
24
+ * `to` / `customerId`.
25
+ * - `IDEMPOTENCY_KEY_REQUIRED` · 400 — no `Idempotency-Key` header. The
26
+ * idempotency wrapper's own codes (`INVALID_IDEMPOTENCY_KEY`,
27
+ * `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS`, `IDEMPOTENCY_KEY_FAILED`,
28
+ * `IDEMPOTENCY_LOCK_CONTENTION`) apply too. `IDEMPOTENCY_KEY_FAILED` (409)
29
+ * means an earlier attempt under this key may have reached Meta: check the
30
+ * recipient's chat before sending again under a NEW key.
31
+ * - `PLATFORM_INTEGRATION_DISABLED` · 503 — WhatsApp is switched off
32
+ * platform-wide by SINFACTURA.
33
+ * - `WHATSAPP_NOT_CONNECTED` · 409 — the calling store has no connected
34
+ * WhatsApp, or a connection with no stored token after a disconnect. Disable
35
+ * the send control. ⚠️ Never falls back to the platform number.
36
+ * - `WHATSAPP_TOKEN_REJECTED` · 409 — connected, but Meta refused the stored
37
+ * token. Reconnect.
38
+ * - `CUSTOMER_NOT_FOUND` · 404 / `WHATSAPP_RECIPIENT_PHONE_MISSING` · 400 — a bad
39
+ * id versus a real customer with no usable number.
40
+ * - `WHATSAPP_RECIPIENT_NOT_ALLOWED` · 403 — non-production stages only: the
41
+ * number is not on that stage's test allowlist. Nothing was sent.
42
+ * - `WHATSAPP_TEMPLATE_NOT_APPROVED` · 422 — not approved, paused, disabled,
43
+ * wrong language or wrong parameter count (Meta does not tell these apart).
44
+ * - `WHATSAPP_RECIPIENT_OPTED_OUT` · 409 — the recipient cannot receive it.
45
+ * - `WHATSAPP_RATE_LIMITED` · 429 — throughput cap; retry later.
46
+ * - `WHATSAPP_ACCOUNT_SUSPENDED` · 403 — the WhatsApp Business account or the
47
+ * number itself is restricted.
48
+ * - `WHATSAPP_SEND_FAILED` · 502 — any other Meta 4xx refusal; carries `metaCode`.
49
+ * - `WHATSAPP_SEND_UNCONFIRMED` · 502 — the request reached Meta and its outcome
50
+ * is unknown: no answer came back, Meta answered 5xx, or Meta accepted it
51
+ * without a message id.
52
+ * The message may have been delivered, so the key is sealed:
53
+ * a retry under it answers `IDEMPOTENCY_KEY_FAILED` rather than re-sending.
54
+ */
55
+ exports.WHATSAPP_SEND_ERROR_CODES = [
56
+ 'VALIDATION_FAILED',
57
+ 'IDEMPOTENCY_KEY_REQUIRED',
58
+ 'PLATFORM_INTEGRATION_DISABLED',
59
+ 'WHATSAPP_NOT_CONNECTED',
60
+ 'WHATSAPP_TOKEN_REJECTED',
61
+ 'CUSTOMER_NOT_FOUND',
62
+ 'WHATSAPP_RECIPIENT_PHONE_MISSING',
63
+ 'WHATSAPP_RECIPIENT_NOT_ALLOWED',
64
+ 'WHATSAPP_TEMPLATE_NOT_APPROVED',
65
+ 'WHATSAPP_RECIPIENT_OPTED_OUT',
66
+ 'WHATSAPP_RATE_LIMITED',
67
+ 'WHATSAPP_ACCOUNT_SUSPENDED',
68
+ 'WHATSAPP_SEND_FAILED',
69
+ 'WHATSAPP_SEND_UNCONFIRMED',
70
+ ];
@@ -57,6 +57,56 @@ declare global {
57
57
  }
58
58
  /** Write shape for customer create/update — carries the transient photo controls. */
59
59
  type CustomerUpsertInput = Partial<Customer> & PhotoUploadControls;
60
+ /**
61
+ * The query string of the customers LIST read — `GET /customers` with no
62
+ * `customerId`, `list` or `email`. Every present filter AND-composes: a row is
63
+ * returned only when it matches all of them. The store's system customer is
64
+ * never listed, and rows come back newest first.
65
+ *
66
+ * Answered as a `ResponseApi` whose `data` is an array of display-projected
67
+ * rows (`customerId` plus the list's display fields — never the commercial or
68
+ * fiscal terms, which only the `customerId` point read serves), with an opaque
69
+ * cursor in `LastEvaluatedKey` and `truncated: true` present EXACTLY when a
70
+ * cursor is — a page that stopped before the store's customers ran out carries
71
+ * both, the last page carries neither.
72
+ *
73
+ * ⚠️ Refusals, all `400` and all before any read:
74
+ * - `INVALID_FILTER`, with `data.param` naming the parameter, for a value outside
75
+ * a chip's set or a `limit` outside 1–100. An unknown value is never ignored:
76
+ * ignoring it answered the unfiltered list under a lit chip.
77
+ * - `INVALID_CURSOR` for a `startKey` that is malformed, belongs to another store,
78
+ * or was issued under DIFFERENT filters. The cursor is bound to the filters it
79
+ * was minted with (the search compared after accent and case folding, `limit`
80
+ * excluded), so a client that changes a filter must restart from page one.
81
+ * Treat it as a stall, not a failure.
82
+ *
83
+ * An EMPTY value (`?withBuy=`) is the same as an absent one.
84
+ */
85
+ interface CustomerListQuery {
86
+ /**
87
+ * Free-text terms matched against the customer's search index — accent- and
88
+ * case-insensitive, up to four terms, every term required. A value that
89
+ * folds to no term is no search.
90
+ */
91
+ search?: string;
92
+ /**
93
+ * The soft-delete set. Absent or `'false'`: enabled customers only (a row
94
+ * that never carried the flag counts as enabled). `'true'`: disabled only.
95
+ * `'all'`: both.
96
+ */
97
+ disabled?: 'true' | 'false' | 'all';
98
+ /** `'true'`: customers who have bought at least once. `'false'`: never bought. */
99
+ withBuy?: 'true' | 'false';
100
+ /**
101
+ * `'owing'`: a positive balance, i.e. the customer owes the store.
102
+ * `'nonzero'`: any non-zero balance, in either direction.
103
+ */
104
+ balance?: 'owing' | 'nonzero';
105
+ /** Rows per page, 1–100. Absent: a page of 1000 rows. */
106
+ limit?: number;
107
+ /** The previous page's `LastEvaluatedKey`, sent back unchanged under the SAME filters. */
108
+ startKey?: string;
109
+ }
60
110
  /**
61
111
  * One unsettled debit on a customer's current account, as denormalised onto
62
112
  * the `Customer` row for the accounts report's aging summary.
@@ -85,7 +85,7 @@ declare global {
85
85
  * exhausts this union (a `switch`, a `Record<FeatureKey, …>`) still has to
86
86
  * handle the new member in the same change.
87
87
  */
88
- type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxWarehouses' | 'priceListsMax' | 'webhooks' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'whatsappCommerce' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing' | 'mandatory2fa';
88
+ type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'whatsappMessages' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxWarehouses' | 'priceListsMax' | 'webhooks' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'whatsappCommerce' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing' | 'mandatory2fa';
89
89
  /** Resolved entitlements for a specific tenant (matrix + overrides applied). */
90
90
  type ResolvedEntitlements = Record<FeatureKey, Entitlement>;
91
91
  /**
package/dist/webhook.d.ts CHANGED
@@ -142,6 +142,78 @@ export interface Webhook {
142
142
  consecutiveFailures: number;
143
143
  entityType: 'WEBHOOK';
144
144
  }
145
+ /**
146
+ * A subscription as the list (`GET …/webhooks`) returns it: an ALLOW-LIST
147
+ * projection of {@link Webhook}. No `storeId`, no `search`, no `entityType` —
148
+ * and never the signing key, which no read returns.
149
+ */
150
+ export type PublicWebhook = Pick<Webhook, 'webhookId' | 'url' | 'events' | 'active' | 'description' | 'createdAt' | 'updatedAt' | 'lastDeliveryAt' | 'lastError' | 'consecutiveFailures'>;
151
+ /**
152
+ * The body of `POST …/webhooks` — a create when `webhookId` is ABSENT, an
153
+ * update of that webhook when present. Omit the key entirely to create; an
154
+ * unknown `webhookId` is `404 WEBHOOK_NOT_FOUND`, never a create.
155
+ *
156
+ * `active` defaults to `true` on a create and to the stored value on an
157
+ * update. An update never re-mints the signing secret — that is
158
+ * `…/rotate-secret`, and nothing else.
159
+ *
160
+ * Tied to the handler's Zod schema by `AssertWebhookUpsertRequestMatchesSchema`
161
+ * in `stacks/lambdas/tenants/_webhooks.ts`.
162
+ */
163
+ export interface WebhookUpsertRequest {
164
+ /** Max 64 characters. */
165
+ webhookId?: string;
166
+ /** A URL, max 2048 characters, and must pass the egress check. */
167
+ url: string;
168
+ /** At least one. */
169
+ events: WebhookEventType[];
170
+ active?: boolean;
171
+ /** Max 500 characters. */
172
+ description?: string;
173
+ }
174
+ /**
175
+ * `data` of `POST …/webhooks`.
176
+ *
177
+ * ⚠️ `secret` is the plaintext signing key and is SHOW-ONCE: present on a
178
+ * create only, absent on every update, and never readable again. Lose it and
179
+ * the only recovery is `…/rotate-secret`.
180
+ */
181
+ export interface WebhookUpsertResponse {
182
+ webhookId: string;
183
+ url: string;
184
+ events: WebhookEventType[];
185
+ active: boolean;
186
+ description?: string;
187
+ secret?: string;
188
+ }
189
+ /**
190
+ * `data` of `POST …/webhooks/{webhookId}/rotate-secret`. The request carries NO
191
+ * body — the path names the webhook.
192
+ *
193
+ * Mints a new signing key and returns it show-once. Only `updatedAt` moves
194
+ * with it (the response carries the value written); every other field is
195
+ * untouched.
196
+ */
197
+ export interface WebhookRotateSecretResponse extends PublicWebhook {
198
+ secret: string;
199
+ }
200
+ /**
201
+ * `data` of `POST …/webhooks/{webhookId}/test`, answered `202`. The request
202
+ * carries NO body — the path names the webhook.
203
+ *
204
+ * Queues ONE signed {@link WebhookTestPayload}, never retried; the outcome is
205
+ * read off `…/deliveries`, on the {@link WebhookDelivery} row with this
206
+ * `deliveryId` and `test: true`.
207
+ */
208
+ export interface WebhookTestResponse {
209
+ webhookId: string;
210
+ /**
211
+ * The queue's message id for the send. Optional because the handler
212
+ * forwards the SQS SDK's `MessageId` as-is and the SDK types it optional;
213
+ * a successful send carries one. Treat absent as "queued, id unknown".
214
+ */
215
+ deliveryId?: string;
216
+ }
145
217
  /**
146
218
  * Payload for `payment.received` — a provider settlement landed and its
147
219
  * `PAYMENT#{storeId}` row is durable.
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Published WhatsApp surface — the tenant-facing connection config only.
2
+ * Published WhatsApp surface — the tenant-facing connection config and the
3
+ * tenant's own template send (`POST /whatsapp/send`).
3
4
  *
4
5
  * ⚠️ **Meta's inbound webhook payload is NOT here and must not come back.**
5
6
  * The twelve shapes Meta POSTs to `/whatsapp` — the entry/change/value
@@ -49,5 +50,99 @@ declare global {
49
50
  /** ISO timestamp. */
50
51
  disconnectedAt?: string;
51
52
  }
53
+ /**
54
+ * Request body for `POST /whatsapp/send` — the TENANT's own template send,
55
+ * under `adminToken`, from the calling store's own connected number.
56
+ *
57
+ * ⚠️ The request must carry an `Idempotency-Key` header — without one it is
58
+ * refused `400 IDEMPOTENCY_KEY_REQUIRED`. Meta is called before anything is
59
+ * written, so a retried request with no key would send the message twice.
60
+ *
61
+ * ⚠️ There is no `storeId` member and there must never be one. The sending
62
+ * store is the authorizer's; a body-supplied one is the shape that would let
63
+ * a tenant address another tenant's number.
64
+ *
65
+ * ⚠️ Template only. Outside Meta's 24-hour customer-service window a free-form
66
+ * message is refused, and every case this serves (invoice reminder, payment
67
+ * link, order confirmation) is outside it — so there is no free-text member.
68
+ *
69
+ * Exactly one of `to` / `customerId` is required.
70
+ */
71
+ interface WhatsAppSendRequest {
72
+ /** Meta-approved template NAME, as registered against the store's own WhatsApp Business account. */
73
+ template: string;
74
+ /**
75
+ * Language code of the approved translation. Defaults to `'es'`.
76
+ *
77
+ * ⚠️ Meta matches a template by (name, language) PAIR — an approved template
78
+ * requested in the wrong language is refused exactly like one that was never
79
+ * approved (`WHATSAPP_TEMPLATE_NOT_APPROVED`).
80
+ */
81
+ languageCode?: string;
82
+ /**
83
+ * BODY placeholders, in `{{1}}`, `{{2}}`, … order.
84
+ *
85
+ * ⚠️ ORDER IS THE BINDING — Meta positions them, so an amount where a due
86
+ * date belongs is accepted, delivered and wrong.
87
+ */
88
+ parameters?: string[];
89
+ /** Recipient, digits with country code (`5491150123456`). Mutually exclusive with `customerId`. */
90
+ to?: string;
91
+ /** Recipient by id, resolved against the CALLING store's own customers. */
92
+ customerId?: string;
93
+ /** Display name for the message-log row only; never sent to Meta. */
94
+ name?: string;
95
+ }
96
+ /**
97
+ * `data` of a successful `POST /whatsapp/send`.
98
+ *
99
+ * (Named for the template send, not `WhatsAppSendResult`: that name belongs
100
+ * to the api-internal send seam and has a different shape.)
101
+ */
102
+ interface WhatsAppTemplateSendResult {
103
+ /** Meta's message id — the handle every later delivery-status webhook carries. */
104
+ messageId: string;
105
+ template: string;
106
+ /** ms epoch of the accepted send. */
107
+ sentAt: number;
108
+ }
52
109
  }
110
+ /**
111
+ * `POST /whatsapp/send` refusals, beyond the cross-cutting `GATING_ERROR_CODES`
112
+ * (notably `PLAN_LIMIT_EXCEEDED` for the `whatsappMessages` monthly cap).
113
+ *
114
+ * - `VALIDATION_FAILED` · 400 — including a body naming both or neither of
115
+ * `to` / `customerId`.
116
+ * - `IDEMPOTENCY_KEY_REQUIRED` · 400 — no `Idempotency-Key` header. The
117
+ * idempotency wrapper's own codes (`INVALID_IDEMPOTENCY_KEY`,
118
+ * `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS`, `IDEMPOTENCY_KEY_FAILED`,
119
+ * `IDEMPOTENCY_LOCK_CONTENTION`) apply too. `IDEMPOTENCY_KEY_FAILED` (409)
120
+ * means an earlier attempt under this key may have reached Meta: check the
121
+ * recipient's chat before sending again under a NEW key.
122
+ * - `PLATFORM_INTEGRATION_DISABLED` · 503 — WhatsApp is switched off
123
+ * platform-wide by SINFACTURA.
124
+ * - `WHATSAPP_NOT_CONNECTED` · 409 — the calling store has no connected
125
+ * WhatsApp, or a connection with no stored token after a disconnect. Disable
126
+ * the send control. ⚠️ Never falls back to the platform number.
127
+ * - `WHATSAPP_TOKEN_REJECTED` · 409 — connected, but Meta refused the stored
128
+ * token. Reconnect.
129
+ * - `CUSTOMER_NOT_FOUND` · 404 / `WHATSAPP_RECIPIENT_PHONE_MISSING` · 400 — a bad
130
+ * id versus a real customer with no usable number.
131
+ * - `WHATSAPP_RECIPIENT_NOT_ALLOWED` · 403 — non-production stages only: the
132
+ * number is not on that stage's test allowlist. Nothing was sent.
133
+ * - `WHATSAPP_TEMPLATE_NOT_APPROVED` · 422 — not approved, paused, disabled,
134
+ * wrong language or wrong parameter count (Meta does not tell these apart).
135
+ * - `WHATSAPP_RECIPIENT_OPTED_OUT` · 409 — the recipient cannot receive it.
136
+ * - `WHATSAPP_RATE_LIMITED` · 429 — throughput cap; retry later.
137
+ * - `WHATSAPP_ACCOUNT_SUSPENDED` · 403 — the WhatsApp Business account or the
138
+ * number itself is restricted.
139
+ * - `WHATSAPP_SEND_FAILED` · 502 — any other Meta 4xx refusal; carries `metaCode`.
140
+ * - `WHATSAPP_SEND_UNCONFIRMED` · 502 — the request reached Meta and its outcome
141
+ * is unknown: no answer came back, Meta answered 5xx, or Meta accepted it
142
+ * without a message id.
143
+ * The message may have been delivered, so the key is sealed:
144
+ * a retry under it answers `IDEMPOTENCY_KEY_FAILED` rather than re-sending.
145
+ */
146
+ export declare const WHATSAPP_SEND_ERROR_CODES: readonly ["VALIDATION_FAILED", "IDEMPOTENCY_KEY_REQUIRED", "PLATFORM_INTEGRATION_DISABLED", "WHATSAPP_NOT_CONNECTED", "WHATSAPP_TOKEN_REJECTED", "CUSTOMER_NOT_FOUND", "WHATSAPP_RECIPIENT_PHONE_MISSING", "WHATSAPP_RECIPIENT_NOT_ALLOWED", "WHATSAPP_TEMPLATE_NOT_APPROVED", "WHATSAPP_RECIPIENT_OPTED_OUT", "WHATSAPP_RATE_LIMITED", "WHATSAPP_ACCOUNT_SUSPENDED", "WHATSAPP_SEND_FAILED", "WHATSAPP_SEND_UNCONFIRMED"];
147
+ export type WhatsAppSendErrorCode = (typeof WHATSAPP_SEND_ERROR_CODES)[number];
53
148
  export {};
package/dist/whatsapp.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Published WhatsApp surface — the tenant-facing connection config only.
2
+ * Published WhatsApp surface — the tenant-facing connection config and the
3
+ * tenant's own template send (`POST /whatsapp/send`).
3
4
  *
4
5
  * ⚠️ **Meta's inbound webhook payload is NOT here and must not come back.**
5
6
  * The twelve shapes Meta POSTs to `/whatsapp` — the entry/change/value
@@ -12,4 +13,55 @@
12
13
  * `interface Document` made `document.filename` compile and type as `string`
13
14
  * against `lib.dom`'s `Document`, returning `undefined` at runtime.
14
15
  */
15
- export {};
16
+ /**
17
+ * `POST /whatsapp/send` refusals, beyond the cross-cutting `GATING_ERROR_CODES`
18
+ * (notably `PLAN_LIMIT_EXCEEDED` for the `whatsappMessages` monthly cap).
19
+ *
20
+ * - `VALIDATION_FAILED` · 400 — including a body naming both or neither of
21
+ * `to` / `customerId`.
22
+ * - `IDEMPOTENCY_KEY_REQUIRED` · 400 — no `Idempotency-Key` header. The
23
+ * idempotency wrapper's own codes (`INVALID_IDEMPOTENCY_KEY`,
24
+ * `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS`, `IDEMPOTENCY_KEY_FAILED`,
25
+ * `IDEMPOTENCY_LOCK_CONTENTION`) apply too. `IDEMPOTENCY_KEY_FAILED` (409)
26
+ * means an earlier attempt under this key may have reached Meta: check the
27
+ * recipient's chat before sending again under a NEW key.
28
+ * - `PLATFORM_INTEGRATION_DISABLED` · 503 — WhatsApp is switched off
29
+ * platform-wide by SINFACTURA.
30
+ * - `WHATSAPP_NOT_CONNECTED` · 409 — the calling store has no connected
31
+ * WhatsApp, or a connection with no stored token after a disconnect. Disable
32
+ * the send control. ⚠️ Never falls back to the platform number.
33
+ * - `WHATSAPP_TOKEN_REJECTED` · 409 — connected, but Meta refused the stored
34
+ * token. Reconnect.
35
+ * - `CUSTOMER_NOT_FOUND` · 404 / `WHATSAPP_RECIPIENT_PHONE_MISSING` · 400 — a bad
36
+ * id versus a real customer with no usable number.
37
+ * - `WHATSAPP_RECIPIENT_NOT_ALLOWED` · 403 — non-production stages only: the
38
+ * number is not on that stage's test allowlist. Nothing was sent.
39
+ * - `WHATSAPP_TEMPLATE_NOT_APPROVED` · 422 — not approved, paused, disabled,
40
+ * wrong language or wrong parameter count (Meta does not tell these apart).
41
+ * - `WHATSAPP_RECIPIENT_OPTED_OUT` · 409 — the recipient cannot receive it.
42
+ * - `WHATSAPP_RATE_LIMITED` · 429 — throughput cap; retry later.
43
+ * - `WHATSAPP_ACCOUNT_SUSPENDED` · 403 — the WhatsApp Business account or the
44
+ * number itself is restricted.
45
+ * - `WHATSAPP_SEND_FAILED` · 502 — any other Meta 4xx refusal; carries `metaCode`.
46
+ * - `WHATSAPP_SEND_UNCONFIRMED` · 502 — the request reached Meta and its outcome
47
+ * is unknown: no answer came back, Meta answered 5xx, or Meta accepted it
48
+ * without a message id.
49
+ * The message may have been delivered, so the key is sealed:
50
+ * a retry under it answers `IDEMPOTENCY_KEY_FAILED` rather than re-sending.
51
+ */
52
+ export const WHATSAPP_SEND_ERROR_CODES = [
53
+ 'VALIDATION_FAILED',
54
+ 'IDEMPOTENCY_KEY_REQUIRED',
55
+ 'PLATFORM_INTEGRATION_DISABLED',
56
+ 'WHATSAPP_NOT_CONNECTED',
57
+ 'WHATSAPP_TOKEN_REJECTED',
58
+ 'CUSTOMER_NOT_FOUND',
59
+ 'WHATSAPP_RECIPIENT_PHONE_MISSING',
60
+ 'WHATSAPP_RECIPIENT_NOT_ALLOWED',
61
+ 'WHATSAPP_TEMPLATE_NOT_APPROVED',
62
+ 'WHATSAPP_RECIPIENT_OPTED_OUT',
63
+ 'WHATSAPP_RATE_LIMITED',
64
+ 'WHATSAPP_ACCOUNT_SUSPENDED',
65
+ 'WHATSAPP_SEND_FAILED',
66
+ 'WHATSAPP_SEND_UNCONFIRMED',
67
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinfactura-types",
3
- "version": "1.10.449",
3
+ "version": "1.10.451",
4
4
  "description": "Shared wire and entity contracts for the SINFACTURA platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",