n8n-nodes-sendzen 1.0.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.
Files changed (44) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +359 -0
  3. package/dist/credentials/SendZenApi.credentials.d.ts +8 -0
  4. package/dist/credentials/SendZenApi.credentials.js +36 -0
  5. package/dist/credentials/SendZenApi.credentials.js.map +1 -0
  6. package/dist/nodes/SendZen/SendZen.node.d.ts +14 -0
  7. package/dist/nodes/SendZen/SendZen.node.js +610 -0
  8. package/dist/nodes/SendZen/SendZen.node.js.map +1 -0
  9. package/dist/nodes/SendZen/SendZen.node.json +20 -0
  10. package/dist/nodes/SendZen/SendZenTrigger.node.d.ts +5 -0
  11. package/dist/nodes/SendZen/SendZenTrigger.node.js +49 -0
  12. package/dist/nodes/SendZen/SendZenTrigger.node.js.map +1 -0
  13. package/dist/nodes/SendZen/sendzen.svg +1 -0
  14. package/dist/package.json +76 -0
  15. package/dist/shared/constants.d.ts +1 -0
  16. package/dist/shared/constants.js +5 -0
  17. package/dist/shared/constants.js.map +1 -0
  18. package/dist/shared/nodeProperties.d.ts +10 -0
  19. package/dist/shared/nodeProperties.js +187 -0
  20. package/dist/shared/nodeProperties.js.map +1 -0
  21. package/dist/shared/template/template.api.types.d.ts +328 -0
  22. package/dist/shared/template/template.api.types.js +3 -0
  23. package/dist/shared/template/template.api.types.js.map +1 -0
  24. package/dist/shared/template/template.builder.d.ts +17 -0
  25. package/dist/shared/template/template.builder.js +200 -0
  26. package/dist/shared/template/template.builder.js.map +1 -0
  27. package/dist/shared/type.d.ts +38 -0
  28. package/dist/shared/type.js +3 -0
  29. package/dist/shared/type.js.map +1 -0
  30. package/dist/shared/utils.d.ts +2 -0
  31. package/dist/shared/utils.js +15 -0
  32. package/dist/shared/utils.js.map +1 -0
  33. package/index.js +10 -0
  34. package/jest.config.js +22 -0
  35. package/n8n-nodes-sendzen-1.0.0.tgz +0 -0
  36. package/nodes/SendZen/SendZen.node.json +20 -0
  37. package/nodes/SendZen/sendzen.svg +1 -0
  38. package/package.json +76 -0
  39. package/shared/constants.ts +1 -0
  40. package/shared/nodeProperties.ts +197 -0
  41. package/shared/template/template.api.types.ts +435 -0
  42. package/shared/template/template.builder.ts +223 -0
  43. package/shared/type.ts +40 -0
  44. package/shared/utils.ts +16 -0
@@ -0,0 +1,435 @@
1
+ // shared/template/template.api.types.ts
2
+
3
+ // Buttons
4
+ export interface UrlButton {
5
+ type: 'URL';
6
+ text: string;
7
+ url: string;
8
+ example?: string[];
9
+ }
10
+
11
+ export interface PhoneNumberButton {
12
+ type: 'PHONE_NUMBER';
13
+ text: string;
14
+ phone_number: string;
15
+ }
16
+
17
+ export interface QuickReplyButton {
18
+ type: 'QUICK_REPLY';
19
+ text: string;
20
+ }
21
+
22
+ export interface CopyCodeButton {
23
+ type: 'COPY_CODE';
24
+ example: string;
25
+ }
26
+
27
+ export type TemplateButton = UrlButton | PhoneNumberButton | QuickReplyButton | CopyCodeButton;
28
+
29
+ // Components
30
+ export interface HeaderTextComponent {
31
+ type: 'HEADER';
32
+ format: 'TEXT';
33
+ text: string;
34
+ example?: {
35
+ header_text: string[];
36
+ }
37
+ }
38
+
39
+ export interface HeaderMediaComponent {
40
+ type: 'HEADER';
41
+ format: 'IMAGE' | 'VIDEO' | 'DOCUMENT';
42
+ example: {
43
+ header_handle: [string];
44
+ }
45
+ }
46
+
47
+ export interface HeaderLocationComponent {
48
+ type: 'HEADER';
49
+ format: 'LOCATION';
50
+ }
51
+
52
+ export interface HeaderProductComponent {
53
+ type: 'HEADER';
54
+ format: 'PRODUCT';
55
+ }
56
+
57
+ export type HeaderComponent = HeaderTextComponent | HeaderMediaComponent | HeaderLocationComponent | HeaderProductComponent;
58
+
59
+ export interface BodyComponent {
60
+ type: 'BODY';
61
+ text: string;
62
+ example?: {
63
+ body_text?: string[][];
64
+ body_text_named_params?: {
65
+ param_name: string;
66
+ example: string;
67
+ }[];
68
+ };
69
+ }
70
+
71
+ export interface FooterComponent {
72
+ type: 'FOOTER';
73
+ text: string;
74
+ }
75
+
76
+ export interface ButtonsComponent {
77
+ type: 'BUTTONS';
78
+ buttons: TemplateButton[];
79
+ }
80
+
81
+ export interface LimitedTimeOfferComponent {
82
+ type: 'LIMITED_TIME_OFFER';
83
+ limited_time_offer: {
84
+ text: string;
85
+ has_expiration?: boolean;
86
+ };
87
+ }
88
+
89
+ // Media Carousel Template
90
+ export type MediaCarouselButton = UrlButton | PhoneNumberButton | QuickReplyButton;
91
+
92
+ export interface MediaCarouselCard {
93
+ components: (
94
+ | {
95
+ type: 'HEADER';
96
+ format: 'IMAGE' | 'VIDEO';
97
+ example: {
98
+ header_handle: [string];
99
+ };
100
+ }
101
+ | {
102
+ type: 'BUTTONS';
103
+ buttons: MediaCarouselButton[];
104
+ }
105
+ )[];
106
+ }
107
+
108
+ // Product Carousel Template
109
+ export interface SPMButton {
110
+ type: 'SPM';
111
+ text: string;
112
+ }
113
+
114
+ export type ProductCarouselButton = SPMButton | UrlButton;
115
+
116
+ export interface ProductCarouselCard {
117
+ components: (
118
+ | HeaderProductComponent
119
+ | {
120
+ type: 'BUTTONS';
121
+ buttons: ProductCarouselButton[];
122
+ }
123
+ )[];
124
+ }
125
+
126
+ // Generic Carousel Component
127
+ export interface CarouselComponent {
128
+ type: 'CAROUSEL';
129
+ cards: (MediaCarouselCard | ProductCarouselCard)[];
130
+ }
131
+
132
+ export type TemplateComponent =
133
+ | HeaderComponent
134
+ | BodyComponent
135
+ | FooterComponent
136
+ | ButtonsComponent
137
+ | LimitedTimeOfferComponent
138
+ | CarouselComponent;
139
+
140
+ // Main Template Interface
141
+ export interface CustomMarketingTemplate {
142
+ name: string;
143
+ language: string;
144
+ category: 'MARKETING';
145
+ parameter_format?: string;
146
+ components: TemplateComponent[];
147
+ }
148
+
149
+ // Catalog Template
150
+ export interface CatalogButton {
151
+ type: 'CATALOG';
152
+ text: string;
153
+ }
154
+
155
+ export type CatalogTemplateComponent = BodyComponent | FooterComponent | {
156
+ type: 'BUTTONS';
157
+ buttons: CatalogButton[];
158
+ };
159
+
160
+ export interface CatalogTemplate {
161
+ name: string;
162
+ language: string;
163
+ category: 'MARKETING';
164
+ components: CatalogTemplateComponent[];
165
+ }
166
+
167
+ // Authentication Template
168
+ export interface AuthBodyComponent {
169
+ type: 'BODY';
170
+ add_security_recommendation?: boolean;
171
+ }
172
+
173
+ export interface AuthFooterComponent {
174
+ type: 'FOOTER';
175
+ code_expiration_minutes?: number;
176
+ }
177
+
178
+ export interface AuthOneTapButton {
179
+ type: 'OTP';
180
+ otp_type: 'one_tap';
181
+ text?: string; // copy_code_button_text
182
+ autofill_text?: string;
183
+ supported_apps: {
184
+ package_name: string;
185
+ signature_hash: string;
186
+ }[];
187
+ }
188
+
189
+ export interface AuthZeroTapButton {
190
+ type: 'OTP';
191
+ otp_type: 'zero_tap';
192
+ text?: string; // copy_code_button_text
193
+ autofill_text?: string;
194
+ zero_tap_terms_accepted: boolean;
195
+ supported_apps: {
196
+ package_name: string;
197
+ signature_hash: string;
198
+ }[];
199
+ }
200
+
201
+ export interface AuthCopyCodeButton {
202
+ type: 'OTP';
203
+ otp_type: 'copy_code';
204
+ text?: string;
205
+ }
206
+
207
+ export type AuthOtpButton = AuthOneTapButton | AuthZeroTapButton | AuthCopyCodeButton;
208
+
209
+ export interface AuthButtonsComponent {
210
+ type: 'BUTTONS';
211
+ buttons: AuthOtpButton[];
212
+ }
213
+
214
+ export type AuthTemplateComponent = AuthBodyComponent | AuthFooterComponent | AuthButtonsComponent;
215
+
216
+ export interface AuthenticationTemplate {
217
+ name: string;
218
+ language: string;
219
+ category: 'AUTHENTICATION';
220
+ message_send_ttl_seconds?: number;
221
+ components: AuthTemplateComponent[];
222
+ }
223
+
224
+ // Call Permission Request Template
225
+ export interface CallPermissionRequestComponent {
226
+ type: 'CALL_PERMISSION_REQUEST';
227
+ }
228
+
229
+ export type CallPermissionRequestTemplateComponent = BodyComponent | CallPermissionRequestComponent;
230
+
231
+ export interface CallPermissionRequestTemplate {
232
+ name: string;
233
+ language: string;
234
+ category: 'MARKETING' | 'UTILITY';
235
+ components: CallPermissionRequestTemplateComponent[];
236
+ }
237
+
238
+ // MPM Template
239
+ export interface MPMButton {
240
+ type: 'MPM';
241
+ text: string;
242
+ }
243
+
244
+ export type MPMTemplateComponent = HeaderTextComponent | BodyComponent | FooterComponent | {
245
+ type: 'BUTTONS';
246
+ buttons: MPMButton[];
247
+ };
248
+
249
+ export interface MPMTemplate {
250
+ name: string;
251
+ language: string;
252
+ category: 'MARKETING';
253
+ components: MPMTemplateComponent[];
254
+ }
255
+
256
+ export type SPMTemplateComponent = HeaderProductComponent | BodyComponent | FooterComponent | {
257
+ type: 'BUTTONS';
258
+ buttons: SPMButton[];
259
+ };
260
+
261
+ // SPM Template
262
+ export interface SPMTemplate {
263
+ name: string;
264
+ language: string;
265
+ category: 'MARKETING';
266
+ components: SPMTemplateComponent[];
267
+ }
268
+
269
+ // Utility Template
270
+ export interface UtilityTemplate {
271
+ name: string;
272
+ language: string;
273
+ category: 'UTILITY';
274
+ parameter_format?: string;
275
+ components: TemplateComponent[];
276
+ }
277
+
278
+ export type CreateTemplatePayload =
279
+ | CustomMarketingTemplate
280
+ | CatalogTemplate
281
+ | AuthenticationTemplate
282
+ | CallPermissionRequestTemplate
283
+ | MPMTemplate
284
+ | SPMTemplate
285
+ | UtilityTemplate;
286
+
287
+ export interface CreateTemplateResponse {
288
+ id: string;
289
+ status: string;
290
+ category: string;
291
+ }
292
+
293
+ // --- GET All Templates Response Types ---
294
+ export interface Paging {
295
+ cursors: {
296
+ before: string;
297
+ after: string;
298
+ };
299
+ next?: string;
300
+ }
301
+
302
+ export interface ResponseUrlButton {
303
+ type: 'URL';
304
+ text: string;
305
+ url: string;
306
+ }
307
+
308
+ export interface ResponsePhoneNumberButton {
309
+ type: 'PHONE_NUMBER';
310
+ text: string;
311
+ phone_number: string;
312
+ }
313
+
314
+ export interface ResponseQuickReplyButton {
315
+ type: 'QUICK_REPLY';
316
+ text: string;
317
+ }
318
+
319
+ export interface ResponseFlowButton {
320
+ type: 'FLOW';
321
+ text: string;
322
+ flow_id: number;
323
+ flow_action: string;
324
+ navigate_screen: string;
325
+ }
326
+
327
+ export interface ResponseSPMButton {
328
+ type: 'SPM';
329
+ text: string;
330
+ }
331
+
332
+ export type ResponseButton = ResponseUrlButton | ResponsePhoneNumberButton | ResponseQuickReplyButton | ResponseFlowButton | ResponseSPMButton;
333
+
334
+ export interface ResponseHeaderTextComponent {
335
+ type: 'HEADER';
336
+ format: 'TEXT';
337
+ text: string;
338
+ example?: {
339
+ header_text: string[];
340
+ };
341
+ }
342
+
343
+ export interface ResponseHeaderMediaComponent {
344
+ type: 'HEADER';
345
+ format: 'IMAGE' | 'VIDEO' | 'DOCUMENT';
346
+ example: {
347
+ header_handle: string[];
348
+ };
349
+ }
350
+
351
+ export interface ResponseHeaderLocationComponent {
352
+ type: 'HEADER';
353
+ format: 'LOCATION';
354
+ }
355
+
356
+ export interface ResponseHeaderProductComponent {
357
+ type: 'HEADER';
358
+ format: 'PRODUCT';
359
+ }
360
+
361
+ export type ResponseHeaderComponent =
362
+ | ResponseHeaderTextComponent
363
+ | ResponseHeaderMediaComponent
364
+ | ResponseHeaderLocationComponent
365
+ | ResponseHeaderProductComponent;
366
+
367
+ export interface ResponseBodyComponent {
368
+ type: 'BODY';
369
+ text: string;
370
+ add_security_recommendation?: boolean;
371
+ example?: {
372
+ body_text?: string[][];
373
+ body_text_named_params?: {
374
+ param_name: string;
375
+ example: string;
376
+ }[];
377
+ };
378
+ }
379
+
380
+ export interface ResponseFooterComponent {
381
+ type: 'FOOTER';
382
+ text: string;
383
+ code_expiration_minutes?: number;
384
+ }
385
+
386
+ export interface ResponseButtonsComponent {
387
+ type: 'BUTTONS';
388
+ buttons: ResponseButton[];
389
+ }
390
+
391
+ export interface ResponseCarouselCard {
392
+ components: (ResponseHeaderComponent | ResponseButtonsComponent)[];
393
+ }
394
+
395
+ export interface ResponseCarouselComponent {
396
+ type: 'CAROUSEL';
397
+ cards: ResponseCarouselCard[];
398
+ }
399
+
400
+ export type ResponseComponent =
401
+ | ResponseHeaderComponent
402
+ | ResponseBodyComponent
403
+ | ResponseFooterComponent
404
+ | ResponseButtonsComponent
405
+ | ResponseCarouselComponent;
406
+
407
+ export interface MessageTemplate {
408
+ id: string;
409
+ name: string;
410
+ components?: ResponseComponent[];
411
+ language: string;
412
+ status: "APPROVED" | "PENDING" | "REJECTED";
413
+ category: "UTILITY" | "MARKETING" | "AUTHENTICATION";
414
+ message_send_ttl_seconds?: number;
415
+ parameter_format?: "named" | "positional";
416
+ sub_category?: string;
417
+ }
418
+
419
+ export interface GetAllTemplatesResponse {
420
+ data: MessageTemplate[];
421
+ paging?: Paging;
422
+ }
423
+
424
+
425
+ export interface TemplatesResponse {
426
+ data?: { data: MessageTemplate[] };
427
+ paging?: {
428
+ cursors: {
429
+ before: string;
430
+ after: string;
431
+ };
432
+ next?: string;
433
+ };
434
+ message?: string;
435
+ }
@@ -0,0 +1,223 @@
1
+ import { MessageTemplate, ResponseBodyComponent, ResponseHeaderComponent, ResponseButtonsComponent, ResponseCarouselComponent } from './template.api.types';
2
+
3
+ export interface TemplatePayloadParams {
4
+ from: string;
5
+ to: string;
6
+ template: MessageTemplate;
7
+ variables: Record<string, string>;
8
+ }
9
+
10
+ /**
11
+ * Builds the SendZen/WhatsApp template payload based on the selected template structure
12
+ * and the variables provided by the user in the n8n Resource Mapper.
13
+ */
14
+ export function buildTemplatePayload(params: TemplatePayloadParams) {
15
+ const { template, variables, from, to } = params;
16
+ const templateComponents: any[] = [];
17
+
18
+ if (!template.components) {
19
+ return {
20
+ from,
21
+ to,
22
+ type: 'template',
23
+ template: {
24
+ name: template.name,
25
+ lang_code: template.language,
26
+ components: [],
27
+ },
28
+ };
29
+ }
30
+
31
+ const processComponent = (component: any, prefix = '') => {
32
+ switch (component.type) {
33
+ case 'HEADER': {
34
+ const header = component as ResponseHeaderComponent;
35
+ if (header.format === 'TEXT' && header.text) {
36
+ const tokens = header.text.match(/\{\{([^}]+)\}\}/g);
37
+ if (tokens && tokens.length > 0) {
38
+ const parameters = tokens.map((match) => {
39
+ const inner = match.replace(/[{}]/g, '');
40
+ const isNumber = /^\d+$/.test(inner);
41
+ const fieldId = `header_param_${inner}`;
42
+ const value = variables[fieldId] || match;
43
+
44
+ return {
45
+ type: 'text',
46
+ text: value,
47
+ ...(isNumber ? {} : { parameter_name: inner }),
48
+ };
49
+ });
50
+
51
+ templateComponents.push({
52
+ type: 'header',
53
+ parameters: parameters,
54
+ });
55
+ }
56
+ } else if (header.format && ['IMAGE', 'VIDEO', 'DOCUMENT'].includes(header.format)) {
57
+ const mediaType = header.format.toLowerCase();
58
+ const mediaUrl = variables['header_media_url'] || '';
59
+
60
+ if (mediaUrl) {
61
+ templateComponents.push({
62
+ type: 'header',
63
+ parameters: [
64
+ {
65
+ type: mediaType,
66
+ [mediaType]: {
67
+ link: mediaUrl,
68
+ },
69
+ },
70
+ ],
71
+ });
72
+ }
73
+ }
74
+ break;
75
+ }
76
+
77
+ case 'BODY': {
78
+ const body = component as ResponseBodyComponent;
79
+ if (body.text) {
80
+ const tokens = body.text.match(/\{\{([^}]+)\}\}/g);
81
+ if (tokens && tokens.length > 0) {
82
+ const parameters = tokens.map((match) => {
83
+ const inner = match.replace(/[{}]/g, '');
84
+ const isNumber = /^\d+$/.test(inner);
85
+ const fieldId = `body_param_${inner}`;
86
+ const value = variables[fieldId] || match;
87
+
88
+ return {
89
+ type: 'text',
90
+ text: value,
91
+ ...(isNumber ? {} : { parameter_name: inner }),
92
+ };
93
+ });
94
+
95
+ templateComponents.push({
96
+ type: 'body',
97
+ parameters: parameters,
98
+ });
99
+ }
100
+ }
101
+ break;
102
+ }
103
+
104
+ case 'BUTTONS': {
105
+ const buttonsComp = component as ResponseButtonsComponent;
106
+ if (buttonsComp.buttons && buttonsComp.buttons.length > 0) {
107
+ buttonsComp.buttons.forEach((button, buttonIndex) => {
108
+ if (button.type === 'URL' && button.url) {
109
+ const urlToken = button.url.match(/\{\{([^}]+)\}\}/);
110
+ if (urlToken && urlToken[1]) {
111
+ const inner = urlToken[1];
112
+ const isNumber = /^\d+$/.test(inner);
113
+ const fieldId = `button_${buttonIndex}_param_${inner}`;
114
+ const value = variables[fieldId] || '123456';
115
+
116
+ templateComponents.push({
117
+ type: 'button',
118
+ sub_type: 'url',
119
+ index: buttonIndex,
120
+ parameters: [
121
+ {
122
+ type: 'text',
123
+ text: value,
124
+ ...(isNumber ? {} : { parameter_name: inner }),
125
+ },
126
+ ],
127
+ });
128
+ }
129
+ } else if ((button.type as any) === 'COPY_CODE') {
130
+ const searchableText = (button as any).text || (button as any).url || '';
131
+ const codeToken = searchableText.match(/\{\{([^}]+)\}\}/);
132
+ if (codeToken && codeToken[1]) {
133
+ const inner = codeToken[1];
134
+ const isNumber = /^\d+$/.test(inner);
135
+ const fieldId = `button_${buttonIndex}_param_${inner}`;
136
+ const value = variables[fieldId] || '123456';
137
+
138
+ templateComponents.push({
139
+ type: 'button',
140
+ sub_type: 'copy_code',
141
+ index: buttonIndex,
142
+ parameters: [
143
+ {
144
+ type: 'text',
145
+ text: value,
146
+ ...(isNumber ? {} : { parameter_name: inner }),
147
+ },
148
+ ],
149
+ });
150
+ }
151
+ } else if (button.type === 'FLOW') {
152
+ const flowButton = button as any;
153
+ const flowAction: {
154
+ flow_token?: string;
155
+ flow_action_data?: any;
156
+ } = {};
157
+
158
+ const flowToken = variables[`button_${buttonIndex}_flow_token`];
159
+ flowAction.flow_token = flowToken || 'unused';
160
+
161
+ const customFlowActionData = variables[`button_${buttonIndex}_flow_action_data`];
162
+ if (customFlowActionData) {
163
+ try {
164
+ flowAction.flow_action_data =
165
+ typeof customFlowActionData === 'string'
166
+ ? JSON.parse(customFlowActionData)
167
+ : customFlowActionData;
168
+ } catch (e) {
169
+ flowAction.flow_action_data = {};
170
+ }
171
+ } else if (flowButton.flow_id || flowButton.flow_action || flowButton.navigate_screen) {
172
+ flowAction.flow_action_data = {};
173
+ if (flowButton.flow_id) flowAction.flow_action_data.flow_id = flowButton.flow_id;
174
+ if (flowButton.flow_action)
175
+ flowAction.flow_action_data.flow_action = flowButton.flow_action;
176
+ if (flowButton.navigate_screen)
177
+ flowAction.flow_action_data.navigate_screen = flowButton.navigate_screen;
178
+ }
179
+
180
+ templateComponents.push({
181
+ type: 'button',
182
+ sub_type: 'flow',
183
+ index: buttonIndex,
184
+ parameters: [
185
+ {
186
+ type: 'action',
187
+ action: flowAction,
188
+ },
189
+ ],
190
+ });
191
+ }
192
+ });
193
+ }
194
+ break;
195
+ }
196
+
197
+ case 'CAROUSEL': {
198
+ const carousel = component as ResponseCarouselComponent;
199
+ if (carousel.cards) {
200
+ carousel.cards.forEach((card, i) => {
201
+ card.components.forEach((cardComp) => {
202
+ processComponent(cardComp, `card_${i + 1}_`);
203
+ });
204
+ });
205
+ }
206
+ break;
207
+ }
208
+ }
209
+ };
210
+
211
+ template.components.forEach((comp) => processComponent(comp));
212
+
213
+ return {
214
+ from,
215
+ to,
216
+ type: 'template',
217
+ template: {
218
+ name: template.name,
219
+ lang_code: template.language,
220
+ components: templateComponents,
221
+ },
222
+ };
223
+ }
package/shared/type.ts ADDED
@@ -0,0 +1,40 @@
1
+ export interface WABAResponseList {
2
+ message: string;
3
+ data: {
4
+ projects: {
5
+ id: number;
6
+ project_name: string;
7
+ client_identifier: string;
8
+ wabas: {
9
+ id: number;
10
+ waba_id: string;
11
+ waba_business_name: string;
12
+ phone_number_id: string;
13
+ phone_number: string;
14
+ number_status: string;
15
+ setup_mode: string;
16
+ mm_lite_api_status: string;
17
+ phone_number_type: string;
18
+ waba_onboard_stage: string;
19
+ }[];
20
+ }[];
21
+ };
22
+ }
23
+
24
+ export interface SendTextMessageRequest {
25
+ from: string;
26
+ to: string;
27
+ type: 'text';
28
+ text: {
29
+ body: string;
30
+ preview_url?: boolean;
31
+ };
32
+ }
33
+
34
+ export type SendzenAPIErroResponse = {
35
+ message: string;
36
+ error: {
37
+ code: string;
38
+ details: string;
39
+ };
40
+ };
@@ -0,0 +1,16 @@
1
+ import { SendzenAPIErroResponse } from "./type";
2
+
3
+ export function isSendzenAPIErroResponse(
4
+ value: unknown
5
+ ): value is SendzenAPIErroResponse {
6
+ return (
7
+ typeof value === 'object' &&
8
+ value !== null &&
9
+ 'message' in value &&
10
+ typeof (value as any).message === 'string' &&
11
+ 'error' in value &&
12
+ typeof (value as any).error === 'object' &&
13
+ typeof (value as any).error?.code === 'string' &&
14
+ typeof (value as any).error?.details === 'string'
15
+ );
16
+ }