Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

This discovery details the code changes necessary

Table of Contents
minLevel1
maxLevel6
outlinefalse
typelist
printablefalse

This refinement details the necessary changes for the search by phone to work in admin tool , and is based on the solution n. 1 proposed documented in Search customer by phone in Admin App .

Table of Contents
minLevel1
maxLevel6
outlinefalse
typelist
printablefalse

POC

Validate SA market solution

In

Jira Legacy
serverSystem JIRA
serverId255417eb-03fa-3e2f-a6ba-05d325fec50d
keyIPN-31
, Saudi Arabia team are implementing the possibility to sign up by phone, which has some overlaps with the changes needed for admin app. Their solution creates the lookup by phone records in DynamoDB that allow us search the customer by phone.

Result:

After confirming that SA market is deployed in eu-central-1 AWS Region, I could find lookup records created in Dynamo:

...

With this information, I was able to validate that the search could work in admin app:

Note

Since this is just a proof of concept, the example is not working perfectly. For example, the search bar is not showing the customer name and the phone in Customer Details page is wrong but this issues would be fixed during the development.

  1. Searching by phone

    image-20240118-064400.pngImage Removed
  2. Result

...

Code changes

In this section are listed the changes made for this validation in intl-packages and intl-admin-app repositories.

intl-packages:

users.ts:

Code Block
languagetypescript
  public getByPhoneNumber(
    phoneNumber: string,
    searchByUserServiceLookup = false,
  ): Promise<IUserItem | undefined> {
    return searchByUserServiceLookup
      ? this.usersDynamo.getByUserServicePhoneLookup(phoneNumber)
      : this.usersDynamo.getByPhoneNumber(phoneNumber);
  }

users-dynamo.ts:

...

Solution 1

Create a phone lookup record in the commit order workflow. This will make sure that we’ll be able to find the customer searching by his phone as long as he has included it on the order, or registered in his account (verified or not). There are three possible scenarios:

Scenario

Condition

Outcome

Delivery order

Phone number is mandatory to close the order

Creates record using the order’s phone number

Not delivery order but user has a phone added to his account

Phone number is not mandatory

Creates record using the user’s account phone number if it doesn’t exist already

Not delivery order and user hasn’t added a phone to his account

Phone number is not mandatory

Doesn’t create a record

Pros and cons

Pros

  • Uses existing lookup record structure

  • Search will work with both phone numbers included in the order or the user account

  • This solution will be compatible with the Phone OTP

  • Doesn’t require phone verification

  • Doesn’t require migration scripts, the search will work as long as the user has made one order after the feature release

Cons

  • A phone number can only be associated with a single customer, which will be the first one to inform the number on the platform

  • There isn’t a simple way to delete the link between a user and a phone number recorded in a delivery order. One possibility is allowing customer support to remove the link via the support tool if it is outdated.

Implementation Proposal

Search by phone

The search will be implemented in the users service API, the NPM user package (represented as intl-packages below) exposes the client that admin app will use to make this request.

...

Create phone lookup records

The creation of the lookup records can be either synchronous or asynchronous, the two diagrams below details the different approaches:

asynchronous: since the commit order operation is already slow, this option minimizes possibly making it slower. [PREFERRED]

...

synchronous: simpler implementation

...

POC

The code below creates lookup records if they don’t exist when the order is committed.

intl-whitelabel-graphql/src/functions/graphql/resolvers/orders.ts

Code Block
languagetypescript
Mutation: {
   async commitOrder(...) {
   ...
    const savePhone = () => {
      const phoneNumber = delivery?.dropoff?.phoneNumber ?? contextUser.details.phoneNumber;
      const { cognitoId } = contextUser;
      if (phoneNumber && cognitoId) {
        providers.users.createPhoneRecord(cognitoId, phoneNumber);
      }
    };
 
    savePhone();
  }
}   

intl-packages/src/user-lookup-by-phone/user-lookup-by-phone.ts

Code Block
  class UserLookupByPhone {
    public async create({ cognitoId, phoneNumber }: { cognitoId: string; phoneNumber: string }) {
      const params = {
        ConditionExpression: INSERT_CONDITION_EXPRESSION,
        Item: {
      if (lookupItem) {    pk: `phone#${phoneNumber}`,
        const cognitoId = lookupItem.pk;  pk2: `phone_user#${cognitoId}`,
           const user = await this.getById(cognitoId);sk: 'v0_UserPhone',
          sk2: 'v0_UserPhoneLookUp',
return user;       },
        TableName: this.tableName,
      };
  
       return undefinedawait this.executeWithRetry(DynamoMethods.put, params);
  }

user-lookup-by-phone:

Code Block
  }
    
    public async getByUserServiceLookup(
      phoneNumber: string,
    ): Promise<IUserLookupByPhone | undefined> {
      const dynamoSearch = {
        ExpressionAttributeValues: {
          ':pk2pk': this.createUserServicePk(phoneNumber),`phone#${phoneNumber}`,
          ':sk2sk': this.createUserServiceSk()'v0_UserPhone',
        },
      IndexName: 'brand-index-2',       KeyConditionExpression: 'pk2pk = :pk2pk AND begins_with(sk2sk, :sk2sk)',
        TableName: this.tableName,
      };
  
  
      const { Items } = await this.executeWithRetry(DynamoMethods.query, dynamoSearch);
      const Item = Items?.[0];
      return Item ? cast(Item, TUserLookupByPhone) : undefined;
    }

intl-admin-app

The changes can be found in https://github.com/rbilabs/intl-admin-app/pull/242 .

Necessary changes - summary

...

User Service API: Adapt GET user endpoint to also search by phone number

...

User Service API: Adapt the create method in user service to create a lookupByPhoneNumber record if a phone number is informed

...

User Service API: Adapt update-user lambda to update or create a lookupByPhoneNumber record if the phone number changed

...

User packages: Change getByPhoneNumber to use User Service API instead of directly accessing the database

...

Admin App Backend: Create a GraphQL query to request customers by phone number

...

Admin App Frontend: Add the option to search customers by phone using the new query

...

DynamoDB: Develop a migration script to create lookupByPhoneNumber for existing customers

Diagrams

Search customer by phone sequence diagram:

...

Necessary changes - details

intl-user-service

...


  }
  

  1. Commit an order with delivery.dropoff.phoneNumber +222222

    image-20240118-155822.pngImage Added

  2. Validate record in database

    image-20240118-160259.pngImage Added

  3. Searching phone in support tool

    image-20240118-160837.pngImage Added

  4. Customer details

    image-20240118-160929.pngImage Added

Solution 2 [Preferred]

Solution 2 uses the same strategy as solution 1, the only difference is that it proposes to update the lookup record structure so that it can link multiple phone numbers to multiple customers. Below is a suggestion of the new record:

Note

We’ll need to implement checks to prevent the creation of multiple lookup records linking the same phone number and customer

Code Block
{
  pk: `phone_user#${cognitoId}`,
  pk2: `phone#${phoneNumber}`,
  sk: `phone#${phoneNumber}`,
  sk2: 'phone_user#${cognitoId}',
}

With these changes, the search by phone will return a list of customers instead of a single one and avoid the problem of manual record deletion that solution 1 requires. However, the support tool front end will have to be adapted so that customer support can choose which one they want to check, given the list of customers associated with that phone number. The front end design will have to be aligned with UI/UX team.

An important point is that customer support can differentiate which customer is the actual owner of the phone number. Phone verification is not enabled in Iberia right now, but the feature is expected to be activated in the following months.

Pros and cons

Pros

  • Search will work with both phone numbers included in the order or in the user account

  • Doesn’t require phone verification

  • This solution will be compatible with the Phone OTP

  • Doesn’t require migration scripts, the search will work as long as the user has made one order after the feature release

  • Allows the association of multiple phone numbers with multiple customers

Cons

  • Require the development of a new page in the support tool front end to select which customer they want to check

  • Although very unlikely, there is a scenario where a single phone number is linked to many customers and this might make the process of finding the right one cumbersome

POC

SearchByPhone POC:

For this POC I changed the getByUserServiceLookup to use the proposed structure:

intl-packages/src/user-lookup-by-phone/user-lookup-by-phone.

...

ts

Code Block
export interface IUserLookupByPhone {
  pk: string;
  pk2: string;
  sk: string;
  sk2: string;
  isVerified: boolean;
}
  1. Create lookupByPhoneNumber record after creating the user in dynamo inside the pre-signup lambda, but only if a phone number is informed: https://github.com/rbilabs/intl-user-service/blob/master/src/cognito-lambdas/pre-signup.service.ts#L135

  2. Update the lookupByPhoneNumber record if an user update is triggered and includes the phone number: https://github.com/rbilabs/intl-user-service/blob/master/src/users/update-comm-pref.service.ts#L150

  3. Adapt the get user endpoint to have an phoneNumber query field, that searches by phone: https://github.com/rbilabs/intl-user-service/blob/master/src/users/users.controller.ts#L124

  4. Create a LaunchDarkly Flag and request it via a backend query (https://github.com/rbilabs/intl-admin-app/blob/master/src/remote/queries/launch-darkly.ts )

intl-packages

...

intl-admin-app

  • Create a GraphQL query that uses the searchByPhone method exposed by intl-packages

  • Update the UI to display the search by phone number option

...

public async getByUserServiceLookup(
  phoneNumber: string,
): Promise<IUserLookupByPhone[] | undefined> {
  const dynamoSearch = {
    ExpressionAttributeValues: {
      ':pk2': `phone_lookup#${phoneNumber}`,
      ':sk2': 'createdAt#',
    },
    IndexName: 'brand-index-2',
    KeyConditionExpression: 'pk2 = :pk2 AND begins_with(sk2, :sk2)',
    TableName: this.tableName,
  };

  const { Items } = await this.executeWithRetry(DynamoMethods.query, dynamoSearch);
  return Items ? Items.map((item) => cast(item, TUserLookupByPhone)) : undefined;
}

created 4 lookup records for 2 customers:

...

and was able to find the two customers when searching for the phone number +123123123:

...

Distinguish verified number POC:

To explicitly display which user is verified, we need to:

  • return phoneVerified in the Customer query from backend

  • check verified numbers and compare them with the searched text in the result.

intl-admin-app/src/components/layout/universal-search/index.tsx

Code Block
languagetypescript
export const UniversalSearch = () => {
  ...
  return (
    <List>
      ...
      {searchResults.map((result) => {
        const customer = result as CustomerDetails;
        const { phoneVerified, phoneNumber } = customer;
        const isVerifiedUser = !!phoneVerified && searchTerm === phoneNumber;

        return (
          <SearchItem
            item={result}
            key={result.id}
            onClick={handleSearchResultSelection}
            isVerified={isVerifiedUser}
          />
        );
      }
    </List>
  )
}

The code alterations above, plus altering SearchItem to change the text if the phone is verified we get:

...

Task breakdown

[intl-user-service]

  1. Refactor user-lookup-by-phone.repository to use both “UserPhone” and “UserPhoneLookUp” records

    1. Since this impacts SA team, we need to ask their review as well

  2. Update the lookup record structure to enable associating multiple phones and users

  3. Develop create lookup records endpoint

  4. Develop find by phone endpoint

[intl-packages]

  1. Develop a method in intl-users that creates lookup records in via the new user endpoint

  2. Update the searchByPhone method to use the new User endpoint

[intl-whitelabel-graphql]

  1. Create lookup records in commitOrder

[intl-fulfillment-service]

  1. Create lookup records in commitOrder

[intl-admin-app]

  1. Update customer query to also search by phone

  2. Include search by phone in search bar

    1. include search by phone in use-universal-search hook

    2. add logic to explicitly show verified numbers