Skip to content

Commit

Permalink
Merge pull request #21 from Kentico/feat/update-user-redirection
Browse files Browse the repository at this point in the history
Feat/update user redirection
  • Loading branch information
michalJakubis authored Sep 26, 2024
2 parents 176a72a + 204a561 commit bd30b62
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 69 deletions.
77 changes: 57 additions & 20 deletions docs/Usage-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
<script>
/// Replace with delay in ms
const redirectionDelay = 5000;
/// Replace with absolute URL of your XByK thank you page
const thankYouPageUrl = "https://my-dancing-goat.com/thank-you";
window.setTimeout(function(){
var urlPart = "/checkouts/";
var currentUrl = window.location.href;
if (!currentUrl.includes(urlPart)) {
return;
}
var sourceId= currentUrl.split(urlPart)[1].split("/")[1];
window.location.href = thankYouPageUrl + "?sourceId=" + sourceId;
}, redirectionDelay);
</script>
```
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
<!-- Change redirect.js to filename created in previous step -->
<script src="{{ 'redirect.js' | asset_url }}" defer="defer"></script>
```


### 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ public ShopifyThankYouController(
}


public async Task<IActionResult> Index(string sourceId)
public async Task<IActionResult> 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();
Expand Down
7 changes: 3 additions & 4 deletions src/Kentico.Xperience.Shopify/Orders/IShopifyOrderService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ namespace Kentico.Xperience.Shopify.Orders
public interface IShopifyOrderService
{
/// <summary>
/// Get order from shopify by order source identifier.
/// Get order from shopify by order identifier.
/// </summary>
/// <param name="sourceId">Order source identifier.</param>
/// <remarks>WARNING: Method looks for orders no older than 1 day.</remarks>
/// <param name="orderId">Order identifier.</param>
/// <returns>Retrieved Shopify order if found.</returns>
Task<Order?> GetRecentOrder(string sourceId);
Task<Order?> GetOrder(long orderId);
}
}
79 changes: 36 additions & 43 deletions src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs
Original file line number Diff line number Diff line change
@@ -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);
}


/// <inheritdoc/>
public async Task<Order?> 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);
}


/// <inheritdoc/>
public async Task<Order?> GetOrder(long orderId)
{
if (orderId == 0)
{
return null;
}

return await TryCatch<Order?>(
async () => await orderService.GetAsync(orderId, ORDER_FIELDS),
() => null);
}
}
}

0 comments on commit bd30b62

Please sign in to comment.