Why some fields in commerce user profile are never set – Sitecore Commerce


Hello guys

I have been privileged to work with Sitecore Commerce and from time to time you will need some extra guidance. Sitecore Commerce has very good documentation but my source of inspiration has been the Sitecore.Demo.Retail.

The “Habitat” gang has done a really great job setting up a demo solution on the github, Sitecore.Demo.Retail – Sitecore Commerce running on Habitat steroids 🙂

Anyways here is a tip on how to update the commerce user profile (and the datatable UserOject).
I noticed that some fields for the Commerce User profile was never set, the same goes for the table UserObject in database YOURPREFIX_profiles.

In fact there is a whole bunch of fields in the Commerce User profile:

And here is all the columns in the UserObject Table(in database YOURPREFIX_profiles):

[u_user_id]
[u_org_id]
[u_user_type]
[u_first_name]
[u_last_name]
[u_email_address]
[u_preferred_address]
[u_addresses]
[u_preferred_credit_card]
[u_credit_cards]
[u_tel_number]
[u_tel_extension]
[u_fax_number]
[u_fax_extension]
[u_user_security_password]
[u_user_id_changed_by]
[u_account_status]
[u_user_catalog_set]
[dt_date_registered]
[u_campaign_history]
[dt_date_last_changed]
[dt_csadapter_date_last_changed]
[dt_date_created]
[u_language]
[u_Pref1]
[u_Pref2]
[u_Pref3]
[u_Pref4]
[u_Pref5]
[u_password_question]
[u_password_answer]
[u_logon_error_dates]
[u_password_answer_error_dates]
[i_keyindex]
[i_access_level_id]
[b_change_password]
[dt_date_last_password_changed]
[dt_last_logon]
[dt_last_lockedout_date]
[u_application_name]
[dt_last_activity_date]
[b_direct_mail_opt_out]
[b_express_checkout]
[dt_date_address_list_last_changed]
[dt_date_credit_card_list_last_changed]
[u_external_id]
[u_comment]
[u_preferred_shipping_method]
[u_default_shopper_list]

When you update a commerce user you are probably using method UpdateUser in Sitecore.Commerce.Services.Customers.CustomerServiceProvider. If you check the code you will see that it will run the following pipelines in section commerce.customers.updateUser:

<commerce.customers.updateUser>
<processor type="Sitecore.Commerce.Pipelines.Customers.UpdateUser.UpdateUserInExternalSystem, Sitecore.Commerce" />
<processor type="Sitecore.Commerce.Pipelines.Customers.UpdateUser.UpdateUserInSitecore, Sitecore.Commerce" >
  <param ref="sitecoreUserRepository" />
</processor>
<processor type="Sitecore.Commerce.Pipelines.Customers.UpdateContact.UpdateContactInXDb, Sitecore.Commerce">
  <param ref="sitecoreUserRepository"/>
</processor>        
<processor type="Sitecore.Commerce.Pipelines.Customers.Common.TriggerUserPageEvent, Sitecore.Commerce">
  <Name>User Account Updated</Name>
  <Text>User account has been updated.</Text>
</processor>
</commerce.customers.updateUser>

Let’s take a look at pipeline Sitecore.Commerce.Pipelines.Customers.UpdateUser.UpdateUserInSitecore, and especially this:

protected override void ProcessSitecoreUser(CommerceUser user, UserRequestWithUser request, ServiceProviderResult result)
{
  this.UserRepository.Update(user);
}

Let’s dive deeper into method UserRepository.Update in SitecoreUserRepository:

public virtual void Update(CommerceUser entity)
{
   Assert.ArgumentNotNull((object) entity, nameof (entity));
   string userFullName = this.GetUserFullName(entity.UserName);
   Assert.IsTrue((User.Exists(userFullName) ? 1 : 0) != 0, "User with name {0} does not exist", (object) entity.UserName);
   this.UpdateExistingUser(this.DecorateUser(User.FromName(userFullName, true)), entity);
}

Here we have something interesting, lets look at method UpdateExistingUser:

protected virtual void UpdateExistingUser(User user, CommerceUser commerceUser)
{
  MembershipUser user1 = Membership.GetUser(user.Name);
  Type type = typeof (MembershipUser);
  Assert.IsNotNull((object) user1, type);
  string comment = commerceUser.Comment;
  user1.Comment = comment;
  string email = commerceUser.Email;
  user1.Email = email;
  int num = !commerceUser.IsDisabled ? 1 : 0;
  user1.IsApproved = num != 0;
  Membership.UpdateUser(user1);
  user.Profile.FullName = string.Format((IFormatProvider) CultureInfo.InvariantCulture, "{0} {1}", new object[2]
  {
  (object) commerceUser.FirstName,
  (object) commerceUser.LastName
  }).Trim();
  user.Profile.Email = commerceUser.Email ?? string.Empty;
  user.Profile.Comment = commerceUser.Comment ?? string.Empty;
  this.SerializeUser(user, commerceUser);
  Role role = this.GetRole(this.DefaultRole);
  if ((Account) role != (Account) null && !user.IsInRole(role))
  user.Roles.Add(role);
  user.Profile.Save();
}

And here we have it 🙂 Notice that fields like FirstName, LastName or Phone are never set. So what can we do?

The great thing about Sitecore Commerce it’s part of Sitecore, that means it will be very easy for us to fix this 🙂

Why not create a new pipeline and call it UpdateCommerceUserProfile. We want it to run after the pipeline, Sitecore.Commerce.Pipelines.Customers.UpdateUser.UpdateUserInSitecore

namespace Sitecore.Foundation.Commerce.Connect.Pipelines.Customers
{
  using CommerceServer.Core.Runtime.Profiles;
  using Sitecore.Commerce.Pipelines;
  using Sitecore.Commerce.Services;
  using Sitecore.Commerce.Services.Customers;
  using Sitecore.Diagnostics;
  using System.Linq;

  public class UpdateCommerceUserProfile : CustomerPipelineProcessor
  {
    

    public override void Process(ServicePipelineArgs args)
    {
      Assert.ArgumentNotNull(args, nameof(args));
      Assert.ArgumentCondition(args.Request is UpdateUserRequest, nameof(args.Request), "args.Request is UpdateUserRequest");
      Assert.ArgumentCondition(args.Result is UpdateUserResult, nameof(args.Result), "args.Result is UpdateUserResult");

      var request = (UpdateUserRequest)args.Request;
      var result = (UpdateUserResult)args.Result;

      Profile customerProfile = null;
      ServiceProviderResult response = GetCommerceUserProfile(request.CommerceUser.ExternalId, ref customerProfile);

      if (!response.Success)
      {
        result.Success = false;
        response.SystemMessages.ToList().ForEach(m => result.SystemMessages.Add(m));
        return;
      }



      foreach (var property in request.CommerceUser.Properties)
      {

        if (!property.Key.StartsWith(Constants.Profile.GeneralInfoPrefix))
          continue;

        customerProfile[property.Key].Value = property.Value ?? string.Empty;
      }

      customerProfile.Update();

    }
  }
}

Here we will get the user profile by calling GetCommerceUserProfile and then we will loop through the Properties collection on the CommerceUser object(We only want properties with the prefix “GeneralInfo”). Finally we will set them to the user profile(customerProfile) and finish it with a customerProfile.Update()

As I mentioned before we want the pipeline to run after the pipeline, Sitecore.Commerce.Pipelines.Customers.UpdateUser.UpdateUserInSitecore, so we need to do the following in config – Foundation.CommerceCore.Customer.config:

<commerce.customers.updateUser>
  <processor patch:after="processor[@type='Sitecore.Commerce.Pipelines.Customers.UpdateUser.UpdateUserInSitecore, Sitecore.Commerce']" 
              type="Sitecore.Foundation.CommerceCore.Connect.Pipelines.Customers.UpdateCommerceUserProfile, Sitecore.Foundation.Commerce">
  </processor>
</commerce.customers.updateUser>

And now we can try it, let’s make some changes in the AccountManager in Sitecore.Demo.Retail at the GitHub.

public ManagerResponse<UpdateUserResult, CommerceUser> UpdateUser(string userName, ProfileModel inputModel)
{
  Assert.ArgumentNotNull(inputModel, nameof(inputModel));

  UpdateUserResult result;

  CommerceUser commerceUser = GetUser(userName).Result;

  if (commerceUser != null)
  {
	commerceUser.FirstName = inputModel.FirstName;
	commerceUser.LastName = inputModel.LastName;
	commerceUser.Email = inputModel.Email;
 
	commerceUser.SetPropertyValue(Sitecore.Foundation.Commerce.Connect.Pipelines.Constants.Profile.GeneralInfo.FirstName, commerceUser.FirstName);
	commerceUser.SetPropertyValue(Sitecore.Foundation.Commerce.Connect.Pipelines.Constants.Profile.GeneralInfo.LastName, commerceUser.LastName);
	commerceUser.SetPropertyValue(Sitecore.Foundation.Commerce.Connect.Pipelines.Constants.Profile.GeneralInfo.TelNumber, inputModel.TelephoneNumber);



	try
	{
	  var request = new UpdateUserRequest(commerceUser);
	  result = CustomerServiceProvider.UpdateUser(request);
	}
	catch (Exception ex)
	{
	  result = new UpdateUserResult { Success = false };
	  result.SystemMessages.Add(new SystemMessage { Message = ex.Message + "/" + ex.StackTrace });
	}

  }
  else
  {
	// user is authenticated, but not in the CommerceUsers domain - probably here because we are in edit or preview mode
	var message = DictionaryPhraseRepository.Current.Get("/System Messages/Account Manager/Update User Profile Error", "Cannot update profile details for user {0}.");
	message = string.Format(message, Context.User.LocalName);
	result = new UpdateUserResult { Success = false };
	result.SystemMessages.Add(new SystemMessage { Message = message });
  }

  result.WriteToSitecoreLog();
  return new ManagerResponse<UpdateUserResult, CommerceUser>(result, result.CommerceUser);
}

The changes we have done is by adding property values to the CommerceUser object:

commerceUser.SetPropertyValue(Sitecore.Foundation.Commerce.Connect.Pipelines.Constants.Profile.GeneralInfo.FirstName, commerceUser.FirstName);
commerceUser.SetPropertyValue(Sitecore.Foundation.Commerce.Connect.Pipelines.Constants.Profile.GeneralInfo.LastName, commerceUser.LastName);
commerceUser.SetPropertyValue(Sitecore.Foundation.Commerce.Connect.Pipelines.Constants.Profile.GeneralInfo.TelNumber, inputModel.TelephoneNumber);

That’s it guys, now the user profile will be set in Sitecore and the UserObject table will be updated

That’s all for now folks 🙂


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.