@wesell/n8n-nodes-confirmx 0.1.0
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/LICENSE +21 -0
- package/README.md +77 -0
- package/credentials/ConfirmXApi.credentials.d.ts +18 -0
- package/credentials/ConfirmXApi.credentials.js +31 -0
- package/credentials/ConfirmXApi.credentials.js.map +1 -0
- package/credentials/ConfirmXApi.credentials.ts +40 -0
- package/index.d.ts +14 -0
- package/index.js +26 -0
- package/nodes/ConfirmX/ConfirmXAccount.node.d.ts +5 -0
- package/nodes/ConfirmX/ConfirmXAccount.node.js +81 -0
- package/nodes/ConfirmX/ConfirmXAccount.node.js.map +1 -0
- package/nodes/ConfirmX/ConfirmXAccount.node.ts +81 -0
- package/nodes/ConfirmX/ConfirmXConversation.node.d.ts +13 -0
- package/nodes/ConfirmX/ConfirmXConversation.node.js +266 -0
- package/nodes/ConfirmX/ConfirmXConversation.node.js.map +1 -0
- package/nodes/ConfirmX/ConfirmXConversation.node.ts +263 -0
- package/nodes/ConfirmX/ConfirmXMessage.node.d.ts +13 -0
- package/nodes/ConfirmX/ConfirmXMessage.node.js +364 -0
- package/nodes/ConfirmX/ConfirmXMessage.node.js.map +1 -0
- package/nodes/ConfirmX/ConfirmXMessage.node.ts +361 -0
- package/nodes/ConfirmX/ConfirmXShippingZone.node.d.ts +5 -0
- package/nodes/ConfirmX/ConfirmXShippingZone.node.js +100 -0
- package/nodes/ConfirmX/ConfirmXShippingZone.node.js.map +1 -0
- package/nodes/ConfirmX/ConfirmXShippingZone.node.ts +103 -0
- package/nodes/ConfirmX/ConfirmXTemplate.node.d.ts +13 -0
- package/nodes/ConfirmX/ConfirmXTemplate.node.js +310 -0
- package/nodes/ConfirmX/ConfirmXTemplate.node.js.map +1 -0
- package/nodes/ConfirmX/ConfirmXTemplate.node.ts +310 -0
- package/nodes/ConfirmX/ConfirmXTrigger.node.d.ts +29 -0
- package/nodes/ConfirmX/ConfirmXTrigger.node.js +190 -0
- package/nodes/ConfirmX/ConfirmXTrigger.node.js.map +1 -0
- package/nodes/ConfirmX/ConfirmXTrigger.node.ts +245 -0
- package/nodes/ConfirmX/ConfirmXWebhook.node.d.ts +5 -0
- package/nodes/ConfirmX/ConfirmXWebhook.node.js +169 -0
- package/nodes/ConfirmX/ConfirmXWebhook.node.js.map +1 -0
- package/nodes/ConfirmX/ConfirmXWebhook.node.ts +163 -0
- package/nodes/ConfirmX/confirmx.svg +4 -0
- package/package.json +69 -0
- package/transports/http.d.ts +43 -0
- package/transports/http.js +117 -0
- package/transports/http.js.map +1 -0
- package/transports/http.ts +170 -0
- package/transports/signature.d.ts +21 -0
- package/transports/signature.js +50 -0
- package/transports/signature.js.map +1 -0
- package/transports/signature.ts +55 -0
- package/types/api.d.ts +199 -0
- package/types/api.js +21 -0
- package/types/api.js.map +1 -0
- package/types/api.ts +238 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMAC-SHA256 signature verification for inbound ConfirmX webhooks.
|
|
3
|
+
*
|
|
4
|
+
* Must stay byte-compatible with the verifier in
|
|
5
|
+
* apps/worker/src/lib/webhooks.ts::verifyPayload (and the corresponding
|
|
6
|
+
* outbound signer in apps/api/src/lib/webhooks.ts::signPayload). The
|
|
7
|
+
* signing scheme is `${timestamp}.${body}` keyed by the per-webhook
|
|
8
|
+
* plaintext secret; the receiver must also enforce a 300s skew window
|
|
9
|
+
* to defeat replay.
|
|
10
|
+
*/
|
|
11
|
+
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
12
|
+
|
|
13
|
+
export interface VerifyOpts {
|
|
14
|
+
secret: string
|
|
15
|
+
body: string
|
|
16
|
+
timestamp: string
|
|
17
|
+
signatureHeader: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Constant-time verification of the X-ConfirmX-Signature header against
|
|
22
|
+
* HMAC-SHA256(secret, `${timestamp}.${body}`).
|
|
23
|
+
*
|
|
24
|
+
* Returns false on any mismatch, length mismatch, or non-string input.
|
|
25
|
+
* Never throws.
|
|
26
|
+
*/
|
|
27
|
+
export function verifyPayload(opts: VerifyOpts): boolean {
|
|
28
|
+
const { secret, body, timestamp, signatureHeader } = opts
|
|
29
|
+
|
|
30
|
+
if (typeof secret !== 'string' || typeof body !== 'string') return false
|
|
31
|
+
if (typeof timestamp !== 'string' || typeof signatureHeader !== 'string') {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const expected =
|
|
36
|
+
'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex')
|
|
37
|
+
|
|
38
|
+
const expectedBuf = Buffer.from(expected, 'utf8')
|
|
39
|
+
const providedBuf = Buffer.from(signatureHeader, 'utf8')
|
|
40
|
+
if (expectedBuf.length !== providedBuf.length) return false
|
|
41
|
+
return timingSafeEqual(expectedBuf, providedBuf)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns the absolute skew (in seconds) between the supplied timestamp
|
|
46
|
+
* and now. Replay window enforced by ConfirmX is 300s.
|
|
47
|
+
*/
|
|
48
|
+
export function timestampSkewSeconds(timestamp: string, now: number = Date.now()): number {
|
|
49
|
+
const ts = parseInt(timestamp, 10)
|
|
50
|
+
if (!Number.isFinite(ts)) return Number.POSITIVE_INFINITY
|
|
51
|
+
return Math.abs(now / 1000 - ts)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Replay window in seconds — must match the receiver convention. */
|
|
55
|
+
export const REPLAY_WINDOW_SECONDS = 300
|
package/types/api.d.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript types mirroring the ConfirmX /api/v1/* response shapes.
|
|
3
|
+
*
|
|
4
|
+
* Kept intentionally loose (a lot of `any` on optional nested objects)
|
|
5
|
+
* because the runtime data is what matters — these exist for IDE help
|
|
6
|
+
* and downstream type-narrowing in node implementations.
|
|
7
|
+
*
|
|
8
|
+
* Verified against the ConfirmX API v1 as of 2026-06.
|
|
9
|
+
*/
|
|
10
|
+
/** Webhook event types accepted by /api/v1/webhooks. */
|
|
11
|
+
export declare const WEBHOOK_EVENT_TYPES: readonly ["message.received", "conversation.created", "message.sent", "message.status", "template.status"];
|
|
12
|
+
export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
|
|
13
|
+
/** Envelope POSTed to subscribers by ConfirmX. The `data` shape varies by event type. */
|
|
14
|
+
export interface WebhookEnvelope<T = any> {
|
|
15
|
+
id: string;
|
|
16
|
+
type: WebhookEventType;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
organizationId: string;
|
|
19
|
+
data: T;
|
|
20
|
+
}
|
|
21
|
+
export interface Account {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
phoneNumberId?: string;
|
|
25
|
+
businessAccountId?: string;
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
}
|
|
29
|
+
export interface Template {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
language: string;
|
|
33
|
+
status: 'PENDING' | 'APPROVED' | 'REJECTED';
|
|
34
|
+
category: string;
|
|
35
|
+
components: Array<Record<string, any>>;
|
|
36
|
+
}
|
|
37
|
+
export interface ConversationContact {
|
|
38
|
+
id: string;
|
|
39
|
+
phoneNumber: string;
|
|
40
|
+
name?: string | null;
|
|
41
|
+
tags?: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
export interface Conversation {
|
|
47
|
+
id: string;
|
|
48
|
+
whatsappAccountId: string;
|
|
49
|
+
whatsappContactId: string;
|
|
50
|
+
status: 'open' | 'resolved' | 'archived';
|
|
51
|
+
unreadCount: number;
|
|
52
|
+
lastMessageAt: string | null;
|
|
53
|
+
windowExpiresAt: string | null;
|
|
54
|
+
assignedToId: string | null;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
contact?: ConversationContact;
|
|
58
|
+
account?: {
|
|
59
|
+
id: string;
|
|
60
|
+
label: string;
|
|
61
|
+
phoneNumberId?: string;
|
|
62
|
+
};
|
|
63
|
+
/** Latest message preview (list endpoint only embeds one). */
|
|
64
|
+
messages?: Array<Partial<Message>>;
|
|
65
|
+
assignedTo?: {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
} | null;
|
|
69
|
+
}
|
|
70
|
+
export type MessageDirection = 'inbound' | 'outbound';
|
|
71
|
+
export type MessageType = 'text' | 'image' | 'video' | 'document' | 'audio' | 'voice' | 'button' | 'template';
|
|
72
|
+
export interface Message {
|
|
73
|
+
id: string;
|
|
74
|
+
wamid?: string | null;
|
|
75
|
+
whatsappConversationId: string;
|
|
76
|
+
direction: MessageDirection;
|
|
77
|
+
type: MessageType | string;
|
|
78
|
+
body?: string | null;
|
|
79
|
+
mediaUrl?: string | null;
|
|
80
|
+
mediaFilename?: string | null;
|
|
81
|
+
mediaMimeType?: string | null;
|
|
82
|
+
mediaCaption?: string | null;
|
|
83
|
+
replyToMessageId?: string | null;
|
|
84
|
+
status: string;
|
|
85
|
+
sentAt?: string | null;
|
|
86
|
+
deliveredAt?: string | null;
|
|
87
|
+
readAt?: string | null;
|
|
88
|
+
failedAt?: string | null;
|
|
89
|
+
failureReason?: string | null;
|
|
90
|
+
senderType: string;
|
|
91
|
+
sentByUserId?: string | null;
|
|
92
|
+
sentByApiKeyId?: string | null;
|
|
93
|
+
sentByApiLabel?: string | null;
|
|
94
|
+
sentByWorkflowId?: string | null;
|
|
95
|
+
sentByCampaignId?: string | null;
|
|
96
|
+
createdAt: string;
|
|
97
|
+
updatedAt: string;
|
|
98
|
+
replyToMessage?: Pick<Message, 'id' | 'type' | 'body' | 'direction' | 'mediaFilename' | 'mediaMimeType'> | null;
|
|
99
|
+
sentByUser?: {
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
} | null;
|
|
103
|
+
sentByWorkflow?: {
|
|
104
|
+
id: string;
|
|
105
|
+
name: string;
|
|
106
|
+
} | null;
|
|
107
|
+
sentByCampaign?: {
|
|
108
|
+
id: string;
|
|
109
|
+
name: string;
|
|
110
|
+
} | null;
|
|
111
|
+
}
|
|
112
|
+
export interface ListMessagesResult {
|
|
113
|
+
messages: Message[];
|
|
114
|
+
nextCursor: string | null;
|
|
115
|
+
}
|
|
116
|
+
export interface WebhookSubscription {
|
|
117
|
+
id: string;
|
|
118
|
+
url: string;
|
|
119
|
+
events: WebhookEventType[];
|
|
120
|
+
isActive: boolean;
|
|
121
|
+
description: string | null;
|
|
122
|
+
failureCount: number;
|
|
123
|
+
lastDeliveredAt: string | null;
|
|
124
|
+
lastError: string | null;
|
|
125
|
+
createdAt: string;
|
|
126
|
+
}
|
|
127
|
+
export interface WebhookCreateResponse {
|
|
128
|
+
webhook: WebhookSubscription;
|
|
129
|
+
/** Plaintext signing secret — returned ONLY at create time. */
|
|
130
|
+
secret: string;
|
|
131
|
+
secretNote: string;
|
|
132
|
+
}
|
|
133
|
+
export interface MatchedZoneSuccess {
|
|
134
|
+
success: true;
|
|
135
|
+
governorate: {
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
externalId: string | null;
|
|
139
|
+
confidence: number;
|
|
140
|
+
};
|
|
141
|
+
area: {
|
|
142
|
+
id: string;
|
|
143
|
+
name: string;
|
|
144
|
+
externalId: string | null;
|
|
145
|
+
confidence: number;
|
|
146
|
+
} | null;
|
|
147
|
+
governorateCorrected?: boolean;
|
|
148
|
+
availableAreas?: string[];
|
|
149
|
+
}
|
|
150
|
+
export interface MatchedZoneGovernorateMissing {
|
|
151
|
+
success: false;
|
|
152
|
+
matched: false;
|
|
153
|
+
error: string;
|
|
154
|
+
input: {
|
|
155
|
+
governorate: string;
|
|
156
|
+
area?: string;
|
|
157
|
+
};
|
|
158
|
+
suggestions: string[];
|
|
159
|
+
}
|
|
160
|
+
export interface MatchedZoneAmbiguous {
|
|
161
|
+
success: false;
|
|
162
|
+
multipleMatches: true;
|
|
163
|
+
areaName: string;
|
|
164
|
+
options: Array<{
|
|
165
|
+
governorateId: string;
|
|
166
|
+
governorateName: string;
|
|
167
|
+
governorateExternalId: string | null;
|
|
168
|
+
areaId: string;
|
|
169
|
+
areaName: string;
|
|
170
|
+
areaExternalId: string | null;
|
|
171
|
+
confidence: number;
|
|
172
|
+
}>;
|
|
173
|
+
message: string;
|
|
174
|
+
}
|
|
175
|
+
export interface MatchedZoneError {
|
|
176
|
+
success: false;
|
|
177
|
+
error: string;
|
|
178
|
+
}
|
|
179
|
+
export type MatchedZone = MatchedZoneSuccess | MatchedZoneGovernorateMissing | MatchedZoneAmbiguous | MatchedZoneError;
|
|
180
|
+
export interface GovernorateTreeNode {
|
|
181
|
+
id: string;
|
|
182
|
+
name: string;
|
|
183
|
+
nameEn: string | null;
|
|
184
|
+
aliases: string[];
|
|
185
|
+
externalId: string | null;
|
|
186
|
+
sortOrder: number;
|
|
187
|
+
areas: Array<{
|
|
188
|
+
id: string;
|
|
189
|
+
governorateId: string;
|
|
190
|
+
name: string;
|
|
191
|
+
nameEn: string | null;
|
|
192
|
+
aliases: string[];
|
|
193
|
+
externalId: string | null;
|
|
194
|
+
sortOrder: number;
|
|
195
|
+
}>;
|
|
196
|
+
}
|
|
197
|
+
export interface ListGovernoratesResult {
|
|
198
|
+
governorates: GovernorateTreeNode[];
|
|
199
|
+
}
|
package/types/api.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript types mirroring the ConfirmX /api/v1/* response shapes.
|
|
4
|
+
*
|
|
5
|
+
* Kept intentionally loose (a lot of `any` on optional nested objects)
|
|
6
|
+
* because the runtime data is what matters — these exist for IDE help
|
|
7
|
+
* and downstream type-narrowing in node implementations.
|
|
8
|
+
*
|
|
9
|
+
* Verified against the ConfirmX API v1 as of 2026-06.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WEBHOOK_EVENT_TYPES = void 0;
|
|
13
|
+
/** Webhook event types accepted by /api/v1/webhooks. */
|
|
14
|
+
exports.WEBHOOK_EVENT_TYPES = [
|
|
15
|
+
'message.received',
|
|
16
|
+
'conversation.created',
|
|
17
|
+
'message.sent',
|
|
18
|
+
'message.status',
|
|
19
|
+
'template.status',
|
|
20
|
+
];
|
|
21
|
+
//# sourceMappingURL=api.js.map
|
package/types/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,wDAAwD;AAC3C,QAAA,mBAAmB,GAAG;IACjC,kBAAkB;IAClB,sBAAsB;IACtB,cAAc;IACd,gBAAgB;IAChB,iBAAiB;CACT,CAAA"}
|
package/types/api.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript types mirroring the ConfirmX /api/v1/* response shapes.
|
|
3
|
+
*
|
|
4
|
+
* Kept intentionally loose (a lot of `any` on optional nested objects)
|
|
5
|
+
* because the runtime data is what matters — these exist for IDE help
|
|
6
|
+
* and downstream type-narrowing in node implementations.
|
|
7
|
+
*
|
|
8
|
+
* Verified against the ConfirmX API v1 as of 2026-06.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Webhook event types accepted by /api/v1/webhooks. */
|
|
12
|
+
export const WEBHOOK_EVENT_TYPES = [
|
|
13
|
+
'message.received',
|
|
14
|
+
'conversation.created',
|
|
15
|
+
'message.sent',
|
|
16
|
+
'message.status',
|
|
17
|
+
'template.status',
|
|
18
|
+
] as const
|
|
19
|
+
|
|
20
|
+
export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number]
|
|
21
|
+
|
|
22
|
+
/** Envelope POSTed to subscribers by ConfirmX. The `data` shape varies by event type. */
|
|
23
|
+
export interface WebhookEnvelope<T = any> {
|
|
24
|
+
id: string
|
|
25
|
+
type: WebhookEventType
|
|
26
|
+
createdAt: string
|
|
27
|
+
organizationId: string
|
|
28
|
+
data: T
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================
|
|
32
|
+
// Account
|
|
33
|
+
// ============================================
|
|
34
|
+
|
|
35
|
+
export interface Account {
|
|
36
|
+
id: string
|
|
37
|
+
label: string
|
|
38
|
+
phoneNumberId?: string
|
|
39
|
+
businessAccountId?: string
|
|
40
|
+
isActive: boolean
|
|
41
|
+
createdAt: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// Template
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
export interface Template {
|
|
49
|
+
id: string
|
|
50
|
+
name: string
|
|
51
|
+
language: string
|
|
52
|
+
status: 'PENDING' | 'APPROVED' | 'REJECTED'
|
|
53
|
+
category: string
|
|
54
|
+
components: Array<Record<string, any>>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ============================================
|
|
58
|
+
// Conversation
|
|
59
|
+
// ============================================
|
|
60
|
+
|
|
61
|
+
export interface ConversationContact {
|
|
62
|
+
id: string
|
|
63
|
+
phoneNumber: string
|
|
64
|
+
name?: string | null
|
|
65
|
+
tags?: Array<{ id: string; name: string }>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Conversation {
|
|
69
|
+
id: string
|
|
70
|
+
whatsappAccountId: string
|
|
71
|
+
whatsappContactId: string
|
|
72
|
+
status: 'open' | 'resolved' | 'archived'
|
|
73
|
+
unreadCount: number
|
|
74
|
+
lastMessageAt: string | null
|
|
75
|
+
windowExpiresAt: string | null
|
|
76
|
+
assignedToId: string | null
|
|
77
|
+
createdAt: string
|
|
78
|
+
updatedAt: string
|
|
79
|
+
contact?: ConversationContact
|
|
80
|
+
account?: { id: string; label: string; phoneNumberId?: string }
|
|
81
|
+
/** Latest message preview (list endpoint only embeds one). */
|
|
82
|
+
messages?: Array<Partial<Message>>
|
|
83
|
+
assignedTo?: { id: string; name: string } | null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================
|
|
87
|
+
// Message
|
|
88
|
+
// ============================================
|
|
89
|
+
|
|
90
|
+
export type MessageDirection = 'inbound' | 'outbound'
|
|
91
|
+
export type MessageType =
|
|
92
|
+
| 'text'
|
|
93
|
+
| 'image'
|
|
94
|
+
| 'video'
|
|
95
|
+
| 'document'
|
|
96
|
+
| 'audio'
|
|
97
|
+
| 'voice'
|
|
98
|
+
| 'button'
|
|
99
|
+
| 'template'
|
|
100
|
+
|
|
101
|
+
export interface Message {
|
|
102
|
+
id: string
|
|
103
|
+
wamid?: string | null
|
|
104
|
+
whatsappConversationId: string
|
|
105
|
+
direction: MessageDirection
|
|
106
|
+
type: MessageType | string
|
|
107
|
+
body?: string | null
|
|
108
|
+
mediaUrl?: string | null
|
|
109
|
+
mediaFilename?: string | null
|
|
110
|
+
mediaMimeType?: string | null
|
|
111
|
+
mediaCaption?: string | null
|
|
112
|
+
replyToMessageId?: string | null
|
|
113
|
+
status: string
|
|
114
|
+
sentAt?: string | null
|
|
115
|
+
deliveredAt?: string | null
|
|
116
|
+
readAt?: string | null
|
|
117
|
+
failedAt?: string | null
|
|
118
|
+
failureReason?: string | null
|
|
119
|
+
senderType: string
|
|
120
|
+
sentByUserId?: string | null
|
|
121
|
+
sentByApiKeyId?: string | null
|
|
122
|
+
sentByApiLabel?: string | null
|
|
123
|
+
sentByWorkflowId?: string | null
|
|
124
|
+
sentByCampaignId?: string | null
|
|
125
|
+
createdAt: string
|
|
126
|
+
updatedAt: string
|
|
127
|
+
replyToMessage?: Pick<Message, 'id' | 'type' | 'body' | 'direction' | 'mediaFilename' | 'mediaMimeType'> | null
|
|
128
|
+
sentByUser?: { id: string; name: string } | null
|
|
129
|
+
sentByWorkflow?: { id: string; name: string } | null
|
|
130
|
+
sentByCampaign?: { id: string; name: string } | null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ListMessagesResult {
|
|
134
|
+
messages: Message[]
|
|
135
|
+
nextCursor: string | null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ============================================
|
|
139
|
+
// Webhook subscription
|
|
140
|
+
// ============================================
|
|
141
|
+
|
|
142
|
+
export interface WebhookSubscription {
|
|
143
|
+
id: string
|
|
144
|
+
url: string
|
|
145
|
+
events: WebhookEventType[]
|
|
146
|
+
isActive: boolean
|
|
147
|
+
description: string | null
|
|
148
|
+
failureCount: number
|
|
149
|
+
lastDeliveredAt: string | null
|
|
150
|
+
lastError: string | null
|
|
151
|
+
createdAt: string
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface WebhookCreateResponse {
|
|
155
|
+
webhook: WebhookSubscription
|
|
156
|
+
/** Plaintext signing secret — returned ONLY at create time. */
|
|
157
|
+
secret: string
|
|
158
|
+
secretNote: string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================
|
|
162
|
+
// Shipping zone matcher
|
|
163
|
+
// ============================================
|
|
164
|
+
|
|
165
|
+
export interface MatchedZoneSuccess {
|
|
166
|
+
success: true
|
|
167
|
+
governorate: {
|
|
168
|
+
id: string
|
|
169
|
+
name: string
|
|
170
|
+
externalId: string | null
|
|
171
|
+
confidence: number
|
|
172
|
+
}
|
|
173
|
+
area: {
|
|
174
|
+
id: string
|
|
175
|
+
name: string
|
|
176
|
+
externalId: string | null
|
|
177
|
+
confidence: number
|
|
178
|
+
} | null
|
|
179
|
+
governorateCorrected?: boolean
|
|
180
|
+
availableAreas?: string[]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface MatchedZoneGovernorateMissing {
|
|
184
|
+
success: false
|
|
185
|
+
matched: false
|
|
186
|
+
error: string
|
|
187
|
+
input: { governorate: string; area?: string }
|
|
188
|
+
suggestions: string[]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface MatchedZoneAmbiguous {
|
|
192
|
+
success: false
|
|
193
|
+
multipleMatches: true
|
|
194
|
+
areaName: string
|
|
195
|
+
options: Array<{
|
|
196
|
+
governorateId: string
|
|
197
|
+
governorateName: string
|
|
198
|
+
governorateExternalId: string | null
|
|
199
|
+
areaId: string
|
|
200
|
+
areaName: string
|
|
201
|
+
areaExternalId: string | null
|
|
202
|
+
confidence: number
|
|
203
|
+
}>
|
|
204
|
+
message: string
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface MatchedZoneError {
|
|
208
|
+
success: false
|
|
209
|
+
error: string
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export type MatchedZone =
|
|
213
|
+
| MatchedZoneSuccess
|
|
214
|
+
| MatchedZoneGovernorateMissing
|
|
215
|
+
| MatchedZoneAmbiguous
|
|
216
|
+
| MatchedZoneError
|
|
217
|
+
|
|
218
|
+
export interface GovernorateTreeNode {
|
|
219
|
+
id: string
|
|
220
|
+
name: string
|
|
221
|
+
nameEn: string | null
|
|
222
|
+
aliases: string[]
|
|
223
|
+
externalId: string | null
|
|
224
|
+
sortOrder: number
|
|
225
|
+
areas: Array<{
|
|
226
|
+
id: string
|
|
227
|
+
governorateId: string
|
|
228
|
+
name: string
|
|
229
|
+
nameEn: string | null
|
|
230
|
+
aliases: string[]
|
|
231
|
+
externalId: string | null
|
|
232
|
+
sortOrder: number
|
|
233
|
+
}>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface ListGovernoratesResult {
|
|
237
|
+
governorates: GovernorateTreeNode[]
|
|
238
|
+
}
|