diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 9764a97..bef4640 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -4,7 +4,7 @@ 2. [Shopify integration setup](#setup) ## Shopify integration -For the communication between Shopify and XByK, GraphQL Storefront API and REST Admin API is used. For the REST Admin API communication, [ShopifySharp](https://www.nuget.org/packages/ShopifySharp/) NuGet package is used as it provides all the necessary methods used in this integration. All the available Shopify APIs are documented [here](https://shopify.dev/docs/api). +For the communication between Shopify and XbyK, GraphQL Storefront API and REST Admin API is used. For the REST Admin API communication, [ShopifySharp](https://www.nuget.org/packages/ShopifySharp/) NuGet package is used as it provides all the necessary methods used in this integration. All the available Shopify APIs are documented [here](https://shopify.dev/docs/api). Class library consists of 2 main parts - Shopify products synchronization and the e-commerce integration. In this repository, there is also an example of standalone product listing widget. ### Product listing widget @@ -54,25 +54,62 @@ Step-by-step tutorial to create new pages: 3. Create new category page and in "Products" field, select all the product detail pages that should be included in the category. The selected products will be displayed as a products listing in the category page. #### Checkout -The next step from shopping cart preview page(`Shopify.ShoppingCartPage`) is redirection to official Shopify store checkout page where user can complete the checkout and create the order. Checkout completion using Shopify API is not possible as whole checkout API will be removed from Shopify. To redirect user from Shopify thank you page to DancingGoat, following javascript tag for redirection needs to be inserted into Shopify order status page (you can set it in Shopify administration via `Settings` -> `Checkout` -> `Order status page` ->`Additional scripts`): -```html - -``` -This script will redirect the user to the Xperience by Kentico Thank you page after 5 seconds. You can adjust the timespan and URL by modifying the `redirectionDelay` and `thankYouPageUrl` constants, respectively. The redirection will occur exclusively from the Shopify thank you page, ensuring users can still check their order status in the future. Query parameter `sourceId` is then used to retrieve created order, update XByK contact information based on the order information and log purchase activity. +The next step from shopping cart preview page(`Shopify.ShoppingCartPage`) is redirection to official Shopify store checkout page where user can complete the checkout and create the order. Checkout completion using Shopify API is not possible as whole checkout API will be removed from Shopify. + +#### Redirection back to DancingGoat site +After order is completed, users need to be redirected back to DancingGoat. To do this, [web pixels](https://shopify.dev/docs/apps/build/marketing-analytics/pixels) in combination with [liquid templates](https://shopify.dev/docs/api/liquid) needs to be used. + +Step-by-step tutorial: +1. Create custom web pixel in `Settings` -> `Customer events` -> `Add custom pixel`. In the web pixel, [checkout completed](https://shopify.dev/docs/api/web-pixels-api/standard-events/checkout_completed) event will be used to set `orderId` cookie. In `customer privacy` section, set `Permission` to `Not required` so the pixel will always run. Then, insert following code: + ```javascript + analytics.subscribe('checkout_completed', (event) => { + const orderId = event.data.checkout.order?.id; + document.cookie = "orderId=" + orderId + ";path=/"; + }); + ``` + Redirect inside the web pixel cannot be used since it runs inside an iframe that does not allow redirects. + +2. Add script to check for `orderId` cookie and redirect back to DancingGoat. Go to `Online store` -> `Themes`. Tap on three dots at your current theme and select `Edit code`. Go to `assets` folder and click `+ Add a new asset` -> `Create a blank file`. Select js extension and create new asset. To the new javascript file, add this code: + ```javascript + function getCookie(cookieName) { + let cookies = document.cookie; + let cookieArray = cookies.split("; "); + + for (let i = 0; i < cookieArray.length; i++) { + let cookie = cookieArray[i]; + let [name, value] = cookie.split("="); + + if (name === cookieName) { + return decodeURIComponent(value); + } + } + + return null; + } + + function deleteCookie(cookieName) { + document.cookie = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + } + + const orderCookieName = "orderId"; + const orderId = getCookie(orderCookieName); + + if (orderId) { + deleteCookie(orderCookieName); + + /// Replace with absolute URL of your XbyK thank you page + const thankYouPageUrl = "https://my-dancing-goat.com/thank-you"; + window.location.href = thankYouPageUrl + "?orderId=" + orderId; + } + ``` + When the order is completed and user will go back to the store(for example by pressing the 'Continue shopping' button), this javascript will be executed and if `orderId` cookie exists, user will be redirected back to the DancingGoat site and the cookie will be removed(to prevent further redirections). Query parameter `orderId` is then used to retrieve created order, update XbyK contact information based on the order information and log purchase activity. + +3. Last step is to add this javascript to the layout. Find `theme.liquid` file inside `layout` folder and add file created in previous step to the `head` HTML element. + ```html + + + ``` + ### Store activity tracking The integration allows you to log product-related [activities](https://docs.kentico.com/x/oYPWCQ) via the `IEcommerceActivityLogger` service. The service provides tracking methods for the following events: diff --git a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyThankYouController.cs b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyThankYouController.cs index 40a070d..c6ac22d 100644 --- a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyThankYouController.cs +++ b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyThankYouController.cs @@ -40,14 +40,20 @@ public ShopifyThankYouController( } - public async Task Index(string sourceId) + public async Task Index(long orderId) { var cart = await shoppingService.GetCurrentShoppingCart(); - var order = await orderService.GetRecentOrder(sourceId); + var order = await orderService.GetOrder(orderId); + if (order != null && cart != null) { UpdateCurrentContact(order); LogPurchaseActivity(order, cart); + } + + if (order == null) + { + return NotFound(); } shoppingService.RemoveCurrentShoppingCart(); diff --git a/src/Kentico.Xperience.Shopify/Orders/IShopifyOrderService.cs b/src/Kentico.Xperience.Shopify/Orders/IShopifyOrderService.cs index 8a1e543..1bf1e7e 100644 --- a/src/Kentico.Xperience.Shopify/Orders/IShopifyOrderService.cs +++ b/src/Kentico.Xperience.Shopify/Orders/IShopifyOrderService.cs @@ -8,11 +8,10 @@ namespace Kentico.Xperience.Shopify.Orders public interface IShopifyOrderService { /// - /// Get order from shopify by order source identifier. + /// Get order from shopify by order identifier. /// - /// Order source identifier. - /// WARNING: Method looks for orders no older than 1 day. + /// Order identifier. /// Retrieved Shopify order if found. - Task GetRecentOrder(string sourceId); + Task GetOrder(long orderId); } } diff --git a/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs b/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs index 3efc843..a7d0fdc 100644 --- a/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs +++ b/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs @@ -1,43 +1,36 @@ -using Kentico.Xperience.Shopify.Config; -using Kentico.Xperience.Shopify.Products; - -using ShopifySharp; -using ShopifySharp.Factories; -using ShopifySharp.Filters; - -namespace Kentico.Xperience.Shopify.Orders -{ - internal class ShopifyOrderService : ShopifyServiceBase, IShopifyOrderService - { - private const string ORDERS_FIELDS = "customer,source_identifier,name,order_status_url,id,line_items,total_price_set,presentment_currency"; - - private readonly IOrderService orderService; - - public ShopifyOrderService( - IOrderServiceFactory orderServiceFactory, - IShopifyIntegrationSettingsService integrationSettingsService) : base(integrationSettingsService) - { - orderService = orderServiceFactory.Create(shopifyCredentials); - } - - - /// - public async Task GetRecentOrder(string sourceId) - { - if (string.IsNullOrEmpty(sourceId)) - { - return null; - } - - var filter = new OrderListFilter() - { - CreatedAtMin = DateTime.Now.AddDays(-1).Date, - Fields = ORDERS_FIELDS - }; - - var result = await orderService.ListAsync(filter); - - return result.Items.FirstOrDefault(x => x.SourceIdentifier.Equals(sourceId, StringComparison.Ordinal)); - } - } -} +using Kentico.Xperience.Shopify.Config; +using Kentico.Xperience.Shopify.Products; + +using ShopifySharp; +using ShopifySharp.Factories; + +namespace Kentico.Xperience.Shopify.Orders +{ + internal class ShopifyOrderService : ShopifyServiceBase, IShopifyOrderService + { + private const string ORDER_FIELDS = "customer,source_identifier,name,order_status_url,id,line_items,total_price_set,presentment_currency"; + + private readonly IOrderService orderService; + + public ShopifyOrderService( + IOrderServiceFactory orderServiceFactory, + IShopifyIntegrationSettingsService integrationSettingsService) : base(integrationSettingsService) + { + orderService = orderServiceFactory.Create(shopifyCredentials); + } + + + /// + public async Task GetOrder(long orderId) + { + if (orderId == 0) + { + return null; + } + + return await TryCatch( + async () => await orderService.GetAsync(orderId, ORDER_FIELDS), + () => null); + } + } +}