Try your search with a different keyword or use * as a wildcard.
using System.Globalization;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Orders;
using Nop.Core.Http.Extensions;
using Nop.Data;
using Nop.Plugin.Widgets.FacebookPixel.Domain;
using Nop.Services.Catalog;
using Nop.Services.Cms;
using Nop.Services.Common;
using Nop.Services.Directory;
using Nop.Services.Logging;
using Nop.Services.Orders;
using Nop.Services.Tax;
using Nop.Web.Framework.Models.Cms;
using Nop.Web.Infrastructure.Cache;
using Nop.Web.Models.Catalog;
namespace Nop.Plugin.Widgets.FacebookPixel.Services;
///
/// Represents Facebook Pixel service
///
public class FacebookPixelService
{
#region Constants
///
/// Get default tabs number to format scripts indentation
///
protected const int TABS_NUMBER = 2;
#endregion
#region Fields
protected readonly CurrencySettings _currencySettings;
protected readonly FacebookConversionsHttpClient _facebookConversionsHttpClient;
protected readonly ICategoryService _categoryService;
protected readonly ICountryService _countryService;
protected readonly ICurrencyService _currencyService;
protected readonly IGenericAttributeService _genericAttributeService;
protected readonly IHttpContextAccessor _httpContextAccessor;
protected readonly ILogger _logger;
protected readonly IOrderService _orderService;
protected readonly IOrderTotalCalculationService _orderTotalCalculationService;
protected readonly IPriceCalculationService _priceCalculationService;
protected readonly IProductService _productService;
protected readonly IRepository _facebookPixelConfigurationRepository;
protected readonly IShoppingCartService _shoppingCartService;
protected readonly IStateProvinceService _stateProvinceService;
protected readonly IStaticCacheManager _staticCacheManager;
protected readonly IStoreContext _storeContext;
protected readonly ITaxService _taxService;
protected readonly IWebHelper _webHelper;
protected readonly IWidgetPluginManager _widgetPluginManager;
protected readonly IWorkContext _workContext;
#endregion
#region Ctor
public FacebookPixelService(CurrencySettings currencySettings,
FacebookConversionsHttpClient facebookConversionsHttpClient,
ICategoryService categoryService,
ICountryService countryService,
ICurrencyService currencyService,
IGenericAttributeService genericAttributeService,
IHttpContextAccessor httpContextAccessor,
ILogger logger,
IOrderService orderService,
IOrderTotalCalculationService orderTotalCalculationService,
IPriceCalculationService priceCalculationService,
IProductService productService,
IRepository facebookPixelConfigurationRepository,
IShoppingCartService shoppingCartService,
IStateProvinceService stateProvinceService,
IStaticCacheManager staticCacheManager,
IStoreContext storeContext,
ITaxService taxService,
IWebHelper webHelper,
IWidgetPluginManager widgetPluginManager,
IWorkContext workContext)
{
_currencySettings = currencySettings;
_facebookConversionsHttpClient = facebookConversionsHttpClient;
_categoryService = categoryService;
_countryService = countryService;
_currencyService = currencyService;
_genericAttributeService = genericAttributeService;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_orderService = orderService;
_orderTotalCalculationService = orderTotalCalculationService;
_priceCalculationService = priceCalculationService;
_productService = productService;
_facebookPixelConfigurationRepository = facebookPixelConfigurationRepository;
_shoppingCartService = shoppingCartService;
_stateProvinceService = stateProvinceService;
_staticCacheManager = staticCacheManager;
_storeContext = storeContext;
_taxService = taxService;
_webHelper = webHelper;
_widgetPluginManager = widgetPluginManager;
_workContext = workContext;
}
#endregion
#region Utilities
///
/// Handle function and get result
///
/// Result type
/// Function
/// Whether to log errors
///
/// A task that represents the asynchronous operation
/// The task result contains the function result
///
protected async Task HandleFunctionAsync(Func> function, bool logErrors = true)
{
try
{
//check whether the plugin is active
if (!await PluginActiveAsync())
return default;
//invoke function
return await function();
}
catch (Exception exception)
{
if (!logErrors)
return default;
var customer = await _workContext.GetCurrentCustomerAsync();
if (customer.IsSearchEngineAccount() || customer.IsBackgroundTaskAccount())
return default;
//log errors
var error = $"{FacebookPixelDefaults.SystemName} error: {Environment.NewLine}{exception.Message}";
await _logger.ErrorAsync(error, exception, customer);
return default;
}
}
///
/// Check whether the plugin is active for the current user and the current store
///
///
/// A task that represents the asynchronous operation
/// The task result contains the result
///
protected async Task PluginActiveAsync()
{
var customer = await _workContext.GetCurrentCustomerAsync();
var store = await _storeContext.GetCurrentStoreAsync();
return await _widgetPluginManager.IsPluginActiveAsync(FacebookPixelDefaults.SystemName, customer, store.Id);
}
///
/// Prepare scripts
///
/// Enabled configurations
///
/// A task that represents the asynchronous operation
/// The task result contains the script code
///
protected async Task PrepareScriptsAsync(IList configurations)
{
return await PrepareInitScriptAsync(configurations) +
await PrepareUserPropertiesScriptAsync(configurations) +
await PreparePageViewScriptAsync(configurations) +
await PrepareTrackedEventsScriptAsync(configurations);
}
///
/// Prepare user info (used with Advanced Matching feature)
///
///
/// A task that represents the asynchronous operation
/// The task result contains the user info
///
protected async Task GetUserObjectAsync()
{
//prepare user object
var customer = await _workContext.GetCurrentCustomerAsync();
var email = customer.Email;
var firstName = customer.FirstName;
var lastName = customer.LastName;
var phone = customer.Phone;
var gender = customer.Gender;
var birthday = customer.DateOfBirth;
var city = customer.City;
var countryId = customer.CountryId;
var countryName = (await _countryService.GetCountryByIdAsync(countryId))?.TwoLetterIsoCode;
var stateId = customer.StateProvinceId;
var stateName = (await _stateProvinceService.GetStateProvinceByIdAsync(stateId))?.Abbreviation;
var zipcode = customer.ZipPostalCode;
return FormatEventObject(
[
("em", JavaScriptEncoder.Default.Encode(email?.ToLowerInvariant() ?? string.Empty)),
("fn", JavaScriptEncoder.Default.Encode(firstName?.ToLowerInvariant() ?? string.Empty)),
("ln", JavaScriptEncoder.Default.Encode(lastName?.ToLowerInvariant() ?? string.Empty)),
("ph", new string(phone?.Where(c => char.IsDigit(c)).ToArray()) ?? string.Empty),
("external_id", customer.CustomerGuid.ToString().ToLowerInvariant()),
("ge", gender?.FirstOrDefault().ToString().ToLowerInvariant()),
("db", birthday?.ToString("yyyyMMdd")),
("ct", JavaScriptEncoder.Default.Encode(city?.ToLowerInvariant() ?? string.Empty)),
("st", stateName?.ToLowerInvariant()),
("zp", JavaScriptEncoder.Default.Encode(zipcode?.ToLowerInvariant() ?? string.Empty)),
("cn", countryName?.ToLowerInvariant())
]);
}
///
/// Prepare script to init Facebook Pixel
///
/// Enabled configurations
///
/// A task that represents the asynchronous operation
/// The task result contains the script code
///
protected async Task PrepareInitScriptAsync(IList configurations)
{
//prepare init script
return await FormatScriptAsync(configurations, async configuration =>
{
var customer = await _workContext.GetCurrentCustomerAsync();
var additionalParameter = configuration.PassUserProperties
? $", {{uid: '{customer.CustomerGuid}'}}"
: (configuration.UseAdvancedMatching
? $", {await GetUserObjectAsync()}"
: null);
return $"fbq('init', '{configuration.PixelId}'{additionalParameter});";
});
}
///
/// Prepare script to pass user properties
///
/// Enabled configurations
///
/// A task that represents the asynchronous operation
/// The task result contains the script code
///
protected async Task PrepareUserPropertiesScriptAsync(IList configurations)
{
//filter active configurations
var activeConfigurations = configurations.Where(configuration => configuration.PassUserProperties).ToList();
if (!activeConfigurations.Any())
return string.Empty;
//prepare user object
var customer = await _workContext.GetCurrentCustomerAsync();
var createdOn = new DateTimeOffset(customer.CreatedOnUtc).ToUnixTimeSeconds().ToString();
var city = customer.City;
var countryId = customer.CountryId;
var countryName = (await _countryService.GetCountryByIdAsync(countryId))?.TwoLetterIsoCode;
var currency = (await _workContext.GetWorkingCurrencyAsync())?.CurrencyCode;
var gender = customer.Gender;
var language = (await _workContext.GetWorkingLanguageAsync())?.UniqueSeoCode;
var stateId = customer.StateProvinceId;
var stateName = (await _stateProvinceService.GetStateProvinceByIdAsync(stateId))?.Abbreviation;
var zipcode = customer.ZipPostalCode;
var userObject = FormatEventObject(
[
("$account_created_time", createdOn),
("$city", JavaScriptEncoder.Default.Encode(city ?? string.Empty)),
("$country", countryName),
("$currency", currency),
("$gender", gender?.FirstOrDefault().ToString()),
("$language", language),
("$state", stateName),
("$zipcode", JavaScriptEncoder.Default.Encode(zipcode ?? string.Empty))
]);
//prepare script
return await FormatScriptAsync(activeConfigurations, configuration =>
Task.FromResult($"fbq('setUserProperties', '{configuration.PixelId}', {userObject});"));
}
///
/// Prepare script to track "PageView" event
///
/// Enabled configurations
///
/// A task that represents the asynchronous operation
/// The task result contains the script code
///
protected async Task PreparePageViewScriptAsync(IList configurations)
{
//a single active configuration is enough to track PageView event
var activeConfigurations = configurations.Where(configuration => configuration.TrackPageView).Take(1).ToList();
return await FormatScriptAsync(activeConfigurations, configuration =>
Task.FromResult($"fbq('track', '{FacebookPixelDefaults.PAGE_VIEW}');"));
}
///
/// Prepare scripts to track events
///
/// Enabled configurations
///
/// A task that represents the asynchronous operation
/// The task result contains the script code
///
protected async Task PrepareTrackedEventsScriptAsync(IList configurations)
{
//get previously stored events and remove them from the session data
var events = (await _httpContextAccessor.HttpContext.Session
.GetAsync>(FacebookPixelDefaults.TrackedEventsSessionValue))
?? new List();
var store = await _storeContext.GetCurrentStoreAsync();
var customer = await _workContext.GetCurrentCustomerAsync();
var activeEvents = events.Where(trackedEvent =>
trackedEvent.CustomerId == customer.Id && trackedEvent.StoreId == store.Id)
.ToList();
await _httpContextAccessor.HttpContext.Session.SetAsync(
FacebookPixelDefaults.TrackedEventsSessionValue,
events.Except(activeEvents).ToList());
if (!activeEvents.Any())
return string.Empty;
return await activeEvents.AggregateAwaitAsync(string.Empty, async (preparedScripts, trackedEvent) =>
{
//filter active configurations
var activeConfigurations = trackedEvent.EventName switch
{
FacebookPixelDefaults.ADD_TO_CART => configurations.Where(configuration => configuration.TrackAddToCart).ToList(),
FacebookPixelDefaults.PURCHASE => configurations.Where(configuration => configuration.TrackPurchase).ToList(),
FacebookPixelDefaults.VIEW_CONTENT => configurations.Where(configuration => configuration.TrackViewContent).ToList(),
FacebookPixelDefaults.ADD_TO_WISHLIST => configurations.Where(configuration => configuration.TrackAddToWishlist).ToList(),
FacebookPixelDefaults.INITIATE_CHECKOUT => configurations.Where(configuration => configuration.TrackInitiateCheckout).ToList(),
FacebookPixelDefaults.SEARCH => configurations.Where(configuration => configuration.TrackSearch).ToList(),
FacebookPixelDefaults.CONTACT => configurations.Where(configuration => configuration.TrackContact).ToList(),
FacebookPixelDefaults.COMPLETE_REGISTRATION => configurations.Where(configuration => configuration.TrackCompleteRegistration).ToList(),
_ => new List()
};
if (trackedEvent.IsCustomEvent)
{
activeConfigurations = await configurations.WhereAwait(async configuration =>
(await GetCustomEventsAsync(configuration.Id)).Any(customEvent => customEvent.EventName == trackedEvent.EventName)).ToListAsync();
}
//prepare event scripts
return preparedScripts + await trackedEvent.EventObjects.AggregateAwaitAsync(string.Empty, async (preparedEventScripts, eventObject) =>
{
return preparedEventScripts + await FormatScriptAsync(activeConfigurations, configuration =>
{
//used for accurate event tracking with multiple Facebook Pixels
var actionName = configurations.Count > 1
? (trackedEvent.IsCustomEvent ? "trackSingleCustom" : "trackSingle")
: (trackedEvent.IsCustomEvent ? "trackCustom" : "track");
var additionalParameter = configurations.Count > 1 ? $", '{configuration.PixelId}'" : null;
//prepare event script
var eventObjectParameter = !string.IsNullOrEmpty(eventObject) ? $", {eventObject}" : null;
return Task.FromResult($"fbq('{actionName}'{additionalParameter}, '{trackedEvent.EventName}'{eventObjectParameter});");
});
});
});
}
///
/// Prepare script to track event and store it for the further using
///
/// Event name
/// Event object
/// Customer identifier
/// Store identifier
/// Whether the event is a custom one
/// A task that represents the asynchronous operation
protected async Task PrepareTrackedEventScriptAsync(string eventName, string eventObject,
int? customerId = null, int? storeId = null, bool isCustomEvent = false)
{
//prepare script and store it into the session data, we use this later
var customer = await _workContext.GetCurrentCustomerAsync();
customerId ??= customer.Id;
var store = await _storeContext.GetCurrentStoreAsync();
storeId ??= store.Id;
var events = await _httpContextAccessor.HttpContext.Session
.GetAsync>(FacebookPixelDefaults.TrackedEventsSessionValue)
?? new List();
var activeEvent = events.FirstOrDefault(trackedEvent =>
trackedEvent.EventName == eventName && trackedEvent.CustomerId == customerId && trackedEvent.StoreId == storeId);
if (activeEvent == null)
{
activeEvent = new TrackedEvent
{
EventName = eventName,
CustomerId = customerId.Value,
StoreId = storeId.Value,
IsCustomEvent = isCustomEvent
};
events.Add(activeEvent);
}
activeEvent.EventObjects.Add(eventObject);
await _httpContextAccessor.HttpContext.Session.SetAsync(FacebookPixelDefaults.TrackedEventsSessionValue, events);
}
///
/// Format event object to look pretty
///
/// Event object properties
/// Tabs number for indentation script
/// Script code
protected string FormatEventObject(List<(string Name, object Value)> properties, int? tabsNumber = null)
{
//local function to format list of objects
string formatObjectList(List> objectList)
{
var formattedList = objectList.Aggregate(string.Empty, (preparedObjects, propertiesList) =>
{
if (propertiesList != null)
{
var value = FormatEventObject(propertiesList, (tabsNumber ?? TABS_NUMBER) + 1);
preparedObjects += $"{Environment.NewLine}{new string('\t', (tabsNumber ?? TABS_NUMBER) + 1)}{value},";
}
return preparedObjects;
}).TrimEnd(',');
return $"[{formattedList}]";
}
//format single object
var formattedObject = properties.Aggregate(string.Empty, (preparedObject, property) =>
{
if (!string.IsNullOrEmpty(property.Value?.ToString()))
{
//format property value
var value = property.Value is string valueString
? $"'{valueString.Replace("'", "\\'")}'"
: (property.Value is List> valueList
? formatObjectList(valueList)
: (property.Value is decimal valueDecimal
? valueDecimal.ToString("F", CultureInfo.InvariantCulture)
: property.Value.ToString().ToLowerInvariant()));
//format object property
preparedObject += $"{Environment.NewLine}{new string('\t', (tabsNumber ?? TABS_NUMBER) + 1)}{property.Name}: {value},";
}
return preparedObject;
}).TrimEnd(',');
return $"{{{formattedObject}{Environment.NewLine}{new string('\t', tabsNumber ?? TABS_NUMBER)}}}";
}
///
/// Format script to look pretty
///
/// Enabled configurations
/// Function to get script for the passed configuration
///
/// A task that represents the asynchronous operation
/// The task result contains the script code
///
protected async Task FormatScriptAsync(IList configurations, Func> getScript)
{
if (!configurations.Any())
return string.Empty;
//format script
var formattedScript = await configurations.AggregateAwaitAsync(string.Empty, async (preparedScripts, configuration) =>
preparedScripts + Environment.NewLine + new string('\t', TABS_NUMBER) + await getScript(configuration));
formattedScript += Environment.NewLine;
return formattedScript;
}
///
/// Format custom event data to look pretty
///
/// Custom data
/// Script code
protected string FormatCustomData(ConversionsEventCustomData customData)
{
List<(string Name, object Value)> getProperties(JObject jObject)
{
var result = jObject.ToObject>();
foreach (var pair in result)
{
if (pair.Value is JObject nestedObject)
result[pair.Key] = getProperties(nestedObject);
if (pair.Value is JArray nestedArray && nestedArray.OfType().Any())
result[pair.Key] = nestedArray.OfType().Select(obj => getProperties(obj)).ToList();
}
return result.Select(pair => (pair.Key, pair.Value)).ToList();
}
try
{
var customDataObject = JObject.FromObject(customData, new JsonSerializer { NullValueHandling = NullValueHandling.Ignore });
return FormatEventObject(getProperties(customDataObject));
}
catch
{
//if something went wrong, just serialize the data without format
return JsonConvert.SerializeObject(customData, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}
}
///
/// Get configurations
///
/// Store identifier; pass 0 to load all records
///
/// A task that represents the asynchronous operation
/// The task result contains the list of configurations
///
protected async Task> GetConfigurationsAsync(int storeId = 0)
{
var key = _staticCacheManager.PrepareKeyForDefaultCache(FacebookPixelDefaults.ConfigurationsCacheKey, storeId);
var query = _facebookPixelConfigurationRepository.Table;
//filter by the store
if (storeId > 0)
query = query.Where(configuration => configuration.StoreId == storeId);
query = query.OrderBy(configuration => configuration.Id);
return await _staticCacheManager.GetAsync(key, async () => await query.ToListAsync());
}
///
/// Prepare Pixel script and send requests to Conversions API for the passed event
///
/// Function to prepare model
/// Event name
/// Store identifier; pass null to load records for the current store
///
/// A task that represents the asynchronous operation
/// The task result contains the value whether handling was successful
///
protected async Task HandleEventAsync(Func> prepareModel, string eventName, int? storeId = null)
{
storeId ??= (await _storeContext.GetCurrentStoreAsync()).Id;
var configurations = (await GetConfigurationsAsync(storeId ?? 0)).Where(configuration => eventName switch
{
FacebookPixelDefaults.ADD_TO_CART => configuration.TrackAddToCart,
FacebookPixelDefaults.ADD_TO_WISHLIST => configuration.TrackAddToWishlist,
FacebookPixelDefaults.PURCHASE => configuration.TrackPurchase,
FacebookPixelDefaults.VIEW_CONTENT => configuration.TrackViewContent,
FacebookPixelDefaults.INITIATE_CHECKOUT => configuration.TrackInitiateCheckout,
FacebookPixelDefaults.PAGE_VIEW => configuration.TrackPageView,
FacebookPixelDefaults.SEARCH => configuration.TrackSearch,
FacebookPixelDefaults.CONTACT => configuration.TrackContact,
FacebookPixelDefaults.COMPLETE_REGISTRATION => configuration.TrackCompleteRegistration,
_ => false
}).ToList();
var conversionsApiConfigurations = configurations.Where(configuration => configuration.ConversionsApiEnabled).ToList();
var pixelConfigurations = configurations.Where(configuration => configuration.PixelScriptEnabled).ToList();
if (!conversionsApiConfigurations.Any() && !pixelConfigurations.Any())
return false;
var model = await prepareModel();
if (pixelConfigurations.Any())
await PrepareEventScriptAsync(model);
var logErrors = true; //set it to false to ignore Conversions API errors
foreach (var configuration in conversionsApiConfigurations)
{
await HandleFunctionAsync(async () =>
{
var response = await _facebookConversionsHttpClient.SendEventAsync(configuration, model);
var error = JsonConvert.DeserializeAnonymousType(response, new { Error = new ApiError() })?.Error;
if (!string.IsNullOrEmpty(error?.Message))
throw new NopException($"{error.Code} - {error.Message}{Environment.NewLine}Debug ID: {error.DebugId}");
return true;
}, logErrors);
}
return true;
}
///
/// Prepare user data for conversions api
///
///
/// Customer
/// A task that represents the asynchronous operation
/// The task result contains the user data
///
protected async Task PrepareUserDataAsync(Customer customer = null)
{
//prepare user object
customer ??= await _workContext.GetCurrentCustomerAsync();
var twoLetterCountryIsoCode = (await _countryService.GetCountryByIdAsync(customer.CountryId))?.TwoLetterIsoCode;
var stateName = (await _stateProvinceService.GetStateProvinceByIdAsync(customer.StateProvinceId))?.Abbreviation;
var ipAddress = _webHelper.GetCurrentIpAddress();
var userAgent = _httpContextAccessor.HttpContext?.Request?.Headers[HeaderNames.UserAgent].ToString();
return new ConversionsEventUserData
{
EmailAddress = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.Email?.ToLowerInvariant() ?? string.Empty), "SHA256")],
FirstName = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.FirstName?.ToLowerInvariant() ?? string.Empty), "SHA256")],
LastName = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.LastName?.ToLowerInvariant() ?? string.Empty), "SHA256")],
PhoneNumber = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.Phone?.ToLowerInvariant() ?? string.Empty), "SHA256")],
ExternalId = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer?.CustomerGuid.ToString()?.ToLowerInvariant() ?? string.Empty), "SHA256")],
Gender = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.Gender?.FirstOrDefault().ToString() ?? string.Empty), "SHA256")],
DateOfBirth = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.DateOfBirth?.ToString("yyyyMMdd") ?? string.Empty), "SHA256")],
City = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.City?.ToLowerInvariant() ?? string.Empty), "SHA256")],
State = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(stateName?.ToLowerInvariant() ?? string.Empty), "SHA256")],
Zip = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(customer.ZipPostalCode?.ToLowerInvariant() ?? string.Empty), "SHA256")],
Country = [HashHelper.CreateHash(Encoding.UTF8.GetBytes(twoLetterCountryIsoCode?.ToLowerInvariant() ?? string.Empty), "SHA256")],
ClientIpAddress = ipAddress?.ToLowerInvariant(),
ClientUserAgent = userAgent?.ToLowerInvariant(),
Id = customer.Id
};
}
///
/// Prepare add to cart event model
///
/// Shopping cart item
///
/// A task that represents the asynchronous operation
/// The task result contains the ConversionsEvent model
///
protected async Task PrepareAddToCartEventModelAsync(ShoppingCartItem item)
{
ArgumentNullException.ThrowIfNull(item);
//check whether the shopping was initiated by the customer
var customer = await _workContext.GetCurrentCustomerAsync();
var store = await _storeContext.GetCurrentStoreAsync();
if (item.CustomerId != customer.Id)
throw new NopException("Shopping was not initiated by customer");
var eventName = item.ShoppingCartTypeId == (int)ShoppingCartType.ShoppingCart
? FacebookPixelDefaults.ADD_TO_CART
: FacebookPixelDefaults.ADD_TO_WISHLIST;
var product = await _productService.GetProductByIdAsync(item.ProductId);
var categoryMapping = (await _categoryService.GetProductCategoriesByProductIdAsync(product?.Id ?? 0)).FirstOrDefault();
var categoryName = (await _categoryService.GetCategoryByIdAsync(categoryMapping?.CategoryId ?? 0))?.Name;
var sku = product != null ? await _productService.FormatSkuAsync(product, item.AttributesXml) : string.Empty;
var quantity = product != null ? (int?)item.Quantity : null;
var (productPrice, _, _, _) = await _priceCalculationService.GetFinalPriceAsync(product, customer, store, includeDiscounts: false);
var (price, _) = await _taxService.GetProductPriceAsync(product, productPrice);
var currentCurrency = await _workContext.GetWorkingCurrencyAsync();
var priceValue = await _currencyService.ConvertFromPrimaryStoreCurrencyAsync(price, currentCurrency);
var currency = currentCurrency?.CurrencyCode;
var eventObject = new ConversionsEventCustomData
{
ContentCategory = categoryName,
ContentIds = [sku],
ContentName = product?.Name,
ContentType = "product",
Contents =
[
new
{
id = sku,
quantity = quantity,
item_price = priceValue
}
],
Currency = currency,
Value = priceValue
};
return new ConversionsEvent
{
Data =
[
new ConversionsEventDatum
{
EventName = eventName,
EventTime = new DateTimeOffset(item.CreatedOnUtc).ToUnixTimeSeconds(),
EventSourceUrl = _webHelper.GetThisPageUrl(true),
ActionSource = "website",
UserData = await PrepareUserDataAsync(customer),
CustomData = eventObject,
StoreId = item.StoreId
}
]
};
}
///
/// Prepare purchase event model
///
/// Order
///
/// A task that represents the asynchronous operation
/// The task result contains the ConversionsEvent model
///
protected async Task PreparePurchaseModelAsync(Order order)
{
ArgumentNullException.ThrowIfNull(order);
//check whether the purchase was initiated by the customer
var customer = await _workContext.GetCurrentCustomerAsync();
if (order.CustomerId != customer.Id)
throw new NopException("Purchase was not initiated by customer");
//prepare event object
var currency = await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId);
var contentsProperties = await (await _orderService.GetOrderItemsAsync(order.Id)).SelectAwait(async item =>
{
var product = await _productService.GetProductByIdAsync(item.ProductId);
var sku = product != null ? await _productService.FormatSkuAsync(product, item.AttributesXml) : string.Empty;
var quantity = product != null ? (int?)item.Quantity : null;
return new { id = sku, quantity = quantity };
}).Cast