@solidxai/core 0.1.10-beta.15 → 0.1.10-beta.21

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 (116) hide show
  1. package/dist/commands/test-data.command.d.ts +2 -0
  2. package/dist/commands/test-data.command.d.ts.map +1 -1
  3. package/dist/commands/test-data.command.js +32 -6
  4. package/dist/commands/test-data.command.js.map +1 -1
  5. package/dist/controllers/chatter-message.controller.d.ts +2 -0
  6. package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
  7. package/dist/controllers/chatter-message.controller.js +17 -0
  8. package/dist/controllers/chatter-message.controller.js.map +1 -1
  9. package/dist/controllers/gupshup-webhook.controller.d.ts +11 -0
  10. package/dist/controllers/gupshup-webhook.controller.d.ts.map +1 -0
  11. package/dist/controllers/gupshup-webhook.controller.js +87 -0
  12. package/dist/controllers/gupshup-webhook.controller.js.map +1 -0
  13. package/dist/controllers/meta-cloud-whatsapp-webhook.controller.d.ts +16 -0
  14. package/dist/controllers/meta-cloud-whatsapp-webhook.controller.d.ts.map +1 -0
  15. package/dist/controllers/meta-cloud-whatsapp-webhook.controller.js +133 -0
  16. package/dist/controllers/meta-cloud-whatsapp-webhook.controller.js.map +1 -0
  17. package/dist/dtos/update-chatter-note-message.dto.d.ts +5 -0
  18. package/dist/dtos/update-chatter-note-message.dto.d.ts.map +1 -0
  19. package/dist/dtos/update-chatter-note-message.dto.js +34 -0
  20. package/dist/dtos/update-chatter-note-message.dto.js.map +1 -0
  21. package/dist/factories/whatsapp.factory.d.ts.map +1 -1
  22. package/dist/factories/whatsapp.factory.js +13 -11
  23. package/dist/factories/whatsapp.factory.js.map +1 -1
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/interfaces.d.ts +1 -0
  29. package/dist/interfaces.d.ts.map +1 -1
  30. package/dist/interfaces.js.map +1 -1
  31. package/dist/listeners/user-registration.listener.d.ts +5 -2
  32. package/dist/listeners/user-registration.listener.d.ts.map +1 -1
  33. package/dist/listeners/user-registration.listener.js +37 -4
  34. package/dist/listeners/user-registration.listener.js.map +1 -1
  35. package/dist/seeders/module-test-data.service.d.ts +2 -0
  36. package/dist/seeders/module-test-data.service.d.ts.map +1 -1
  37. package/dist/seeders/module-test-data.service.js +61 -0
  38. package/dist/seeders/module-test-data.service.js.map +1 -1
  39. package/dist/seeders/seed-data/solid-core-metadata.json +1 -0
  40. package/dist/services/authentication.service.d.ts +6 -1
  41. package/dist/services/authentication.service.d.ts.map +1 -1
  42. package/dist/services/authentication.service.js +144 -14
  43. package/dist/services/authentication.service.js.map +1 -1
  44. package/dist/services/chatter-message.service.d.ts +7 -1
  45. package/dist/services/chatter-message.service.d.ts.map +1 -1
  46. package/dist/services/chatter-message.service.js +94 -2
  47. package/dist/services/chatter-message.service.js.map +1 -1
  48. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts +1 -0
  49. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
  50. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +11 -0
  51. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  52. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts +1 -0
  53. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  54. package/dist/services/mediaStorageProviders/file-storage-provider.js +6 -0
  55. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  56. package/dist/services/settings/default-settings-provider.service.d.ts +174 -0
  57. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  58. package/dist/services/settings/default-settings-provider.service.js +98 -0
  59. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  60. package/dist/services/user.service.js +1 -1
  61. package/dist/services/user.service.js.map +1 -1
  62. package/dist/services/whatsapp/GupshupOtpWhatsappService.d.ts +11 -0
  63. package/dist/services/whatsapp/GupshupOtpWhatsappService.d.ts.map +1 -0
  64. package/dist/services/whatsapp/GupshupOtpWhatsappService.js +127 -0
  65. package/dist/services/whatsapp/GupshupOtpWhatsappService.js.map +1 -0
  66. package/dist/services/whatsapp/MetaCloudWhatsappService.d.ts +17 -0
  67. package/dist/services/whatsapp/MetaCloudWhatsappService.d.ts.map +1 -0
  68. package/dist/services/whatsapp/MetaCloudWhatsappService.js +209 -0
  69. package/dist/services/whatsapp/MetaCloudWhatsappService.js.map +1 -0
  70. package/dist/solid-core.module.d.ts +2 -2
  71. package/dist/solid-core.module.d.ts.map +1 -1
  72. package/dist/solid-core.module.js +14 -6
  73. package/dist/solid-core.module.js.map +1 -1
  74. package/dist-tests/api/authenticate.spec.js +119 -0
  75. package/dist-tests/api/authenticate.spec.js.map +1 -0
  76. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
  77. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
  78. package/dist-tests/api/ping.spec.js +21 -0
  79. package/dist-tests/api/ping.spec.js.map +1 -0
  80. package/dist-tests/helpers/auth.js +41 -0
  81. package/dist-tests/helpers/auth.js.map +1 -0
  82. package/dist-tests/helpers/env.js +11 -0
  83. package/dist-tests/helpers/env.js.map +1 -0
  84. package/docs/grouping-enhancements.md +89 -0
  85. package/docs/java-spring/README.md +3 -0
  86. package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
  87. package/docs/seed-changes.md +65 -0
  88. package/docs/test-data-workflow.md +200 -0
  89. package/docs/type-declaration-import-issue.md +24 -0
  90. package/package.json +1 -1
  91. package/src/commands/test-data.command.ts +29 -6
  92. package/src/controllers/chatter-message.controller.ts +12 -0
  93. package/src/controllers/gupshup-webhook.controller.ts +91 -0
  94. package/src/controllers/meta-cloud-whatsapp-webhook.controller.ts +155 -0
  95. package/src/dtos/update-chatter-note-message.dto.ts +14 -0
  96. package/src/factories/whatsapp.factory.ts +33 -32
  97. package/src/helpers/field-crud-managers/field-quality-check-fixes.md +178 -0
  98. package/src/index.ts +2 -0
  99. package/src/interfaces.ts +1 -1
  100. package/src/listeners/user-registration.listener.ts +49 -6
  101. package/src/seeders/module-test-data.service.ts +79 -2
  102. package/src/seeders/seed-data/solid-core-metadata.json +1 -0
  103. package/src/services/authentication.service.ts +281 -46
  104. package/src/services/chatter-message.service.ts +112 -2
  105. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +12 -0
  106. package/src/services/mediaStorageProviders/file-storage-provider.ts +8 -1
  107. package/src/services/settings/default-settings-provider.service.ts +98 -0
  108. package/src/services/user.service.ts +1 -1
  109. package/src/services/whatsapp/GupshupOtpWhatsappService.ts +174 -0
  110. package/src/services/whatsapp/MetaCloudWhatsappService.ts +253 -0
  111. package/src/solid-core.module.ts +205 -196
  112. package/src/testing/README.md +15 -0
  113. package/.claude/settings.local.json +0 -15
  114. package/CLAUDE.md +0 -18
  115. package/CURRENT_PROMPT +0 -6
  116. package/src/services/1.js +0 -6
@@ -779,6 +779,46 @@ const getSolidCoreSettings = (isProd: boolean) =>
779
779
  value: parseInt(process.env.IAM_OTP_EXPIRY ?? "10"),
780
780
  level: SettingLevel.SystemEnv,
781
781
  },
782
+ {
783
+ moduleName: "solid-core",
784
+ key: "otpWhatsappFallbackEnabled",
785
+ value: (process.env.IAM_OTP_WHATSAPP_FALLBACK_ENABLED ?? "true") === "true",
786
+ level: SettingLevel.SystemAdminEditable,
787
+ label: "OTP WhatsApp Fallback Enabled",
788
+ group: "authentication-settings",
789
+ sortOrder: 85,
790
+ controlType: "boolean",
791
+ },
792
+ {
793
+ moduleName: "solid-core",
794
+ key: "otpWhatsappTemplateId",
795
+ value: process.env.IAM_OTP_WHATSAPP_TEMPLATE_ID ?? "common_otp",
796
+ level: SettingLevel.SystemAdminEditable,
797
+ label: "OTP WhatsApp Template ID",
798
+ group: "authentication-settings",
799
+ sortOrder: 86,
800
+ controlType: "shortText",
801
+ },
802
+ {
803
+ moduleName: "solid-core",
804
+ key: "otpWhatsappIndependentEnabled",
805
+ value: (process.env.IAM_OTP_WHATSAPP_INDEPENDENT_ENABLED ?? "true") === "true",
806
+ level: SettingLevel.SystemAdminEditable,
807
+ label: "OTP WhatsApp Independent Enabled",
808
+ group: "authentication-settings",
809
+ sortOrder: 87,
810
+ controlType: "boolean",
811
+ },
812
+ {
813
+ moduleName: "solid-core",
814
+ key: "otpDefaultCountryDialCode",
815
+ value: process.env.IAM_OTP_DEFAULT_COUNTRY_DIAL_CODE ?? "",
816
+ level: SettingLevel.SystemAdminEditable,
817
+ label: "OTP Default Country Dial Code",
818
+ group: "authentication-settings",
819
+ sortOrder: 88,
820
+ controlType: "shortText",
821
+ },
782
822
  {
783
823
  moduleName: "solid-core",
784
824
  key: "forgotPasswordVerificationTokenExpiry",
@@ -1178,6 +1218,64 @@ const getSolidCoreSettings = (isProd: boolean) =>
1178
1218
  sortOrder: 30,
1179
1219
  controlType: "shortText",
1180
1220
  },
1221
+ {
1222
+ moduleName: "solid-core",
1223
+ key: "metaWhatsappApiUrl",
1224
+ value: process.env.COMMON_META_WHATSAPP_API_URL || "https://graph.facebook.com",
1225
+ level: SettingLevel.SystemAdminReadonly,
1226
+ label: "Meta WhatsApp API URL",
1227
+ group: "whatsapp-settings",
1228
+ sortOrder: 40,
1229
+ controlType: "shortText",
1230
+ },
1231
+ {
1232
+ moduleName: "solid-core",
1233
+ key: "metaWhatsappApiVersion",
1234
+ value: process.env.COMMON_META_WHATSAPP_API_VERSION || "v23.0",
1235
+ level: SettingLevel.SystemAdminReadonly,
1236
+ label: "Meta WhatsApp API Version",
1237
+ group: "whatsapp-settings",
1238
+ sortOrder: 50,
1239
+ controlType: "shortText",
1240
+ },
1241
+ {
1242
+ moduleName: "solid-core",
1243
+ key: "metaWhatsappPhoneNumberId",
1244
+ value: process.env.COMMON_META_WHATSAPP_PHONE_NUMBER_ID,
1245
+ level: SettingLevel.SystemAdminReadonly,
1246
+ label: "Meta WhatsApp Phone Number ID",
1247
+ group: "whatsapp-settings",
1248
+ sortOrder: 60,
1249
+ controlType: "shortText",
1250
+ },
1251
+ {
1252
+ moduleName: "solid-core",
1253
+ key: "metaWhatsappBusinessAccountId",
1254
+ value: process.env.COMMON_META_WHATSAPP_BUSINESS_ACCOUNT_ID,
1255
+ level: SettingLevel.SystemAdminReadonly,
1256
+ label: "Meta WhatsApp Business Account ID",
1257
+ group: "whatsapp-settings",
1258
+ sortOrder: 70,
1259
+ controlType: "shortText",
1260
+ },
1261
+ {
1262
+ moduleName: "solid-core",
1263
+ key: "metaWhatsappAccessToken",
1264
+ value: process.env.COMMON_META_WHATSAPP_ACCESS_TOKEN,
1265
+ level: SettingLevel.SystemEnv,
1266
+ },
1267
+ {
1268
+ moduleName: "solid-core",
1269
+ key: "metaWhatsappWebhookVerifyToken",
1270
+ value: process.env.COMMON_META_WHATSAPP_WEBHOOK_VERIFY_TOKEN,
1271
+ level: SettingLevel.SystemEnv,
1272
+ },
1273
+ {
1274
+ moduleName: "solid-core",
1275
+ key: "metaWhatsappAppSecret",
1276
+ value: process.env.COMMON_META_WHATSAPP_APP_SECRET,
1277
+ level: SettingLevel.SystemEnv,
1278
+ },
1181
1279
  ] as const satisfies SettingDefinition[];
1182
1280
 
1183
1281
  // 2.
@@ -285,7 +285,7 @@ export class UserService extends CRUDService<User> {
285
285
  }
286
286
 
287
287
  async resolveUserOnOauthFacebook(oauthUserDto: OauthUserDto): Promise<User> {
288
- const normalizedEmail = oauthUserDto.email?.trim().toLowerCase() || null;
288
+ const normalizedEmail = oauthUserDto.email?.trim().toLowerCase();
289
289
  let user: User | null = null;
290
290
 
291
291
  if (oauthUserDto.providerId) {
@@ -0,0 +1,174 @@
1
+ import { HttpService } from "@nestjs/axios";
2
+ import { Injectable, Logger } from "@nestjs/common";
3
+ import { AxiosError } from "axios";
4
+ import { WhatsAppProvider } from "src/decorators/whatsapp-provider.decorator";
5
+ import { IWhatsAppTransport } from "src/interfaces";
6
+
7
+ @Injectable()
8
+ @WhatsAppProvider()
9
+ export class GupshupOtpWhatsappService implements IWhatsAppTransport {
10
+ private readonly logger = new Logger(GupshupOtpWhatsappService.name);
11
+
12
+ constructor(private readonly httpService: HttpService) {}
13
+
14
+ async sendWhatsAppMessage(
15
+ to: string,
16
+ templateId: string,
17
+ parameters: any,
18
+ parentEntity?: any,
19
+ parentEntityId?: any,
20
+ ): Promise<any> {
21
+ if (!to) {
22
+ throw new Error("WhatsApp destination number is required");
23
+ }
24
+
25
+ const payload = parameters?.payload;
26
+ if (payload?.message?.type === "text" && payload?.message?.text) {
27
+ await this.sendTextMessage(
28
+ payload.destination || to,
29
+ payload.message.text,
30
+ payload.source,
31
+ payload["src.name"],
32
+ );
33
+ return { to, templateId, parameters, parentEntity, parentEntityId };
34
+ }
35
+
36
+ if (parameters?.type === "text" && parameters?.text) {
37
+ await this.sendTextMessage(to, parameters.text);
38
+ return { to, templateId, parameters, parentEntity, parentEntityId };
39
+ }
40
+
41
+ if (!templateId) {
42
+ throw new Error("WhatsApp templateId is required for template message");
43
+ }
44
+
45
+ const bodyParams = Array.isArray(parameters)
46
+ ? parameters
47
+ : Array.isArray(parameters?.body)
48
+ ? parameters.body
49
+ : [];
50
+
51
+ await this.sendOtpTemplate(to, templateId, bodyParams.map((x) => String(x)));
52
+ return { to, templateId, parameters, parentEntity, parentEntityId };
53
+ }
54
+
55
+ private async sendTextMessage(
56
+ destination: string,
57
+ text: string,
58
+ sourceOverride?: string,
59
+ appNameOverride?: string,
60
+ ): Promise<void> {
61
+ const apiKey =
62
+ process.env.COMMON_GUPSHUP_WHATSAPP_API_KEY ||
63
+ process.env.GUPSHUP_API_KEY;
64
+ const msgUrl =
65
+ process.env.COMMON_GUPSHUP_WHATSAPP_API_URL ||
66
+ process.env.GUPSHUP_API_URL ||
67
+ "https://api.gupshup.io/wa/api/v1/msg";
68
+ const source =
69
+ sourceOverride ||
70
+ process.env.COMMON_GUPSHUP_WHATSAPP_SOURCE ||
71
+ process.env.GUPSHUP_SOURCE_NUMBER;
72
+ const appName =
73
+ appNameOverride || process.env.COMMON_GUPSHUP_APP_NAME || "solidx";
74
+
75
+ if (!apiKey || !msgUrl || !source) {
76
+ throw new Error("Missing Gupshup WhatsApp configuration for text message");
77
+ }
78
+
79
+ const rawDestination = destination.replace(/\D/g, "");
80
+ const params = new URLSearchParams();
81
+ params.append("channel", "whatsapp");
82
+ params.append("source", source);
83
+ params.append("destination", rawDestination);
84
+ params.append("src.name", appName);
85
+ params.append("message", JSON.stringify({ type: "text", text }));
86
+
87
+ await this.httpService.axiosRef.post(msgUrl, params.toString(), {
88
+ headers: {
89
+ apikey: apiKey,
90
+ "Content-Type": "application/x-www-form-urlencoded",
91
+ },
92
+ });
93
+ }
94
+
95
+ async sendOtpTemplate(
96
+ destination: string,
97
+ templateId: string,
98
+ bodyParams: string[],
99
+ ): Promise<void> {
100
+ const apiKey =
101
+ process.env.COMMON_GUPSHUP_WHATSAPP_API_KEY ||
102
+ process.env.GUPSHUP_API_KEY;
103
+ const baseUrl =
104
+ process.env.COMMON_GUPSHUP_WHATSAPP_API_URL ||
105
+ process.env.GUPSHUP_API_URL ||
106
+ "https://api.gupshup.io/wa/api/v1/msg";
107
+ const templateUrl =
108
+ process.env.COMMON_GUPSHUP_WHATSAPP_TEMPLATE_API_URL ||
109
+ baseUrl.replace(/\/msg$/, "/template/msg");
110
+ const source =
111
+ process.env.COMMON_GUPSHUP_WHATSAPP_SOURCE ||
112
+ process.env.GUPSHUP_SOURCE_NUMBER;
113
+ const appName = process.env.COMMON_GUPSHUP_APP_NAME || "solidx";
114
+
115
+ if (!apiKey || !templateUrl || !source) {
116
+ throw new Error("Missing Gupshup OTP WhatsApp configuration");
117
+ }
118
+
119
+ if (!destination || !String(destination).trim()) {
120
+ throw new Error("WhatsApp destination is empty");
121
+ }
122
+
123
+ try {
124
+ const rawDestination = destination.replace(/\D/g, "");
125
+ const params = new URLSearchParams();
126
+ params.append("channel", "whatsapp");
127
+ params.append("source", source);
128
+ params.append("destination", rawDestination);
129
+ params.append(
130
+ "to",
131
+ rawDestination.startsWith("+")
132
+ ? rawDestination
133
+ : `+${rawDestination}`,
134
+ );
135
+ params.append("src.name", appName);
136
+ params.append(
137
+ "template",
138
+ JSON.stringify({
139
+ id: templateId,
140
+ params: bodyParams,
141
+ }),
142
+ );
143
+
144
+ this.logger.debug(
145
+ `Gupshup OTP outbound (template): endpoint=${templateUrl}, source=${source}, destination=${rawDestination}, to=${rawDestination.startsWith("+") ? rawDestination : `+${rawDestination}`}, templateId=${templateId}, params=${JSON.stringify(bodyParams)}`,
146
+ );
147
+
148
+ const response = await this.httpService.axiosRef.post(
149
+ templateUrl,
150
+ params.toString(),
151
+ {
152
+ headers: {
153
+ apikey: apiKey,
154
+ "Content-Type": "application/x-www-form-urlencoded",
155
+ },
156
+ },
157
+ );
158
+
159
+ this.logger.debug(
160
+ `Gupshup response: ${response.status}, data=${JSON.stringify(response.data)}`,
161
+ );
162
+
163
+ this.logger.debug(
164
+ `Independent OTP WhatsApp sent to ${rawDestination} template=${templateId} via template endpoint`,
165
+ );
166
+ } catch (error) {
167
+ const axiosError = error as AxiosError;
168
+ this.logger.error(
169
+ `Independent OTP WhatsApp failed: destination=${destination}, templateId=${templateId}, status=${axiosError.response?.status ?? "unknown"}, response=${typeof axiosError.response?.data === "object" ? JSON.stringify(axiosError.response?.data) : axiosError.response?.data}, url=${templateUrl}`,
170
+ );
171
+ throw error;
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,253 @@
1
+ import { HttpService } from "@nestjs/axios";
2
+ import { Injectable, Logger } from "@nestjs/common";
3
+ import { AxiosError } from "axios";
4
+ import { WhatsAppProvider } from "src/decorators/whatsapp-provider.decorator";
5
+ import { IWhatsAppTransport } from "src/interfaces";
6
+ import { QueueMessage } from "src/interfaces/mq";
7
+ import { SettingService } from "src/services/setting.service";
8
+ import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
9
+
10
+ type MetaTemplatePayload = {
11
+ type?: "template";
12
+ templateName?: string;
13
+ templateId?: string;
14
+ languageCode?: string;
15
+ body?: string[];
16
+ headerText?: string;
17
+ imageLink?: string;
18
+ };
19
+
20
+ type MetaTextPayload = {
21
+ type?: "text";
22
+ text?: string;
23
+ previewUrl?: boolean;
24
+ };
25
+
26
+ @Injectable()
27
+ @WhatsAppProvider()
28
+ export class MetaCloudWhatsappService implements IWhatsAppTransport {
29
+ readonly logger = new Logger(MetaCloudWhatsappService.name);
30
+
31
+ constructor(
32
+ private readonly httpService: HttpService,
33
+ private readonly settingService: SettingService,
34
+ ) {}
35
+
36
+ async sendWhatsAppMessage(
37
+ to: string,
38
+ templateId: string,
39
+ parameters: any,
40
+ parentEntity?: any,
41
+ parentEntityId?: any,
42
+ ): Promise<any> {
43
+ const message: QueueMessage<any> = {
44
+ payload: {
45
+ to,
46
+ templateId,
47
+ ...parameters,
48
+ },
49
+ parentEntity,
50
+ parentEntityId,
51
+ };
52
+
53
+ await this.sendWhatsAppMessageSynchronously(message);
54
+ return message;
55
+ }
56
+
57
+ async sendWhatsAppMessageSynchronously(message: QueueMessage<any>): Promise<void> {
58
+ const phoneNumberId = this.settingService.getConfigValue<SolidCoreSetting>(
59
+ "metaWhatsappPhoneNumberId",
60
+ );
61
+ const apiVersion = this.settingService.getConfigValue<SolidCoreSetting>(
62
+ "metaWhatsappApiVersion",
63
+ );
64
+ const apiBaseUrl = this.settingService.getConfigValue<SolidCoreSetting>(
65
+ "metaWhatsappApiUrl",
66
+ );
67
+ const accessToken = this.settingService.getConfigValue<SolidCoreSetting>(
68
+ "metaWhatsappAccessToken",
69
+ );
70
+
71
+ if (!phoneNumberId || !apiVersion || !apiBaseUrl || !accessToken) {
72
+ throw new Error(
73
+ "Missing Meta WhatsApp configuration. Set metaWhatsappApiUrl, metaWhatsappApiVersion, metaWhatsappPhoneNumberId, metaWhatsappAccessToken.",
74
+ );
75
+ }
76
+
77
+ const requestBody = this.createWhatsappRequest(message);
78
+ const url = `${String(apiBaseUrl).replace(/\/$/, "")}/${apiVersion}/${phoneNumberId}/messages`;
79
+
80
+ try {
81
+ await this.httpService.axiosRef.post(url, requestBody, {
82
+ headers: {
83
+ Authorization: `Bearer ${accessToken}`,
84
+ "Content-Type": "application/json",
85
+ },
86
+ });
87
+ this.logger.debug(
88
+ `Sent Meta WhatsApp message to ${message.payload?.to} with type ${requestBody.type}`,
89
+ );
90
+ } catch (error) {
91
+ const axiosError = error as AxiosError;
92
+ const status = axiosError.response?.status;
93
+ const data = axiosError.response?.data;
94
+ this.logger.error(
95
+ `Meta WhatsApp send failed: status=${status ?? "unknown"}, url=${url}, response=${typeof data === "object" ? JSON.stringify(data) : data}`,
96
+ );
97
+ throw error;
98
+ }
99
+ }
100
+
101
+ private createWhatsappRequest(message: QueueMessage<any>): any {
102
+ const payload = message?.payload ?? {};
103
+
104
+ if (payload?.payload) {
105
+ const normalizedFromWrappedPayload = this.normalizeWrappedPayload(payload.payload);
106
+ if (normalizedFromWrappedPayload) {
107
+ return normalizedFromWrappedPayload;
108
+ }
109
+ return payload.payload;
110
+ }
111
+
112
+ const to = this.normalizePhone(payload.to);
113
+ const templatePayload: MetaTemplatePayload = payload;
114
+ const textPayload: MetaTextPayload = payload;
115
+
116
+ if (textPayload.type === "text" || textPayload.text) {
117
+ return {
118
+ messaging_product: "whatsapp",
119
+ recipient_type: "individual",
120
+ to,
121
+ type: "text",
122
+ text: {
123
+ body: textPayload.text ?? "",
124
+ preview_url: Boolean(textPayload.previewUrl),
125
+ },
126
+ };
127
+ }
128
+
129
+ const templateName = payload.templateId || templatePayload.templateId || templatePayload.templateName;
130
+ if (!templateName) {
131
+ throw new Error(
132
+ "Meta WhatsApp template name is missing. Provide templateId or parameters.templateName.",
133
+ );
134
+ }
135
+
136
+ const components: any[] = [];
137
+ if (templatePayload.headerText) {
138
+ components.push({
139
+ type: "header",
140
+ parameters: [
141
+ {
142
+ type: "text",
143
+ text: templatePayload.headerText,
144
+ },
145
+ ],
146
+ });
147
+ } else if (templatePayload.imageLink) {
148
+ components.push({
149
+ type: "header",
150
+ parameters: [
151
+ {
152
+ type: "image",
153
+ image: { link: templatePayload.imageLink },
154
+ },
155
+ ],
156
+ });
157
+ }
158
+
159
+ if (Array.isArray(templatePayload.body) && templatePayload.body.length > 0) {
160
+ components.push({
161
+ type: "body",
162
+ parameters: templatePayload.body.map((entry) => ({
163
+ type: "text",
164
+ text: String(entry),
165
+ })),
166
+ });
167
+ }
168
+
169
+ return {
170
+ messaging_product: "whatsapp",
171
+ recipient_type: "individual",
172
+ to,
173
+ type: "template",
174
+ template: {
175
+ name: templateName,
176
+ language: {
177
+ code: templatePayload.languageCode || "en",
178
+ },
179
+ ...(components.length > 0 ? { components } : {}),
180
+ },
181
+ };
182
+ }
183
+
184
+ private normalizeWrappedPayload(payload: any): any | null {
185
+ if (!payload || typeof payload !== "object") {
186
+ return null;
187
+ }
188
+
189
+ if (payload.messaging_product && payload.to && payload.type) {
190
+ return payload;
191
+ }
192
+
193
+ const destination = payload.destination || payload.to;
194
+ const message = payload.message || {};
195
+ if (!destination || !message.type) {
196
+ return null;
197
+ }
198
+
199
+ if (message.type === "text" && message.text) {
200
+ return {
201
+ messaging_product: "whatsapp",
202
+ recipient_type: "individual",
203
+ to: this.normalizePhone(destination),
204
+ type: "text",
205
+ text: {
206
+ body: String(message.text),
207
+ preview_url: false,
208
+ },
209
+ };
210
+ }
211
+
212
+ if (message.type === "template" && message.template?.id) {
213
+ const params = Array.isArray(message.template?.params)
214
+ ? message.template.params
215
+ : [];
216
+ return {
217
+ messaging_product: "whatsapp",
218
+ recipient_type: "individual",
219
+ to: this.normalizePhone(destination),
220
+ type: "template",
221
+ template: {
222
+ name: String(message.template.id),
223
+ language: {
224
+ code: "en",
225
+ },
226
+ ...(params.length > 0
227
+ ? {
228
+ components: [
229
+ {
230
+ type: "body",
231
+ parameters: params.map((entry: any) => ({
232
+ type: "text",
233
+ text: String(entry),
234
+ })),
235
+ },
236
+ ],
237
+ }
238
+ : {}),
239
+ },
240
+ };
241
+ }
242
+
243
+ return null;
244
+ }
245
+
246
+ private normalizePhone(phone: string): string {
247
+ const digits = String(phone || "").replace(/\D/g, "");
248
+ if (!digits) {
249
+ throw new Error("Destination phone number is required for WhatsApp message.");
250
+ }
251
+ return digits;
252
+ }
253
+ }