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 +41 -0
- package/dist/ai.d.ts +17 -0
- package/dist/api.d.ts +8 -2
- package/dist/cjs/whatsapp.js +55 -1
- package/dist/customer.d.ts +50 -0
- package/dist/subscription.d.ts +1 -1
- package/dist/webhook.d.ts +72 -0
- package/dist/whatsapp.d.ts +96 -1
- package/dist/whatsapp.js +54 -2
- package/package.json +1 -1
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
|
-
*
|
|
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.
|
package/dist/cjs/whatsapp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Published WhatsApp surface — the tenant-facing connection config
|
|
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
|
+
];
|
package/dist/customer.d.ts
CHANGED
|
@@ -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.
|
package/dist/subscription.d.ts
CHANGED
|
@@ -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.
|
package/dist/whatsapp.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Published WhatsApp surface — the tenant-facing connection config
|
|
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
|
|
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
|
-
|
|
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
|
+
];
|