Sync customer data from Commerce Engine to Sitecore Membership

Tags: Sitecore XC 9, Customer, Membership, Sync

When an user register new account in Storefront, by default new User will be created in Sitecore Membership and new Customer will be created in Commerce Engine

But there's no oposite way, new customer created in CE (via Business Tool) has no relation with Storefront. Hence that customer is not able to login to storefront

I made it happen by syncing customer data from CE to Sitecore Membership.

This is how I did it:

Firstly, create customer connect project (or use existing one if you already have it). It is in charge of connecting to Customer data of Sitecore Commerce Engine

Create CustomerRepository to retrieve commerce customer from Sitecore CE:

public List<CommerceCustomer> GetCommerceCustomers()
{
try
{
var customers = GetCustomerItems();

return customers?.Select(x => PopulateCustomerData(x))?.ToList();
}
catch (Exception ex)
{
Log.Error("Error while getting commerce customers", ex, this);
}
return null;
}

To do that, we need to invoke GetList API with id = 'Customers' & type = 'Sitecore.Commerce.Plugin.Customers.Customer':

InvokeHttpClientGet(string.Format("GetList(id='Customers',type='Sitecore.Commerce.Plugin.Customers.Customer, Sitecore.Commerce.Plugin.Customers',skip={0},take={1})?$expand=Items", currentResult, pageSize), false, false, null, null);

Full function will be:

private List<JToken> GetCustomerItems()
{
var currentResult = 0;
var totalResult = 1;
var pageSize = 100;
var jtokenList = new List<JToken>();
Log.Info("Commerce.Connector - Loading the mapping entries", this);
Log.Info("Commerce.Connector - Attempting to connect to CE", this);
if (string.IsNullOrEmpty(this.Environment))
Environment = CommerceEngineConfiguration.Instance.DefaultEnvironment;

while (currentResult < totalResult)
{
string getCustomerResult = InvokeHttpClientGet(string.Format("GetList(id='Customers',type='Sitecore.Commerce.Plugin.Customers.Customer, Sitecore.Commerce.Plugin.Customers',skip={0},take={1})?$expand=Items", currentResult, pageSize), false, false, null, null);
if (string.IsNullOrEmpty(getCustomerResult))
{
Log.Error("Commerce.Connector - There was an error retrieving the mappings from the Commerce Service", (object)this);
return jtokenList;
}

Log.Info(string.Format("Commerce.Connector - Processing response from CE. Skipping {0} items", currentResult), this);

var jobject = JsonConvert.DeserializeObject<JObject>(getCustomerResult, JsonSettings);
jtokenList.AddRange(jobject["Items"]);
totalResult = jobject["TotalItemCount"].Value<int>();
currentResult += pageSize;
}
Log.Info(string.Format("Commerce.Connector - Total CustomerItems count {0}", totalResult), this);
return jtokenList;
}

To invoke client get method, we need this one:

internal async Task<string> InvokeHttpClientGetAsync(string serviceCallUrl, bool useCommerceOps = false, bool raiseException = true, string language = null, IDictionary<string, string> headers = null)
{
try
{
var httpClient = GetClient(useCommerceOps, language);

try
{
if (headers != null)
{
foreach (KeyValuePair<string, string> header in headers)
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
HttpResponseMessage async = await httpClient.GetAsync(serviceCallUrl);
if (async.IsSuccessStatusCode)
return await async.Content.ReadAsStringAsync();
LogResponseError(async, raiseException);
return string.Empty;
}
finally
{
if (httpClient != null)
httpClient.Dispose();
}
}
catch (Exception ex)
{
Log.Error(string.Format(CultureInfo.InvariantCulture, "{0}\r\n{1}", serviceCallUrl, ex), this);
return string.Empty;
}
}

And to get client:

private HttpClient GetClient(bool useCommerceOps = false, string language = null)
{
var instance = CommerceEngineConfiguration.Instance;
Environment = instance.DefaultEnvironment;
var httpClient = new HttpClient()
{
BaseAddress = new Uri(useCommerceOps ? instance.CommerceOpsServiceUrl : instance.ShopsServiceUrl)
};

httpClient.DefaultRequestHeaders.Add("ShopName", instance.DefaultShopName);
httpClient.DefaultRequestHeaders.Add("Language", language ?? _language);
httpClient.DefaultRequestHeaders.Add("Currency", instance.DefaultShopCurrency);
httpClient.DefaultRequestHeaders.Add("Environment", instance.DefaultEnvironment);
string certificate = instance.GetCertificate();
if (certificate != null)
httpClient.DefaultRequestHeaders.Add(instance.CertificateHeaderName, certificate);
httpClient.Timeout = instance.CommerceRequestTimeout == 0 ? Timeout.InfiniteTimeSpan : new TimeSpan(0, 0, instance.CommerceRequestTimeout);
return httpClient;
}

After having a list of customer in JToken format, now we're gonna convert each one to a model called CommerceCustomer. It is done by PopulateCustomerData method:

private CommerceCustomer PopulateCustomerData(JToken commerceCustomer)
{
if (commerceCustomer == null) return null;

var customer = new CommerceCustomer
{
UserName = commerceCustomer["UserName"].Value<string>(),
Email = commerceCustomer["Email"].Value<string>(),
FirstName = commerceCustomer["FirstName"].Value<string>(),
UserId = commerceCustomer["Id"].Value<string>(),
LastName = commerceCustomer["LastName"].Value<string>()
};
var password = commerceCustomer["Password"].Value<string>();
customer.Password = string.IsNullOrEmpty(password) ? DefaultUserPassword : password;

// We can add more information here

return customer;
}

That's all for CustomerRepository. Next, I'm gonna create new service to get above customer list, then update to Sitecore User. This is how it look like:

var customerReporitory = new CustomerRepository();
var commerceCustomers = customerReporitory.GetCommerceCustomers();
if (commerceCustomers == null) return;

InsertOfUpdateUsers(commerceCustomers);
private void InsertOfUpdateUsers(List<CommerceCustomer> commerceCustomers)
{
if (commerceCustomers == null) return;

var successCount = 0;
foreach (var commerceCustomer in commerceCustomers)
{
if (!InsertOfUpdateUser(commerceCustomer)) continue;

successCount++;
}

Log.Info($"Finish upserting commerce users. Total updated users = {successCount}", this);
}

It is up to you for upsert customer data. In my case, I decided to insert new user if it does not exist or update the existing one:

private bool InsertOfUpdateUser(CommerceCustomer commerceCustomer)
{
if (commerceCustomer == null) return false;

try
{
User user = null;
var username = commerceCustomer.UserName;
if (User.Exists(username))
{
user = User.FromName(username, true);
}
else
{
user = User.Create(username, commerceCustomer.Password);
}
if (user?.Profile == null) return false;
user.Profile.ProfileItemId = Constants.CommerceUserProfileId;
user.Profile.SetCustomProperty("first_name", commerceCustomer.FirstName ?? "");
user.Profile.SetCustomProperty("last_name", commerceCustomer.LastName ?? "");
user.Profile.SetCustomProperty("email_address", commerceCustomer.Email ?? "");
user.Profile.SetCustomProperty("user_id", commerceCustomer.UserId ?? "");
user.Profile.FullName = $"{commerceCustomer.FirstName} {commerceCustomer.LastName}";
user.Profile.Email = commerceCustomer.Email ?? "";

// More code goes here for mapping customer data to user profile

user.Profile.Save();

}
catch (Exception ex)
{
Log.Error($"Error while upserting user. Username = {commerceCustomer.UserName}", ex, this);
return false;
}

return true;
}

No Comments

Add a Comment