Repository: calcom/cal.com
Pull request #20133: fix: Salesforce Stricter Website querying to prevent wrong account match during contact owner check
Author: login: hariombalhara
Labels: name: 🐛 bug
description: Something isn't working
name: enterprise
description: area: enterprise, audit log, organisation, SAML, SSO
name: core
description: area: core, team members only
name: crm-apps
description: area: crm apps, salesforce, hubspot, close.com, sendgrid
name: ready-for-e2e
Created: 2025-03-17T13:08:55Z | Merged: 2025-03-17T20:34:36Z

## What does this PR do?

Followup of https://github.com/calcom/cal.com/pull/19799



Diff:
diff --git a/packages/app-store/salesforce/lib/CrmService.test.ts b/packages/app-store/salesforce/lib/CrmService.test.ts
index 560059bcefe476..b33849d764ad97 100644
--- a/packages/app-store/salesforce/lib/CrmService.test.ts
+++ b/packages/app-store/salesforce/lib/CrmService.test.ts
@@ -511,4 +511,13 @@ describe("SalesforceCRMService", () => {
       });
     });
   });
+
+  describe("getAllPossibleAccountWebsiteFromEmailDomain", () => {
+    it("should return all possible account websites from email domain", () => {
+      const result = service.getAllPossibleAccountWebsiteFromEmailDomain("example.com");
+      expect(result).toEqual(
+        "example.com, www.example.com, http://www.example.com, http://example.com, https://www.example.com, https://example.com"
+      );
+    });
+  });
 });
diff --git a/packages/app-store/salesforce/lib/CrmService.ts b/packages/app-store/salesforce/lib/CrmService.ts
index db355bcc601631..cfdcca0677bd5f 100644
--- a/packages/app-store/salesforce/lib/CrmService.ts
+++ b/packages/app-store/salesforce/lib/CrmService.ts
@@ -883,6 +883,17 @@ export default class SalesforceCRMService implements CRM {
       });
   }
 
+  public getAllPossibleAccountWebsiteFromEmailDomain(emailDomain: string) {
+    return [
+      emailDomain,
+      `www.${emailDomain}`,
+      `http://www.${emailDomain}`,
+      `http://${emailDomain}`,
+      `https://www.${emailDomain}`,
+      `https://${emailDomain}`,
+    ].join(", ");
+  }
+
   private async getAccountIdBasedOnEmailDomainOfContacts(email: string) {
     const conn = await this.conn;
     const emailDomain = email.split("@")[1];
@@ -890,13 +901,16 @@ export default class SalesforceCRMService implements CRM {
     log.info("getAccountIdBasedOnEmailDomainOfContacts", safeStringify({ email, emailDomain }));
     // First check if an account has the same website as the email domain of the attendee
     const accountQuery = await conn.query(
-      `SELECT Id, Website FROM Account WHERE Website IN ('${emailDomain}', 'www.${emailDomain}', 
-                      'http://www.${emailDomain}', 'http://${emailDomain}',
-                      'https://www.${emailDomain}', 'https://${emailDomain}') LIMIT 1`
+      `SELECT Id, Website FROM Account WHERE Website IN (${this.getAllPossibleAccountWebsiteFromEmailDomain(
+        emailDomain
+      )}) LIMIT 1`
     );
     if (accountQuery.records.length > 0) {
-      const account = accountQuery.records[0] as { Id: string };
-      log.info("Found account based on email domain", safeStringify({ accountId: account.Id }));
+      const account = accountQuery.records[0] as { Id: string; Website: string };
+      log.info(
+        "Found account based on email domain",
+        safeStringify({ emailDomain, accountWebsite: account.Website, accountId: account.Id })
+      );
       return account.Id;
     }
 
@@ -923,13 +937,26 @@ export default class SalesforceCRMService implements CRM {
     log.info("Querying first account matching email domain", safeStringify({ emailDomain }));
     // First check if an account has the same website as the email domain of the attendee
     const accountQuery = await conn.query(
-      `SELECT Id, OwnerId, Owner.Email FROM Account WHERE Website LIKE '%${emailDomain}%' LIMIT 1`
+      `SELECT Id, OwnerId, Owner.Email FROM Account WHERE Website IN (${this.getAllPossibleAccountWebsiteFromEmailDomain(
+        emailDomain
+      )}) LIMIT 1`
     );
 
     if (accountQuery.records.length > 0) {
-      log.info("Found account matching email domain", safeStringify({ emailDomain }));
+      const account = accountQuery.records[0] as {
+        Id?: string;
+        OwnerId?: string;
+        Owner?: { Email?: string };
+        Website?: string;
+      };
+
+      log.info(
+        "Found account matching email domain",
+        safeStringify({ emailDomain, accountWebsite: account.Website, accountId: account.Id })
+      );
+
       return {
-        ...(accountQuery.records[0] as { Id?: string; OwnerId?: string; Owner?: { Email?: string } }),
+        ...account,
         Email: undefined,
       };
     }
diff --git a/packages/app-store/salesforce/lib/__tests__/CrmService.integration.test.ts b/packages/app-store/salesforce/lib/__tests__/CrmService.integration.test.ts
index 0e98fc451d569c..48d59a935705f6 100644
--- a/packages/app-store/salesforce/lib/__tests__/CrmService.integration.test.ts
+++ b/packages/app-store/salesforce/lib/__tests__/CrmService.integration.test.ts
@@ -178,8 +178,8 @@ describe("SalesforceCRMService", () => {
 
       it("(Lookup-3) should fallback to account having most number of contacts matched by emailDomain when Lookup-1 and Lookup-2 fails", async () => {
         const crmService = new SalesforceCRMService(credential, appOptions);
-        const account1OwnerEmail = "contact-account-owner1@example.com";
-        const account2OwnerEmail = "contact-account-owner2@example.com";
+        const account1OwnerEmail = "account1-owner@example.com";
+        const account2OwnerEmail = "account2-owner@example.com";
         const emailDomain = "example.com";
         const lookingForEmail = `test@${emailDomain}`;
         const account1 = salesforceMock.addAccount({
diff --git a/packages/app-store/salesforce/lib/__tests__/salesforceMock.ts b/packages/app-store/salesforce/lib/__tests__/salesforceMock.ts
index 63bcf63ce0af5f..f51ae250a7d29f 100644
--- a/packages/app-store/salesforce/lib/__tests__/salesforceMock.ts
+++ b/packages/app-store/salesforce/lib/__tests__/salesforceMock.ts
@@ -45,6 +45,7 @@ export const createSalesforceMock = () => {
   // Query parser and responder
   const handleQuery = (query: string) => {
     // Simple SOQL parser
+    console.log({ query });
     const fromMatch = query.match(/FROM\s+(\w+)/i);
     const whereMatch = query.match(/WHERE\s+(.+?)(?:\s+LIMIT|\s+ORDER|\s*$)/i);
     const limitMatch = query.match(/LIMIT\s+(\d+)/i);
@@ -77,6 +78,7 @@ export const createSalesforceMock = () => {
         return { records: [] };
     }
 
+    console.log({ whereClause });
     // Apply where clause filtering (basic implementation)
     if (whereClause) {
       if (whereClause.includes("Email =")) {
@@ -134,6 +136,14 @@ export const createSalesforceMock = () => {
         }
       }
 
+      if (whereClause.includes("Website IN")) {
+        const websitesMatch = whereClause.match(/Website IN \((.+)\)/i);
+        if (websitesMatch) {
+          const websites = websitesMatch[1].split(",").map((w) => w.trim());
+          result = result.filter((r) => websites.includes(r.Website));
+        }
+      }
+
       if (whereClause.includes("IsActive = true")) {
         result = result.filter((r) => r.IsActive === true);
       }


Reviews:
user: login: hariombalhara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T13:15:37Z
user: login: hariombalhara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T13:18:16Z
user: login: keithwillcode
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T19:45:04Z

Review comments:
diff_hunk: @@ -883,20 +883,34 @@ export default class SalesforceCRMService implements CRM {
       });
   }
 
+  private getAllPossibleAccountWebsiteFromEmailDomain(emailDomain: string) {
+    return [
+      emailDomain,
+      `www.${emailDomain}`,
+      `http://www.${emailDomain}`,
+      `http://${emailDomain}`,
+      `https://www.${emailDomain}`,
+      `https://${emailDomain}`,
+    ].join(", ");
+  }
+
   private async getAccountIdBasedOnEmailDomainOfContacts(email: string) {
     const conn = await this.conn;
     const emailDomain = email.split("@")[1];
     const log = logger.getSubLogger({ prefix: [`[getAccountIdBasedOnEmailDomainOfContacts]:${email}`] });
     log.info("getAccountIdBasedOnEmailDomainOfContacts", safeStringify({ email, emailDomain }));
     // First check if an account has the same website as the email domain of the attendee
     const accountQuery = await conn.query(
-      `SELECT Id, Website FROM Account WHERE Website IN ('${emailDomain}', 'www.${emailDomain}', 
-                      'http://www.${emailDomain}', 'http://${emailDomain}',
-                      'https://www.${emailDomain}', 'https://${emailDomain}') LIMIT 1`
+      `SELECT Id, Website FROM Account WHERE Website IN (${this.getAllPossibleAccountWebsiteFromEmailDomain(
+        emailDomain
+      )}) LIMIT 1`
     );
     if (accountQuery.records.length > 0) {
-      const account = accountQuery.records[0] as { Id: string };
-      log.info("Found account based on email domain", safeStringify({ accountId: account.Id }));
+      const account = accountQuery.records[0] as { Id: string; Website: string };
+      log.info(
+        "Found account based on email domain",
+        safeStringify({ emailDomain, accountWebsite: account.Website, accountId: account.Id })
path: packages/app-store/salesforce/lib/CrmService.ts
user: login: hariombalhara
body: Logging Website and accountId both it can confirm that when we matched what Website value was there for the account
created_at: 2025-03-17T13:15:36Z
author_association: MEMBER
diff_hunk: @@ -178,8 +178,8 @@ describe("SalesforceCRMService", () => {
 
       it("(Lookup-3) should fallback to account having most number of contacts matched by emailDomain when Lookup-1 and Lookup-2 fails", async () => {
         const crmService = new SalesforceCRMService(credential, appOptions);
-        const account1OwnerEmail = "contact-account-owner1@example.com";
-        const account2OwnerEmail = "contact-account-owner2@example.com";
+        const account1OwnerEmail = "account1-owner@example.com";
path: packages/app-store/salesforce/lib/__tests__/CrmService.integration.test.ts
user: login: hariombalhara
body: Better name
created_at: 2025-03-17T13:18:15Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-17T13:09:08Z
body: Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.

Details:

```
No release type found in pull request title "Fix-salesforce-stricter-website-query". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit
```
<!-- Sticky Pull Request Commentpr-title-lint-error -->
author_association: CONTRIBUTOR
user: login: vercel[bot]
created_at: 2025-03-17T13:09:16Z
body: [vc]: #7ukMtXi/rCVKi+iM/qeWc4L1rxgTIH8RFuGNwoMWPdQ=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJjYWxjb20td2ViLWNhbmFyeSIsInJvb3REaXJlY3RvcnkiOiJhcHBzL3dlYiIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2NhbC9jYWxjb20td2ViLWNhbmFyeS9FN3hKSkFBZUFxb1RYaGFMcHdGektUQjlyS1B6IiwibmV4dENvbW1pdFN0YXR1cyI6IklHTk9SRUQiLCJwcmV2aWV3VXJsIjoiY2FsY29tLXdlYi1jYW5hcnktZ2l0LWZpeC1zYWxlc2ZvcmNlLXN0cmljdGVyLXdlYnNpdGUtcXVlcnktY2FsLnZlcmNlbC5hcHAifSx7Im5hbWUiOiJjYWwiLCJyb290RGlyZWN0b3J5IjoiYXBwcy93ZWIiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9jYWwvY2FsL0YxZGZYWTdjcmNFVWQ1MzFzdEtDc2JWNDN2ZVoiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCIsInByZXZpZXdVcmwiOiJjYWwtZ2l0LWZpeC1zYWxlc2ZvcmNlLXN0cmljdGVyLXdlYnNpdGUtcXVlcnktY2FsLnZlcmNlbC5hcHAifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>2 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **cal** | ⬜️ Ignored ([Inspect](https://vercel.com/cal/cal/F1dfXY7crcEUd531stKCsbV43veZ)) | [Visit Preview](https://cal-git-fix-salesforce-stricter-website-query-cal.vercel.app) |  | Mar 17, 2025 1:31pm |
| **calcom-web-canary** | ⬜️ Ignored ([Inspect](https://vercel.com/cal/calcom-web-canary/E7xJJAAeAqoTXhaLpwFzKTB9rKPz)) | [Visit Preview](https://calcom-web-canary-git-fix-salesforce-stricter-website-query-cal.vercel.app) |  | Mar 17, 2025 1:31pm |
</details>



author_association: NONE
user: login: graphite-app[bot]
created_at: 2025-03-17T13:35:10Z
body: ### Graphite Automations
<details><summary><a href="https://app.graphite.dev/automations/?org=calcom&ruleId=qoTwHtX8MnP39HKxIt4V&ref=gt-automation-github-comment">"Add consumer team as reviewer"</a> took an action on this PR • (03/17/25)</summary><p>1 reviewer was added to this PR based on Keith Williams's automation.</p></details>
<details><summary><a href="https://app.graphite.dev/automations/?org=calcom&ruleId=tJdOAwF9biWptZ2BLCTk&ref=gt-automation-github-comment">"Add ready-for-e2e label"</a> took an action on this PR • (03/17/25)</summary><p>1 label was added to this PR based on Keith Williams's automation.</p></details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-17T20:14:23Z
body: <!-- GENERATED-E2E-RESULTS -->
## E2E results are ready!
- [Workflow #52671.2 latest results](https://calcom.github.io/test-results/reports/fix-salesforce-stricter-website-query/13908476903/2)
author_association: CONTRIBUTOR