Skip to content

[Shopify] Fix customer matching when phone numbers contain spaces - #10256

Open
Onat Buyukakkus (onbuyuka) wants to merge 2 commits into
mainfrom
bugs/646794-shopify-customer-phone-space-match
Open

[Shopify] Fix customer matching when phone numbers contain spaces#10256
Onat Buyukakkus (onbuyuka) wants to merge 2 commits into
mainfrom
bugs/646794-shopify-customer-phone-space-match

Conversation

@onbuyuka

@onbuyuka Onat Buyukakkus (onbuyuka) commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Fixes AB#646794

Problem

When exporting a Business Central customer to Shopify, the connector searches existing Shopify customers by email and then by phone. Shopify's search syntax treats whitespace as a term separator (implicit AND), so a formatted phone number such as +45 4545 4545 is parsed as phone:+45 plus two loose 4545 terms and can match an unrelated customer. The connector then accepts that customer id and skips the export with the misleading message "Customer already exists with the same e-mail or phone." — even though both the email and phone are unique. The outcome also depended on export order.

Fix

  • Normalize the phone to + and digits before sending it to Shopify, so the whole number is scoped to the phone: filter as a single, whitespace-free term (Shpfy Customer APIFindIdByPhone + new FormatPhoneNo helper).
  • Verify the match is exact — only accept a search result when the returned customer's phone/email matches the requested value exactly, so a tokenized or partial Shopify search result is no longer treated as an existing customer (FindIdByPhone / FindIdByEmail).
  • Return the compared fieldsFindCustomerIdByPhone.graphql and FindCustomerIdByEMail.graphql now return defaultPhoneNumber / defaultEmailAddress so the match can be verified.

Tests

Added Shpfy Customer API tests (HTTP-mocked) covering:

  • spaced international phone numbers normalized to a single search term,
  • a tokenized/partial Shopify result → no match (customer is not skipped),
  • an exact phone match → the customer id is returned,
  • exact and case-insensitive email matching.

Fixes AB#646794

When exporting a Business Central customer to Shopify, the connector searched
existing Shopify customers by email and then by phone. Shopify's search syntax
treats whitespace as a term separator (implicit AND), so a formatted phone
number such as "+45 4545 4545" was parsed as `phone:+45` plus two loose "4545"
terms and could match an unrelated customer. The connector then accepted that
customer id and skipped the export with the misleading message "Customer
already exists with the same e-mail or phone.", even though both the email and
phone were unique. The result also depended on export order.

Fix:
- Normalize the phone number to '+' and digits before sending it to Shopify so
  the whole number is scoped to the phone filter as a single, whitespace-free
  term (Shpfy Customer API: FindIdByPhone + new FormatPhoneNo helper).
- Only accept a search result when the returned customer's phone/email matches
  the requested value exactly, so a tokenized or partial Shopify search result
  is no longer treated as an existing customer (FindIdByPhone / FindIdByEmail).
- Return defaultPhoneNumber / defaultEmailAddress from the find-by-phone and
  find-by-email GraphQL queries so the match can be verified.

Tests:
- Added Shpfy Customer API tests covering spaced international phone numbers,
  tokenized non-matches, exact matches, and case-insensitive email matching.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 89e1180c-4ba0-4612-9f7c-d7686ccecd2b
@onbuyuka
Onat Buyukakkus (onbuyuka) requested a review from a team August 14, 2026 13:25
@github-actions github-actions Bot added AL: Apps (W1) Add-on apps for W1 Integration GitHub request for Integration area labels Aug 14, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Aug 14, 2026
@@ -1,2 +1,2 @@
# cost: 3
{"query":"{customers(first: 1, query:\"email:{{EMail}}\") {edges {node {id}}}}"} No newline at end of file
{"query":"{customers(first: 1, query:\"email:{{EMail}}\") {edges {node {id defaultEmailAddress { emailAddress }}}}}"} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Web\ Services}$

FindIdByEmail now rejects a returned customer unless its defaultEmailAddress.emailAddress matches the requested e-mail exactly, but the backing GraphQL resource still requests only customers(first: 1, ...). If Shopify's tokenized search ranks a non-matching customer first, the exact-match check now correctly rejects it, but the code never looks at any further candidates, so an existing customer with the exact e-mail (ranked lower) is missed and reported as not found. Consider requesting more than one candidate (or paging) before giving up, so a real match ranked behind a partial match is still found.

Agent judgement — not directly backed by a BCQuality knowledge article.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

@@ -1,2 +1,2 @@
# cost: 3
{"query":"{customers(first: 1, query:\"phone:{{Phone}}\") {edges {node {id}}}}"} No newline at end of file
{"query":"{customers(first: 1, query:\"phone:{{Phone}}\") {edges {node {id defaultPhoneNumber { phoneNumber }}}}}"} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Web\ Services}$

FindIdByPhone now normalizes the requested phone number and verifies the returned defaultPhoneNumber.phoneNumber matches exactly, but the backing GraphQL resource still requests only customers(first: 1, ...). If Shopify's tokenized search (the same tokenization behavior the accompanying code comment describes) ranks a non-matching customer first, the real matching customer further down the result set is never inspected and the lookup incorrectly reports 'not found'. Consider requesting more than one candidate (or paging) before concluding no match exists.

Agent judgement — not directly backed by a BCQuality knowledge article.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

The four HTTP-mocked FindIdByPhone/FindIdByEmail tests call
ShpfyInitializeTest.CreateShop(), which creates GL accounts via
LibraryERM and therefore needs the demo General Posting Setup that is
only available in the IntegrationTest environment. Without a TestType
the codeunit ran in the Default bucket, so CreateShop() failed with
"There is no General Posting Setup within the filter" and the remaining
tests then saw a blank Shop ("Shopify Admin URL must have a value").

Set TestType = IntegrationTest to match the sibling Shpfy Create
Customer Test / Shpfy Customer Mapping Test codeunits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 89e1180c-4ba0-4612-9f7c-d7686ccecd2b
SearchEMail: Text;
begin
if EMail <> '' then begin
Parameters.Add('EMail', EMail.ToLower());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Breaking\ Changes}$

FindIdByEmail and FindIdByPhone keep the same signatures, but they now reject a Shopify search result unless the returned e-mail/phone matches exactly, whereas before any tokenized/partial search hit was accepted as a match. For existing Shopify Connector installs this is a semantic behavior change: flows that previously auto-matched an existing customer via a loose match can now return 0 and treat that customer as new, changing synchronization behavior and risking duplicate customer creation. This is a legitimate correctness fix, but consider documenting the new exact-match requirement (e.g. release/upgrade note) so integrators are aware existing loosely-matched customers may no longer be recognized.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

if EMail <> '' then begin
Parameters.Add('EMail', EMail.ToLower());
SearchEMail := EMail.ToLower();
Parameters.Add('EMail', SearchEMail);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Error\ Handling}$

The new exact-match guards treat a missing or null Shopify match field as "not found" instead of as an API/response anomaly. JsonHelper.GetValueAsText returns empty text for an absent path, so if defaultEmailAddress.emailAddress or defaultPhoneNumber.phoneNumber is missing from the response, the lookup silently falls through and returns 0, making a malformed/unexpected Shopify response indistinguishable from a genuine no-match. Consider validating that the comparison field is actually present before concluding there is no match, so a shape change in the Shopify response doesn't silently mask itself as "customer not found".

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

end;

[HttpClientHandler]
internal procedure HttpClientHandler(Request: TestHttpRequestMessage; var Response: TestHttpResponseMessage): Boolean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Testing}$

The new FindIdByPhone/FindIdByEmail integration tests only assert the returned customer id against a mocked response; the HttpClientHandler never verifies the outbound GraphQL search term that was actually sent. A regression in the new e-mail lowercasing or phone-normalization logic (SearchEMail/SearchPhone/FormatPhoneNo) would still pass these tests as long as the canned response contains a matching customer, since the request payload itself is unchecked. Extend the handler or the tests to assert the normalized search value sent to Shopify for both the e-mail and phone lookups.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AL: Apps (W1) Add-on apps for W1 Integration GitHub request for Integration area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant