diff --git a/platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts b/platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts index 3095b458d..5e7484eff 100644 --- a/platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts +++ b/platforms/blabsy-w3ds-auth-api/src/controllers/WebhookController.ts @@ -158,6 +158,15 @@ export class WebhookController { if (tableName === "users") { docRef = collection.doc(data.ename); } else if (tableName === "chats") { + // Check if this is a DM with DM ID in description (already processed) + // This prevents duplicate chat creation when webhooks arrive from eCurrency + const description = data.description || mappedData.description; + if (description && typeof description === 'string' && description.startsWith('DM ID:')) { + console.log("⏭️ Skipping DM creation - this DM has DM ID in description (already processed)"); + adapter.addToLockedIds(globalId); + return; // Exit early, don't create new chat + } + // Check for existing DM (2 participants, no name) before creating const participants = mappedData.participants || []; const isDM = participants.length === 2 && !mappedData.name; diff --git a/platforms/eCurrency-api/src/services/LedgerService.ts b/platforms/eCurrency-api/src/services/LedgerService.ts index 27c07e0fc..6cbae4f36 100644 --- a/platforms/eCurrency-api/src/services/LedgerService.ts +++ b/platforms/eCurrency-api/src/services/LedgerService.ts @@ -130,9 +130,10 @@ export class LedgerService { // Send transaction notifications for all transfer types // (USER-to-USER, USER-to-GROUP, GROUP-to-USER, GROUP-to-GROUP) - try { + // Fire-and-forget: don't block the transfer response + setImmediate(() => { const notificationService = new TransactionNotificationService(); - await notificationService.sendTransactionNotifications( + notificationService.sendTransactionNotifications( amount, currency, fromAccountId, @@ -140,11 +141,11 @@ export class LedgerService { toAccountId, toAccountType, description - ); - } catch (error) { - // Don't fail the transfer if notification fails - console.error("Error sending transaction notifications:", error); - } + ).catch(error => { + // Don't fail the transfer if notification fails + console.error("Error sending transaction notifications:", error); + }); + }); return { debit, credit }; } diff --git a/platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts b/platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts index d378fdc3c..208860c61 100644 --- a/platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts +++ b/platforms/eCurrency-api/src/web3adapter/watchers/subscriber.ts @@ -351,6 +351,12 @@ export class PostgresSubscriber implements EntitySubscriberInterface { */ private async sendGroupWebhook(data: any): Promise { try { + // Skip DMs with DM ID in description (already processed, prevents duplicate webhooks) + if (data.description && typeof data.description === 'string' && data.description.startsWith('DM ID:')) { + console.log(`🔍 Skipping DM webhook - has DM ID in description: ${data.description}`); + return; + } + let globalId = await this.adapter.mappingDb.getGlobalId(data.id); globalId = globalId ?? ""; diff --git a/platforms/pictique-api/src/controllers/WebhookController.ts b/platforms/pictique-api/src/controllers/WebhookController.ts index 342e04469..ad061ff8d 100644 --- a/platforms/pictique-api/src/controllers/WebhookController.ts +++ b/platforms/pictique-api/src/controllers/WebhookController.ts @@ -215,6 +215,13 @@ export class WebhookController { } } } else if (mapping.tableName === "chats") { + // Check if this is a DM with DM ID in description (already processed) + // This prevents duplicate chat creation when webhooks arrive from eCurrency + if (local.data.description && typeof local.data.description === 'string' && local.data.description.startsWith('DM ID:')) { + console.log("⏭️ Skipping DM creation - this DM has DM ID in description (already processed)"); + return res.status(200).send("Skipped DM ID group"); + } + let participants: User[] = []; if ( local.data.participants && @@ -265,30 +272,20 @@ export class WebhookController { this.adapter.addToLockedIds(localId); await this.chatService.chatRepository.save(chat); } else { - // Check for existing DM (2 participants, no name) before creating - const participantIds = participants.map((p) => p.id); - const isDM = participantIds.length === 2 && !local.data.name; - - let chat; - if (isDM) { - const existingChat = await this.chatService.findChatByParticipants(participantIds); + let chat: Chat | null = null; + + // First, check if a mapping already exists for this globalId + // This prevents duplicates when webhooks arrive simultaneously from multiple platforms + const existingLocalId = await this.adapter.mappingDb.getLocalId(globalId); + if (existingLocalId) { + const existingChat = await this.chatService.findById(existingLocalId); if (existingChat) { - // Use existing chat and store mapping + console.log(`⚠️ Chat mapping already exists for globalId ${globalId}, using existing chat: ${existingChat.id}`); chat = existingChat; + chat.name = local.data.name as string; + chat.participants = participants; chat.admins = admins; - await this.chatService.chatRepository.save(chat); - this.adapter.addToLockedIds(chat.id); - await this.adapter.mappingDb.storeMapping({ - localId: chat.id, - globalId: req.body.id, - }); - } else { - // Create new chat - chat = await this.chatService.createChat( - local.data.name as string, - participantIds - ); - chat.admins = admins; + chat.ename = local.data.ename as string; await this.chatService.chatRepository.save(chat); this.adapter.addToLockedIds(chat.id); await this.adapter.mappingDb.storeMapping({ @@ -297,17 +294,15 @@ export class WebhookController { }); } } else { - // Group chat - check for eCurrency Chat duplicates by name - const chatName = local.data.name as string; - if (chatName && (chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat"))) { - // Check if chat with this name already exists - const existingChat = await this.chatService.chatRepository.findOne({ - where: { name: chatName }, - relations: ["participants", "admins"] - }); - + // Check for existing DM (2 participants, no name) before creating + const participantIds = participants.map((p) => p.id); + const isDM = participantIds.length === 2 && !local.data.name; + + let chat; + if (isDM) { + const existingChat = await this.chatService.findChatByParticipants(participantIds); if (existingChat) { - console.log(`⚠️ eCurrency Chat with name "${chatName}" already exists, using existing: ${existingChat.id}`); + // Use existing chat and store mapping chat = existingChat; chat.admins = admins; await this.chatService.chatRepository.save(chat); @@ -319,7 +314,7 @@ export class WebhookController { } else { // Create new chat chat = await this.chatService.createChat( - chatName, + local.data.name as string, participantIds ); chat.admins = admins; @@ -331,18 +326,53 @@ export class WebhookController { }); } } else { - // Regular group chat - always create new - chat = await this.chatService.createChat( - chatName, - participantIds - ); - chat.admins = admins; - await this.chatService.chatRepository.save(chat); - this.adapter.addToLockedIds(chat.id); - await this.adapter.mappingDb.storeMapping({ - localId: chat.id, - globalId: req.body.id, - }); + // Group chat - check for eCurrency Chat duplicates by name + const chatName = local.data.name as string; + if (chatName && (chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat"))) { + // Check if chat with this name already exists + const existingChat = await this.chatService.chatRepository.findOne({ + where: { name: chatName }, + relations: ["participants", "admins"] + }); + + if (existingChat) { + console.log(`⚠️ eCurrency Chat with name "${chatName}" already exists, using existing: ${existingChat.id}`); + chat = existingChat; + chat.admins = admins; + await this.chatService.chatRepository.save(chat); + this.adapter.addToLockedIds(chat.id); + await this.adapter.mappingDb.storeMapping({ + localId: chat.id, + globalId: req.body.id, + }); + } else { + // Create new chat + chat = await this.chatService.createChat( + chatName, + participantIds + ); + chat.admins = admins; + await this.chatService.chatRepository.save(chat); + this.adapter.addToLockedIds(chat.id); + await this.adapter.mappingDb.storeMapping({ + localId: chat.id, + globalId: req.body.id, + }); + } + } else { + // Regular group chat - always create new + chat = await this.chatService.createChat( + chatName, + participantIds + ); + chat.admins = admins; + await this.chatService.chatRepository.save(chat); + this.adapter.addToLockedIds(chat.id); + await this.adapter.mappingDb.storeMapping({ + localId: chat.id, + globalId: req.body.id, + }); + } } } }