diff --git a/README.md b/README.md
index b937c6e..b6474a0 100644
--- a/README.md
+++ b/README.md
@@ -3,39 +3,74 @@
[![Kentico Labs](https://img.shields.io/badge/Kentico_Labs-grey?labelColor=orange&logo=data:image/svg+xml;base64,PHN2ZyBjbGFzcz0ic3ZnLWljb24iIHN0eWxlPSJ3aWR0aDogMWVtOyBoZWlnaHQ6IDFlbTt2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO2ZpbGw6IGN1cnJlbnRDb2xvcjtvdmVyZmxvdzogaGlkZGVuOyIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik05NTYuMjg4IDgwNC40OEw2NDAgMjc3LjQ0VjY0aDMyYzE3LjYgMCAzMi0xNC40IDMyLTMycy0xNC40LTMyLTMyLTMyaC0zMjBjLTE3LjYgMC0zMiAxNC40LTMyIDMyczE0LjQgMzIgMzIgMzJIMzg0djIxMy40NEw2Ny43MTIgODA0LjQ4Qy00LjczNiA5MjUuMTg0IDUxLjIgMTAyNCAxOTIgMTAyNGg2NDBjMTQwLjggMCAxOTYuNzM2LTk4Ljc1MiAxMjQuMjg4LTIxOS41MnpNMjQxLjAyNCA2NDBMNDQ4IDI5NS4wNFY2NGgxMjh2MjMxLjA0TDc4Mi45NzYgNjQwSDI0MS4wMjR6IiAgLz48L3N2Zz4=)](https://github.com/Kentico/.github/blob/main/SUPPORT.md#labs-limited-support)
[![CI: Build and Test](https://github.com/Kentico/xperience-by-kentico-ecommerce/actions/workflows/ci.yml/badge.svg)](https://github.com/Kentico/xperience-by-kentico-ecommerce/actions/workflows/ci.yml)
+**This integration is currently a Proof of Concept (PoC). For further details, please refer to the [Support](#support) section and the [KenticoLabs](https://github.com/Kentico/.github/blob/main/SUPPORT.md#labs-limited-support) tag associated with this feature.**
+
+| Name | Package |
+| ------------- |:-------------:|
+| Kentico.Xperience.K13Ecommerce | [![NuGet Package](https://img.shields.io/nuget/v/Kentico.Xperience.K13Ecommerce.svg)](https://www.nuget.org/packages/Kentico.Xperience.K13Ecommerce) |
+| Kentico.Xperience.Store.Rcl | [![NuGet Package](https://img.shields.io/nuget/v/Kentico.Xperience.Store.Rcl.svg)](https://www.nuget.org/packages/Kentico.Xperience.Store.Rcl) |
+| Kentico.Xperience.StoreApi | [![NuGet Package](https://img.shields.io/nuget/v/Kentico.Xperience.StoreApi.svg)](https://www.nuget.org/packages/Kentico.Xperience.StoreApi) |
+
+
## Description
-Repository contains solution with Xperience By Kentico integration to Kentico Xperience 13 E-Commerce features
-to create E-Commerce solution on XByK.
-Currently there are 2 solutions:
-- Kentico.Xperience.K13Ecommerce.sln
- - It shows the possibility of XbyK integration on Kentico 13 E-Commerce solution.
- - Consists of these parts:
- - Library for Kentico 13 that exposes a REST API for an E-Commerce site (Kentico.Xperience.StoreApi).
- - Library for XbyK connecting to the REST Store API running under Kentico 13 (Kentico.Xperience.K13Ecommerce)
- - Razor Class Library for selector components (Kentico.Xperience.Store.Rcl)
- - Sample Dancing Goat sites
- - DancingGoat.csproj - XbyK Dancing Goat enriched with integration to KX 13 Dancing Goat to show how to
-create simple e-shop with product listing, product detail and checkout process on XByK
- - Kentico13_DancingGoat.csproj - KX 13 Dancing Goat example with configured Store API to show you how you can setup
-REST Store API on you own KX 13 e-commerce solution
-- Kentico.Xperience.K13Ecommerce.Libs.sln
- - Contains only libraries without sample sites
-
-Solution covers several scenarios according to the complexity of integration between XByK and KX 13:
-- [Product listing widget example](./examples/DancingGoat-K13Ecommerce/Components/Widgets/Store/ProductListWidget)
- - Used to display products directly from the KX 13, purchase itself still takes place
- on Kentico 13
-- Full scale e-commerce solution
- - Product data (with variants and images) are synchronized to Content hub (can be turned off)
- - Product listing, detail and checkout process is placed on XbyK (shopping cart is calculated still on KX 13)
- - Linking products to categories in Pages channels needs to be done manually from Content hub.
- Page types are prepared to CI restore, details info in [this section of User Guide](./docs/Usage-Guide.md#dancing-goat-example---setup).
-
-## Screenshots
-
-![Cart content](./images/screenshots/cart_content.png "Cart content")
-![Products in content hub](./images/screenshots/products_content_hub.png "Products in content hub")
+This integration is primary intended for existing Kentico 13 (KX 13) E-Commerce projects to enable them to migrate
+to new Xperience By Kentico (XbyK) and still use KX 13 E-Commerce functionality.\
+It can also be used as a basis for new projects where E-Commerce data will be stored on KX 13, but further development is necessary to achieve this goal.
+
+This solution covers several scenarios according to the complexity of integration between XByK and KX 13:
+
+### Product listing widget
+- We recommend to use this widget for simple scenarios such as Landing page offers, etc.
+- [Product listing widget example](./examples/DancingGoat-K13Ecommerce/Components/Widgets/Store/ProductListWidget/StoreProductListWidgetViewComponent.cs)
+is located in [Dancing Goat XbyK example project](./examples/DancingGoat-K13Ecommerce).
+- The widget is used to display products directly from KX 13, purchase itself still takes place on Kentico 13.
+- The widget has a couple of properties based on the [Store property selector](./src/Kentico.Xperience.K13Ecommerce/Components/FormComponents/KenticoStorePropertySelector/KenticoStorePropertySelectorComponent.cs) which enable to display products for given category, culture and currency.
+
+![Product listing widget](./images/screenshots/product_listing_widget.png "Product listing widget")
+### Full scale e-commerce solution
+ - We recommend to use for possible partial migration of existing e-commerce projects from KX 13 to XbyK.
+ - Product data (with variants and images) are [synchronized to Content hub](./docs/Usage-Guide.md#products-synchronization) (can be [turned off](./docs/Usage-Guide.md#setup-1)).
+ ![Products in content hub](./images/screenshots/products_content_hub.png "Products in content hub")
+ - Product listing, detail and checkout process are placed on XbyK (shopping cart is saved and calculated still on KX 13).
+ ![Cart content](./images/screenshots/cart_content.png "Cart content")
+ - Orders are created from cart, order related data are saved on KX 13 side.
+ - Linking products to categories in Pages channels need to be done manually from Content hub.
+ Page types are prepared to CI restore, details info in [this section of User Guide](./docs/Usage-Guide.md#dancing-goat-example---setup).
+ ![Store pages](./images/screenshots/store_pages.png "Store pages")
+ - [Sample XbyK Dancing Goat site](./examples/DancingGoat-K13Ecommerce) implements store functionality and can be used as an example of migration of existing e-commerce projects to new XbyK.
+
+ There are a couple of services which cover these actions:
+ - Listing products based on parameters, product categories, prices and inventory
+ - Actions with shopping cart, changing currency and order creation
+ - Listing of orders (currently suitable for implementing listing orders in administration, not in My account)
+ - **Order updates and listing for specific customers are under development**
+ - Listing site cultures and currencies
+ - Check [this part of User Guide](./docs/Usage-Guide.md#kx-13-e-commerce-integration-in-xperience-by-kentico) for more specific description
+
+
+### Project structure
+
+There are currently 2 solutions:
+
+#### Kentico.Xperience.K13Ecommerce.sln
+
+Complete solution with all libraries and sample sites:
+
+- `Kentico.Xperience.StoreApi` - library for Kentico 13 that exposes a [REST API](./docs/Usage-Guide.md#store-api-kentico-xperience-13) for an E-Commerce site.
+- `Kentico.Xperience.K13Ecommerce` - library for XbyK connecting to the REST Store API running under Kentico 13.
+- `Kentico.Xperience.Store.Rcl` - Razor Class Library for selector components.
+- Sample Dancing Goat sites
+ - `DancingGoat.csproj` - XbyK Dancing Goat (live site channel + administration) enriched with integration to KX 13 Dancing Goat to show how to
+create simple e-shop with product listing, product detail and checkout process on XByK.
+ - `Kentico13_DancingGoat.csproj` - KX 13 Dancing Goat example (live site) with configured Store API to demonstrate how you can set up
+REST Store API on your own KX 13 e-commerce solution.
+- **KX 13 administration project (CMSApp) is not part of this solution!**
+
+![Project diagram](./images/project_diagram.png "Project diagram")
+
+#### Kentico.Xperience.K13Ecommerce.Libs.sln
+ - Contains only libraries without sample sites, used for publishing of nuget packages
## Library Version Matrix
@@ -52,8 +87,6 @@ Summary of libraries which are supported by the following versions Xperince by K
### Dependencies
-#### Kentico 13 E-Commerce
-
Xperience by Kentico application:
- [ASP.NET Core 8.0](https://dotnet.microsoft.com/en-us/download)
- [Xperience by Kentico](https://docs.xperience.io/xp/changelog)
@@ -64,7 +97,7 @@ Kentico Xperience 13 application (or standalone API app):
## Package Installation
-### Kentico Xperience 13 E-Commerce integration
+### Xperience by Kentico
Add these packages to your XbyK application using the .NET CLI
@@ -73,20 +106,22 @@ dotnet add package Kentico.Xperience.K13Ecommerce
dotnet add package Kentico.Xperience.Store.Rcl
```
+### Kentico Xperience 13
+
Add this package to your Kentico Xperience 13 ASP.NET.Core application (live site or create standalone application
when your KX 13 live site is not running)
```powershell
dotnet add package Kentico.Xperience.StoreApi
```
-
+
## Quick Start
-### Kentico Xperience 13 E-Commerce integration
+### Kentico Xperience 13
-**First setup your Kentico 13 ASP.NET Core application**:
+**First set up your Kentico 13 ASP.NET Core application**:
-1. Setup your own settings for Store REST API authentication (based on JWT and OAuth client credentials flow)
+1. Set up your own [settings](.\examples\Kentico13_DancingGoatStore\appsettings.json) for Store REST API authentication (based on JWT and OAuth client credentials flow)
```json
{
"CMSStoreApi": {
@@ -102,7 +137,7 @@ dotnet add package Kentico.Xperience.StoreApi
}
```
-2. Add Store API services to application services and configure Swagger
+2. Add [Store API services](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/Kentico13_DancingGoatStore/Startup.cs#L130) to application services and configure Swagger
```csharp
// Startup.cs
@@ -122,9 +157,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
}
```
-**Then setup your Xperience By Kentico application**
+### Xperience By Kentico
+
+**Then set up your Xperience By Kentico application**
-1. Fill settings to connect your Kentico Xperience 13 instance
+1. Fill [settings](.\examples\DancingGoat-K13Ecommerce\appsettings.json) to connect your Kentico Xperience 13 instance
```json
{
"CMSKenticoStoreConfig": {
@@ -136,17 +173,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
}
}
```
-2. Add K13Ecommerce library to the application services
+2. Add K13Ecommerce library to the [application services](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/DancingGoat-K13Ecommerce/Program.cs#L61)
```csharp
// Program.cs
// Registers Kentico Store API and services for e-commerce support
builder.Services.AddKenticoStoreServices(builder.Configuration);
```
-3. For most simple scenario: copy product listing widget from Dancing Goat example project to your project and configure
-properties to display products from Kentico 13. Sample widget is located [here](./examples/DancingGoat-K13Ecommerce/Components/Widgets/Store/ProductListWidget).
-4. For more complex scenario with full e-shop, you can inspire how Dancing Goat sample Store on XbyK is implemented.
-Check [Usage guide](./docs/Usage-Guide.md#store-setup) for detailed instructions how to configure categories, products and cart steps.
+3. For the simplest scenario: copy [product listing widget](./examples/DancingGoat-K13Ecommerce/Components/Widgets/Store/ProductListWidget) from Dancing Goat example project to your project and configure
+properties to display products from KX 13.
+4. For more complex scenario with full e-shop, you can be inspired by implementation of [Dancing Goat sample Store](./examples/DancingGoat-K13Ecommerce) on XbyK.
+Check [Usage guide](./docs/Usage-Guide.md#dancing-goat-example---setup) for detailed instructions to configure categories, products and cart steps.
5. Restore CI repository files to database (reusable content types, custom activities). CI files are located in
`.\examples\DancingGoat-K13Ecommerce\App_Data\CIRepository\` and you need to copy these files to your application.
```powershell
diff --git a/docs/Kentico.Xperience.K13Ecommerce.argr b/docs/Kentico.Xperience.K13Ecommerce.argr
new file mode 100644
index 0000000..72c8ff4
--- /dev/null
+++ b/docs/Kentico.Xperience.K13Ecommerce.argr
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md
index abb95db..612e445 100644
--- a/docs/Usage-Guide.md
+++ b/docs/Usage-Guide.md
@@ -1,37 +1,44 @@
# Usage Guide
+This user guide covers more detailed instructions for Xperience By Kentico (XbyK) integration to Kentico Xperience 13 (KX 13) E-Commerce features
+to create E-Commerce solution on XbyK.
+
## Table of contents
1. [Store API (Kentico Xperience 13)](#store-api-kentico-xperience-13)
2. [K13 Ecommerce integration (Xperience by Kentico)](#k13-ecommerce-integration-in-xperience-by-kentico)
3. [Dancing Goat example - setup](#dancing-goat-example---setup)
+4. [Scenarios](#scenarios)
## Store API (Kentico Xperience 13)
Store API (library `Kentico.Xperience.StoreApi`) is REST API which exposes KX 13 E-Commerce features to consume then from another sources
(primary intended for Xperience By Kentico, but you are completely free to use it any way you want).
-API is exposed via Swagger (Open API 3 standard) on relative path `/swagger/storeapi/swagger.json`
+API is exposed via [Swagger](https://swagger.io/) ([Open API 3 standard](https://swagger.io/specification/)) on relative path `/swagger/storeapi/swagger.json`
+
+> **_NOTE:_** To list all current endpoints with their request and response formats, run example project `Kentico13_DancingGoat`
+> and go to address [Project_URL]/swagger with Swagger UI tool.
We recommend to use this API in combination with `Kentico.Xperience.K13Ecommerce` [library for XByK applications](#k13-ecommerce-integration-in-xperience-by-kentico),
-because there are services to simplify e-commerce integration (like `IShoppingService`) and NSwag API client is
+because there are services to simplify e-commerce integration (such as `IShoppingService`) and [NSwag API client](https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?view=aspnetcore-8.0&tabs=visual-studio) is
already generated there.
### Authentication
-API is intended to use with OAuth 2.0 client credentials flow, when ClientId and ClientSecret is shared between
-client application (XByK) and KX 13 application. Access tokens are generated in JWT standard (from endpoint `/api/store/auth/token`).
+API is intended to use with [OAuth 2.0 client credentials flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4), when ClientId and ClientSecret are shared between
+client application (XByK) and KX 13 application. Access tokens are generated in [JWT standard](https://jwt.io/introduction) (from endpoint `/api/store/auth/token`).
Token request can contain `username` parameter to identify for which user token is generated.
-This user name is validated that exists and then embedded in token as `sub` and `name` claims. All subsequent
-requests needs to be sent with Bearer token in Authorization header.
+This user name's existence is validated and then embedded in token as `sub` and `name` claims. All subsequent
+requests need to be [sent with Bearer token](https://www.dofactory.com/code-examples/csharp/authorization-header) in [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) header.
All API controllers are secured by custom authorization attribute and filter `AuthorizeStore`. This filter checks
-user claim and when this user exists and is enabled, then is assigned to `MembershipContext.AuthenticatedUser`. When
+user claim and when this user exists and is enabled, is then assigned to `MembershipContext.AuthenticatedUser`. When
specific user name isn't provided, AuthenticatedUser remains as public user.
### Products
These endpoints have prefix `/api/store/products` and cover these domains:
-- Getting product pages based on parameters (returned data can be [customized](#todo))
+- Getting product pages based on parameters (returned data can be [customized](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/Kentico13_DancingGoatStore/Startup.cs#L134))
- Getting all product categories for given culture
- Getting prices and inventory info
@@ -40,12 +47,12 @@ These endpoints have prefix `/api/store/products` and cover these domains:
These endpoints have prefix `api/store/cart` and cover work with current shopping cart. Many actions correspond
to functionality in KX 13 `CMS.Ecommerce.IShoppingService` (adding/removing items to cart, set delivery data, creating order etc.).
All endpoints use `ShoppingCartGUID` parameter sent
-in HTTP header to identify current shopping cart. Client application needs to manage this identifier (this already covers
-`Kentico.Xperience.K13Ecommerce` library for XByK applications).
+in HTTP header to identify current shopping cart. Client application (XbyK) needs to manage this identifier (this already covers
+`Kentico.Xperience.K13Ecommerce` library for XByK applications).
All calls internally use IShoppingService with some
noticeable customizations to handle [retrieving cart](https://docs.kentico.com/13/e-commerce-features/customizing-on-line-stores/shopping-cart-related-customizing/retrieving-the-current-shopping-cart) in RESTful manner.
-This customizations are applied only on request with `api/store` prefix to not break default e-commerce functionality:
+These customizations are applied only on request with `api/store` prefix to not break default e-commerce functionality:
- Custom `IShoppingCartCache` - session usage is removed, cache key for cart's cache token identifier (`jti` claim) is
used instead.
So cache duration is also determined by current token expiration time and very short time for token expiration can cause
@@ -53,18 +60,18 @@ more frequent retrieving from database.
- Custom `ICurrentShoppingCartService` - session and cookie access is removed, current shopping cart is retrieved from
`ShoppingCartGUID` header value.
-In all API responses current `ShoppingCartGuid` is always sent to ensure correct shopping cart is always saved on client
+In all API responses current `ShoppingCartGuid` is always sent to ensure correct shopping cart is always saved on client application (XbyK)
in cases like user log in/log out.
#### Discounts
All KX 13 discounts and coupon codes are supported.
#### Currencies
-By default shopping is calculated in main site's currency. Cart's currency can be changes via `api/store/cart/set-currency`.
+By default shopping is calculated in main site's currency. Cart's currency can be changed via `api/store/cart/set-currency`.
All enabled currencies can be retrieved from `api/store/site/currencies`.
#### Current known limitations
-Not all cart's data can be changed, f.e. custom data (properties like ShoppingCartCustomData) cannot be currenly changed
+Not all cart's data can be changed, e.g. custom data (properties like ShoppingCartCustomData) cannot be currently changed
via API.
### Orders
@@ -78,18 +85,25 @@ via API.
- Endpoint `api/store/site/currencies` returns all enabled site currencies
### User synchronization
+When user is created on XbyK, this user needs to be synchronized to KX 13, then user can be used for API authorization.
+(user identity is generated in JWT).
+We suppose that users are already synchronized between client (XbyK) and KX app before starting using this API.
+Complete synchronization is not part of this PoC solution.
+
- Endpoint `api/store/synchronization/user-synchronization` creates new user
- - Client app should use this to ensure all new users on client's are synchronized to KX 13, this is necessary when client's
+ - Client app (XbyK) should use this to be ensured that all new users on client's are synchronized to KX 13, this is necessary when client's
e-commerce solution allows users to log in. Users are created with random generated password and are used only for
API authorization and assigning to MembershipContext.
+> **_NOTE:_** Please implement double opt-in mechanism for user registration to ensure users's are paired safely between
+> XbyK and KX 13. In current Dancing Goat example, we dont't have double opt-in mechanism implemented, but we recommend it as best practice.
+
#### Current known limitations
-User's roles synchronization isn't currently supported. We assume before start of using this API, users are already synchronized
-between client and KX app.
+User's roles synchronization isn't currently supported. We assume users to be already synchronized between client (XbyK) and KX app before starting using this API.
### Setup
-**How to setup your Kentico 13 ASP.NET Core application**:
+**How to set up your Kentico 13 ASP.NET Core application**:
Add this package to your Kentico Xperience 13 ASP.NET.Core application (live site or create standalone application
when your KX 13 live site is not running)
@@ -98,7 +112,7 @@ when your KX 13 live site is not running)
dotnet add package Kentico.Xperience.StoreApi
```
-1. Setup your own settings for Store REST API authentication (based on JWT and OAuth client credentials flow)
+1. Set up your own [settings](..\examples\Kentico13_DancingGoatStore\appsettings.json) for Store REST API authentication (based on JWT and OAuth client credentials flow)
```json
{
"CMSStoreApi": {
@@ -117,16 +131,16 @@ dotnet add package Kentico.Xperience.StoreApi
| Setting | Description |
|--------------------------------|-------------------------------------------------------------------------------|
-| CMSStoreApi:Jwt:Key | Your unique secret key for signing JWT access tokens (at least 64 chars long) |
-| CMSStoreApi:Jwt:Issuer | Fill arbitrary value for this claim (like your domain) |
-| CMSStoreApi:Jwt:Audience | Fill arbitrary value for this claim to identify recipients |
-| CMSStoreApi:Jwt:TokenExpiresIn | Duration in minutes for token validity |
-| ClientId | Fill your value, used for getting token (client credentials OAuth 2.0 flow) |
-| ClientSecret | Fill your value, used for getting token (client credentials OAuth 2.0 flow) |
+| Jwt:Key | Your unique secret key for signing JWT access tokens (at least 64 chars long) |
+| Jwt:Issuer | Fill arbitrary value for this claim (as your domain) |
+| Jwt:Audience | Fill arbitrary value for this claim to identify recipients |
+| Jwt:TokenExpiresIn | Duration in minutes for token validity |
+| ClientId | Fill your value, used for getting token (client credentials OAuth 2.0 flow) |
+| ClientSecret | Fill your value, used for getting token (client credentials OAuth 2.0 flow) |
-2. Add Store API services to application services and configure Swagger
+2. Add [Store API services](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/Kentico13_DancingGoatStore/Startup.cs#L130) to application services and configure Swagger
```csharp
// Startup.cs
@@ -144,6 +158,26 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
//Registers Swagger endpoint middleware and swagger UI
app.UseStoreApiSwagger();
}
+```
+or in [Minimal API](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/overview?view=aspnetcore-8.0) approach:
+```csharp
+// Program.cs
+
+var builder = WebApplication.CreateBuilder(args);
+
+// ...
+//Store API registration
+builder.Services.AddKenticoStoreApi();
+//Registers Swagger generation
+builder.Services.AddKenticoStoreApiSwagger();
+
+var app = builder.Build();
+
+//Registers Swagger endpoint middleware and swagger UI
+app.UseStoreApiSwagger();
+
+app.Run();
+
```
### Library matrix
@@ -152,25 +186,42 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
|------------------------------------|-------------------| --------------- |-------------|
| Kentico.Xperience.StoreApi | \>= 13.0.131 | 1.0.0 | \>= .NET 6 |
-## K13 Ecommerce integration in Xperience By Kentico
+## KX 13 E-Commerce integration in Xperience By Kentico
Library `Kentico.Xperience.K13Ecommerce` encapsulates Store API calls and exposes several services for KX 13 e-commerce
integration on XByK:
- `IProductService`
- Listing products based on parameters, product categories, prices and inventory
+ - Service is used e.g in [product synchronization](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/src/Kentico.Xperience.K13Ecommerce/Synchronization/Products/ProductSynchronizationService.cs#L36)
+ or [in product list](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/DancingGoat-K13Ecommerce/Controllers/KStore/StoreCategoryController.cs#L48) where prices are retrieved for listed products.
- `IShoppingService`
- - Actions on shopping cart and order creation
+ - Actions on shopping cart and order creation:
+ - Adding/removing products to/from cart
+ - Updating product quantity in cart
+ - Adding/removing coupon codes
+ - Retrieving cart content and delivery details
+ - Retrieving current customer
+ - Set shipping option and payment option
+ - Set customer, billing and shipping address
+ - Validate cart items
+ - Change cart's currency
+ - Create order
- Service saves and retrieves the shopping cart identifier (`ShoppingCartGuid`) to session (uses `IShoppingCartSessionStorage`)
and to browser cookie (uses `IShoppingCartClientStorage`)
+ - See [CheckoutController in Dancing Goat example](../examples/DancingGoat-K13Ecommerce/Controllers/KStore/CheckoutController.cs)
+ where checkout process is implemented using this service.
- `ICustomerService`
- List of customer addresses
+ - Service is used e.g. in [CheckoutService in Dancing Goat example](../examples/DancingGoat-K13Ecommerce/Services/CheckoutService.cs)
+ where customer's addresses are retrieved in cart's second step.
- `IOrderService`
- List of orders - currently suitable for implementing listing orders in administration
- `ISiteStoreService`
- - Site cultures and currencies
+ - Use for retrieving site's [list of enabled cultures](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/src/Kentico.Xperience.K13Ecommerce/SiteStore/ISiteStoreService.cs#L13), e.g. for implementation of language selector
+ - Use for retrieving site's [list of enabled currencies](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/src/Kentico.Xperience.K13Ecommerce/SiteStore/ISiteStoreService.cs#L18), e.g. for implementation of currency selector
- `ICountryService`
- - Countries and states - these objects are already on XByK, there is no Store API call
+ - [Countries and states](../src/Kentico.Xperience.K13Ecommerce/Countries/ICountryService.cs) - these objects are already on XByK, there is no Store API call
### Products synchronization
@@ -187,11 +238,39 @@ Interval can be set in minutes (`ProductSyncInterval` setting). Synchronized dat
changes, so data cannot be edited in XbyK safely, but new custom or reusable fields can be added and edited
safely.
-No price data are synced, because catalog prices needs
+No price data are synced, because catalog prices need
calculator evaluation in context of user's cart and standalone requests via `IProductService` are required.
#### Limitations
-Currently products are synchronized only in default content culture. **Same language needs to be enabled in XByK**.
+Products are currently synchronized only in default content culture. **Same language needs to be enabled in XByK**.
+
+### Activity logging
+When you are using `IShoppingService` for shopping cart actions, these actions are logged to XByK [Online marketing activities](https://docs.kentico.com/developers-and-admins/digital-marketing-setup/set-up-activities)
+for current contact:
+
+| Activity display name | Activity name | Description |
+|---------------|---------------|--------------------------------------------|
+| Product added to shopping cart | custom_productaddedtoshoppingcart | Product added to cart |
+| Product removed from shopping cart | custom_productremovedfromshoppingcart | Product removed from cart |
+| Purchased product | custom_purchasedproduct | Purchased product (after order is created) |
+| Purchase | custom_purchase | Order created |
+
+You need to ensure these [custom activity types](https://docs.kentico.com/developers-and-admins/digital-marketing-setup/set-up-activities/custom-activities)
+are created (via CI restore - see [Setup section](#setup-1) or [manually](https://docs.kentico.com/developers-and-admins/digital-marketing-setup/set-up-activities/custom-activities#add-custom-activity-types)).
+
+### Email notifications
+Currently all e-commerce email notifications are sent from KX 13 application.
+You need to have [configured email sending](https://docs.kentico.com/13/configuring-xperience/configuring-smtp-servers) and
+[e-commerce email templates](https://docs.kentico.com/13/e-commerce-features/configuring-on-line-stores/configuring-e-commerce-email-notifications).
+
+### Product listing widget
+- We recommend to use this widget for simple scenarios such as Landing page offers, etc.
+- [Product listing widget example](../examples/DancingGoat-K13Ecommerce/Components/Widgets/Store/ProductListWidget/StoreProductListWidgetViewComponent.cs)
+ is located in [Dancing Goat XbyK example project](../examples/DancingGoat-K13Ecommerce).
+- The widget is used to display products directly from KX 13, purchase itself still takes place on Kentico 13.
+- The widget has a couple of properties based on the [Store property selector](../src/Kentico.Xperience.K13Ecommerce/Components/FormComponents/KenticoStorePropertySelector/KenticoStorePropertySelectorComponent.cs) which enable to display products for given category, culture and currency.
+
+![Product listing widget](../images/screenshots/product_listing_widget.png "Product listing widget")
### Setup
@@ -214,6 +293,17 @@ dotnet add package Kentico.Xperience.Store.Rcl
}
}
```
+**Setting description**
+
+| Setting | Description |
+|--------------------------------|--------------------------------------------------------------------|
+| StoreApiUrl | Fill main URL (without path) to KX 13 live app instance |
+| ClientId | Fill same value which is defined on KX 13 side |
+| ClientSecret | Fill same value which is defined on KX 13 side |
+| ProductSyncEnabled | If true, product synchronization is enabled |
+| ProductSyncInterval | Interval in minutes specifies how often synchronization is running |
+
+
2. Add K13Ecommerce library to the application services
```csharp
// Program.cs
@@ -221,10 +311,10 @@ dotnet add package Kentico.Xperience.Store.Rcl
// Registers Kentico Store API and services for e-commerce support
builder.Services.AddKenticoStoreServices(builder.Configuration);
```
-3. For most simple scenario: copy product listing widget from Dancing Goat example project to your project and configure
- properties to display products from Kentico 13. Sample widget is located [here](./examples/DancingGoat-K13Ecommerce/Components/Widgets/Store/ProductListWidget).
-4. For more complex scenario with full e-shop, you can inspire how Dancing Goat sample Store on XbyK is implemented.
- Check [Dancing Goat example - setup](./docs/Usage-Guide.md#store-setup) for detailed instructions how to configure categories, products and cart steps.
+3. For the simplest scenario: copy product listing widget from Dancing Goat example project to your project and configure
+ properties to display products from Kentico 13. Sample widget is located [here](../examples/DancingGoat-K13Ecommerce/Components/Widgets/Store/ProductListWidget).
+4. For more complex scenario with full e-shop, you can be inspired by implementation of [Dancing Goat sample Store](../examples/DancingGoat-K13Ecommerce) on XbyK.
+ Check [Dancing Goat example - setup](#dancing-goat-example---setup) for detailed instructions to configure categories, products and cart steps.
5. Restore CI repository files to database (reusable content types, custom activities). CI files are located in
`.\examples\DancingGoat-K13Ecommerce\App_Data\CIRepository\` and you need to copy these files to your application.
```powershell
@@ -253,22 +343,22 @@ All content types and custom activities for e-ecommerce events are created.
Except reusable content types used in product synchronization, additional page types are restored:
-For Store page, categories and product detail pages these page types are restored:
+These page types are restored for Store page, categories and product detail pages:
- `K13Store.StorePage` - Main store page
- `K13Store.CategoryPage` - Page type for categories linking products
- `K13Store.ProductPage` - Page type for product detail page - only linking product SKU from content hub
For checkout process these page types are restored:
-- `K13Store.CartContent` - used for shopping cart first step
-- `K13Store.CartDeliveryDetails`- used for shopping cart second step
-- `K13Store.CartSummary` - used for shopping cart third step
+- `K13Store.CartContent` - used for the shopping cart first step
+- `K13Store.CartDeliveryDetails`- used for the shopping cart second step
+- `K13Store.CartSummary` - used for the shopping cart third step
- `K13Store.OrderComplete` - used for thank you page
2. Start sample KX 13 Dancing Goat application (`Kentico13_DancingGoat` in `.\examples`) configured with your own database
3. Start Xperience By Kentico Dancing Goat application (`DancingGoat` in `.\examples`) configured with your own database.\
-Wait for product synchronization finish. Check `K13-Store product synchronization done.` in debug console or check Event log for errors.
+Let the product synchronization finish. Check `K13-Store product synchronization done.` in debug console or check Event log for errors.
4. Create pages for Store:
1. Store page (of type `K13Store - Store page`)
2. Product pages (of type `K13Store - Product page`) - for each page select corresponding Product SKU from content hub.
@@ -278,6 +368,49 @@ Wait for product synchronization finish. Check `K13-Store product synchronizatio
2. Cart delivery details page
3. Cart summary page
4. Order complete page
+
+## Scenarios
+
+### How to add products onto website?
+The product synchronization creates reusable content items for products, product variants and product images.
+It's on you how to display these product on your website. But you can use the approach from
+[Dancing Goat example](#dancing-goat-example---setup):
+1. Create pages for products (e.g. in folder structure) in your web site channel and link them to product content items
+(of type `K13Store.ProductSKU`). You can use `K13Store.ProductPage` page type for this.
+![Link product pages](../images/screenshots/linking_products_webchannel.png "Link product pages")
+2. Create Store page (use `K13Store.StorePage` page type) which represents entry point for your store. You can display here main categories
+and Hot tip products. Skip this step when you don't need this type of page.
+3. Create pages for categories (use `K13Store.CategoryPage` page type) and select product pages in Products in category field.
+![Products in category](../images/screenshots/category_products.png "Products in category")
+
+### How to display products on your website?
+
+1. For displaying products on your live site, see [StoreCategoryController](../examples/DancingGoat-K13Ecommerce/Controllers/KStore/StoreCategoryController.cs)
+on Dancing Goat example site. Products pages are retrieved for current category and current prices are retrieved for these products via `IProductService`.
+2. You can also to consider using [Product listing widget](#product-listing-widget) for simple scenarios like Landing pages with product offer.
+
+### How to implement shopping cart / checkout process?
+
+1. Create pages representing cart steps
+ 1. Cart content page
+ 2. Cart delivery details page
+ 3. Cart summary page
+ 4. Order complete page\
+ Set Cart next steps / Cart previous step fields for each step page.
+ ![Cart steps](../images/screenshots/cart_steps.png "Cart steps")
+ This approach has the advantage that you can use [page builder features](https://docs.kentico.com/developers-and-admins/development/builders/page-builder) for each step.
+2. For shopping cart and checkout process implementation, see [CheckoutController](../examples/DancingGoat-K13Ecommerce/Controllers/KStore/CheckoutController.cs)
+
+Here are links for some specific parts of shopping cart:
+
+- [Shopping cart content](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/DancingGoat-K13Ecommerce/Controllers/KStore/CheckoutController.cs#L84)
+- [Discount / Coupon codes](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/DancingGoat-K13Ecommerce/Controllers/KStore/CheckoutController.cs#L163)
+- [Delivery details + shipping](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/DancingGoat-K13Ecommerce/Controllers/KStore/CheckoutController.cs#L194)
+- [Payment](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/DancingGoat-K13Ecommerce/Controllers/KStore/CheckoutController.cs#L330)
+- Payment gateway - Is not part of this PoC solution, you need to implement integration with specific payment gateway. **API for updating orders (and their statuses) is under development**.
+- [Order creation](https://github.com/Kentico/xperience-by-kentico-ecommerce/blob/main/examples/DancingGoat-K13Ecommerce/Controllers/KStore/CheckoutController.cs#L315)
+
+
diff --git a/examples/DancingGoat-K13Ecommerce/appsettings.json b/examples/DancingGoat-K13Ecommerce/appsettings.json
index 97deca9..e97cc90 100644
--- a/examples/DancingGoat-K13Ecommerce/appsettings.json
+++ b/examples/DancingGoat-K13Ecommerce/appsettings.json
@@ -16,8 +16,8 @@
"CMSHashStringSalt": "9f38667d-c99f-43bb-9a92-043ce36ecb5d",
"CMSKenticoStoreConfig": {
"StoreApiUrl": "http://dev.dancinggoat.com:65375",
- "ClientId": "3ef7fe1b-696c-4afa-8b56-d3176b7bea95",
- "ClientSecret": "dgKQeq0y3E59qCcSICAl",
+ "ClientId": "YourUniqueClientIdentifier",
+ "ClientSecret": "********************",
"ProductSyncEnabled": true,
"ProductSyncInterval": 10
}
diff --git a/examples/Kentico13_DancingGoatStore/appsettings.json b/examples/Kentico13_DancingGoatStore/appsettings.json
index b5d800c..fdb53d2 100644
--- a/examples/Kentico13_DancingGoatStore/appsettings.json
+++ b/examples/Kentico13_DancingGoatStore/appsettings.json
@@ -25,7 +25,7 @@
"Audience": "XbyK-DancingGoat",
"TokenExpiresIn": 60
},
- "ClientId": "3ef7fe1b-696c-4afa-8b56-d3176b7bea95",
- "ClientSecret": "dgKQeq0y3E59qCcSICAl"
+ "ClientId": "YourUniqueClientIdentifier",
+ "ClientSecret": "********************"
}
}
\ No newline at end of file
diff --git a/images/project_diagram.png b/images/project_diagram.png
new file mode 100644
index 0000000..1a6db59
Binary files /dev/null and b/images/project_diagram.png differ
diff --git a/images/screenshots/cart_steps.png b/images/screenshots/cart_steps.png
new file mode 100644
index 0000000..cb00c65
Binary files /dev/null and b/images/screenshots/cart_steps.png differ
diff --git a/images/screenshots/category_products.png b/images/screenshots/category_products.png
new file mode 100644
index 0000000..bd35088
Binary files /dev/null and b/images/screenshots/category_products.png differ
diff --git a/images/screenshots/linking_products_webchannel.png b/images/screenshots/linking_products_webchannel.png
new file mode 100644
index 0000000..d6fb783
Binary files /dev/null and b/images/screenshots/linking_products_webchannel.png differ
diff --git a/images/screenshots/product_listing_widget.png b/images/screenshots/product_listing_widget.png
new file mode 100644
index 0000000..a032415
Binary files /dev/null and b/images/screenshots/product_listing_widget.png differ
diff --git a/images/screenshots/store_pages.png b/images/screenshots/store_pages.png
new file mode 100644
index 0000000..71dcb3b
Binary files /dev/null and b/images/screenshots/store_pages.png differ
diff --git a/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json b/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json
index 64e661b..008536d 100644
--- a/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json
+++ b/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json
@@ -27,12 +27,14 @@
"default": "client_credentials"
},
"client_id": {
+ "minLength": 16,
"type": "string"
},
"client_secret": {
+ "minLength": 16,
"type": "string"
},
- "username": {
+ "user_email": {
"type": "string"
}
}
@@ -47,7 +49,7 @@
"client_secret": {
"style": "form"
},
- "username": {
+ "user_email": {
"style": "form"
}
}
@@ -66,12 +68,14 @@
"default": "client_credentials"
},
"client_id": {
+ "minLength": 16,
"type": "string"
},
"client_secret": {
+ "minLength": 16,
"type": "string"
},
- "username": {
+ "user_email": {
"type": "string"
}
}
@@ -86,7 +90,7 @@
"client_secret": {
"style": "form"
},
- "username": {
+ "user_email": {
"style": "form"
}
}
@@ -1959,6 +1963,37 @@
}
}
}
+ },
+ "/api/store/synchronization/user-exists": {
+ "get": {
+ "tags": [
+ "UserSynchronization"
+ ],
+ "summary": "Checks if user exists.",
+ "parameters": [
+ {
+ "name": "userName",
+ "in": "query",
+ "description": "User name",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
}
},
"components": {
@@ -3117,10 +3152,20 @@
"description": "Dto for CMS.Ecommerce.SummaryItem."
},
"KUserSynchronization": {
+ "required": [
+ "userName"
+ ],
"type": "object",
"properties": {
"userName": {
+ "maxLength": 100,
+ "minLength": 1,
+ "type": "string"
+ },
+ "email": {
+ "maxLength": 254,
"type": "string",
+ "format": "email",
"nullable": true
}
},
diff --git a/src/Kentico.Xperience.K13Ecommerce/Users/CustomClientCredentialsTokenEndpointService.cs b/src/Kentico.Xperience.K13Ecommerce/Users/CustomClientCredentialsTokenEndpointService.cs
index 7d4a0f2..bab6dc4 100644
--- a/src/Kentico.Xperience.K13Ecommerce/Users/CustomClientCredentialsTokenEndpointService.cs
+++ b/src/Kentico.Xperience.K13Ecommerce/Users/CustomClientCredentialsTokenEndpointService.cs
@@ -1,4 +1,6 @@
-using Duende.AccessTokenManagement;
+using CMS.Membership;
+
+using Duende.AccessTokenManagement;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -9,14 +11,18 @@ namespace Kentico.Xperience.K13Ecommerce.Users;
internal class CustomClientCredentialsTokenEndpointService : ClientCredentialsTokenEndpointService
{
private readonly IHttpContextAccessor httpContextAccessor;
+ private readonly IMemberInfoProvider memberInfoProvider;
public CustomClientCredentialsTokenEndpointService(IHttpClientFactory httpClientFactory,
IOptionsMonitor options, IClientAssertionService clientAssertionService,
IDPoPKeyStore dPoPKeyMaterialService, IDPoPProofService dPoPProofService,
- ILogger logger, IHttpContextAccessor httpContextAccessor) :
+ ILogger logger, IHttpContextAccessor httpContextAccessor,
+ IMemberInfoProvider memberInfoProvider) :
base(httpClientFactory, options, clientAssertionService, dPoPKeyMaterialService, dPoPProofService, logger)
- => this.httpContextAccessor = httpContextAccessor;
-
+ {
+ this.httpContextAccessor = httpContextAccessor;
+ this.memberInfoProvider = memberInfoProvider;
+ }
public override async Task RequestToken(string clientName,
TokenRequestParameters? parameters = null,
@@ -24,9 +30,13 @@ public override async Task RequestToken(string clientNam
{
parameters ??= new TokenRequestParameters();
- string userName = httpContextAccessor.HttpContext?.User.Identity?.Name ?? "public";
+ string userName = httpContextAccessor.HttpContext?.User.Identity?.Name ?? string.Empty;
+ string userEmail = userName != string.Empty ? (await memberInfoProvider.Get()
+ .TopN(1)
+ .WhereEquals(nameof(MemberInfo.MemberName), userName)
+ .GetEnumerableTypedResultAsync()).FirstOrDefault()?.MemberEmail ?? string.Empty : string.Empty;
- parameters.Parameters.Add("username", userName);
+ parameters.Parameters.Add("user_email", userEmail);
return await base.RequestToken(clientName, parameters, cancellationToken);
}
}
diff --git a/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/IUserSynchronizationService.cs b/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/IUserSynchronizationService.cs
index 5236f4d..4e7d440 100644
--- a/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/IUserSynchronizationService.cs
+++ b/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/IUserSynchronizationService.cs
@@ -11,4 +11,11 @@ public interface IUserSynchronizationService
/// Synchronize XbyK member to K13.
///
Task SynchronizeUser(MemberInfo user);
+
+ ///
+ /// Checks if user exists in K13.
+ ///
+ /// User name
+ /// True if user exists
+ Task UserExists(string userName);
}
diff --git a/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationModule.cs b/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationModule.cs
index 18795fc..e13a7ad 100644
--- a/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationModule.cs
+++ b/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationModule.cs
@@ -29,13 +29,11 @@ private void SynchronizeUser(object? sender, ObjectEventArgs e)
try
{
var user = (MemberInfo)e.Object;
- if (user != null)
- {
- using var serviceScope = Service.Resolve().CreateScope();
- var provider = serviceScope.ServiceProvider;
- var userSynchronizationService = provider.GetRequiredService();
- userSynchronizationService.SynchronizeUser(user).GetAwaiter().GetResult();
- }
+
+ using var serviceScope = Service.Resolve().CreateScope();
+ var provider = serviceScope.ServiceProvider;
+ var userSynchronizationService = provider.GetRequiredService();
+ userSynchronizationService.SynchronizeUser(user).GetAwaiter().GetResult();
}
catch (Exception ex)
{
diff --git a/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationService.cs b/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationService.cs
index e1a7674..6c64b64 100644
--- a/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationService.cs
+++ b/src/Kentico.Xperience.K13Ecommerce/Users/UserSynchronization/UserSynchronizationService.cs
@@ -4,23 +4,19 @@
namespace Kentico.Xperience.K13Ecommerce.Users.UserSynchronization;
-internal class UserSynchronizationService : IUserSynchronizationService
+internal class UserSynchronizationService(IKenticoStoreApiClient storeApiClient) : IUserSynchronizationService
{
- private IKenticoStoreApiClient StoreApiClient { get; }
+ private IKenticoStoreApiClient StoreApiClient { get; } = storeApiClient;
- public UserSynchronizationService(IKenticoStoreApiClient storeApiClient)
+ ///
+ public async Task SynchronizeUser(MemberInfo user) => await StoreApiClient.UserSynchronizationAsync(new KUserSynchronization()
{
- StoreApiClient = storeApiClient;
- }
-
+ UserName = user.MemberName,
+ Email = user.MemberEmail
+ });
///
- public async Task SynchronizeUser(MemberInfo user)
- {
- await StoreApiClient.UserSynchronizationAsync(new KUserSynchronization()
- {
- UserName = user.MemberEmail
- });
- }
+ public async Task UserExists(string userName) =>
+ !string.IsNullOrEmpty(userName) && await StoreApiClient.UserExistsAsync(userName);
}
diff --git a/src/Kentico.Xperience.StoreApi/Authentication/AuthenticationController.cs b/src/Kentico.Xperience.StoreApi/Authentication/AuthenticationController.cs
index 9076279..b2fb357 100644
--- a/src/Kentico.Xperience.StoreApi/Authentication/AuthenticationController.cs
+++ b/src/Kentico.Xperience.StoreApi/Authentication/AuthenticationController.cs
@@ -47,13 +47,14 @@ public async Task> AccessToken([FromForm] TokenReque
return Unauthorized();
}
- var user = string.IsNullOrEmpty(tokenRequest.UserName) || tokenRequest.UserName == AuthenticationHelper.GlobalPublicUser.UserName
+ var user = string.IsNullOrEmpty(tokenRequest.UserEmail)
? AuthenticationHelper.GlobalPublicUser
- : await userInfoProvider.GetAsync(tokenRequest.UserName);
+ : (await userInfoProvider.Get().TopN(1)
+ .WhereEquals(nameof(UserInfo.Email), tokenRequest.UserEmail).GetEnumerableTypedResultAsync()).FirstOrDefault();
if (user is null)
{
- return BadRequest("Invalid user name");
+ return BadRequest("Invalid user");
}
string issuer = jwtOptions.Issuer;
diff --git a/src/Kentico.Xperience.StoreApi/Authentication/TokenRequest.cs b/src/Kentico.Xperience.StoreApi/Authentication/TokenRequest.cs
index 8ed01a5..8de8f83 100644
--- a/src/Kentico.Xperience.StoreApi/Authentication/TokenRequest.cs
+++ b/src/Kentico.Xperience.StoreApi/Authentication/TokenRequest.cs
@@ -17,12 +17,14 @@ public class TokenRequest
[Required]
[FromForm(Name = "client_id")]
+ [MinLength(16)]
public string ClientId { get; set; }
[Required]
[FromForm(Name = "client_secret")]
+ [MinLength(16)]
public string ClientSecret { get; set; }
- [FromForm(Name = "username")]
- public string UserName { get; set; }
+ [FromForm(Name = "user_email")]
+ public string UserEmail { get; set; }
}
diff --git a/src/Kentico.Xperience.StoreApi/Helpers/PasswordHelper.cs b/src/Kentico.Xperience.StoreApi/Helpers/PasswordHelper.cs
new file mode 100644
index 0000000..f998364
--- /dev/null
+++ b/src/Kentico.Xperience.StoreApi/Helpers/PasswordHelper.cs
@@ -0,0 +1,19 @@
+using System.Security.Cryptography;
+
+namespace Kentico.Xperience.StoreApi.Helpers;
+
+internal static class PasswordHelper
+{
+ ///
+ /// Generates a random password of the specified length.
+ ///
+ /// Required password length
+ /// Generated password
+ public static string GeneratePassword(int length)
+ {
+ var rng = RandomNumberGenerator.Create();
+ byte[] bytes = new byte[length];
+ rng.GetBytes(bytes);
+ return Convert.ToBase64String(bytes)[..length];
+ }
+}
diff --git a/src/Kentico.Xperience.StoreApi/Sychronization/KUserSynchronization.cs b/src/Kentico.Xperience.StoreApi/Sychronization/KUserSynchronization.cs
index c398c81..c99a854 100644
--- a/src/Kentico.Xperience.StoreApi/Sychronization/KUserSynchronization.cs
+++ b/src/Kentico.Xperience.StoreApi/Sychronization/KUserSynchronization.cs
@@ -1,9 +1,17 @@
-namespace Kentico.Xperience.StoreApi.Sychronization;
+using System.ComponentModel.DataAnnotations;
+
+namespace Kentico.Xperience.StoreApi.Sychronization;
///
/// Represents model for synchronizing user.
///
public class KUserSynchronization
{
+ [Required]
+ [MaxLength(100)]
public string UserName { get; set; }
+
+ [MaxLength(254)]
+ [EmailAddress]
+ public string Email { get; set; }
}
diff --git a/src/Kentico.Xperience.StoreApi/Sychronization/UserSynchronizationController.cs b/src/Kentico.Xperience.StoreApi/Sychronization/UserSynchronizationController.cs
index 15e8518..222eeb0 100644
--- a/src/Kentico.Xperience.StoreApi/Sychronization/UserSynchronizationController.cs
+++ b/src/Kentico.Xperience.StoreApi/Sychronization/UserSynchronizationController.cs
@@ -1,7 +1,9 @@
-using System.Net.Mime;
+using System.ComponentModel.DataAnnotations;
+using System.Net.Mime;
using Kentico.Membership;
using Kentico.Xperience.StoreApi.Authentication;
+using Kentico.Xperience.StoreApi.Helpers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
@@ -17,10 +19,7 @@ public class UserSynchronizationController : ControllerBase
private readonly ApplicationUserManager userManager;
- public UserSynchronizationController(ApplicationUserManager userManager)
- {
- this.userManager = userManager;
- }
+ public UserSynchronizationController(ApplicationUserManager userManager) => this.userManager = userManager;
///
@@ -43,7 +42,7 @@ public async Task SynchronizeUser([FromBody] KUserSynchronization
var newUser = new ApplicationUser
{
UserName = user.UserName,
- Email = user.UserName,
+ Email = user.Email,
Enabled = true
};
@@ -51,7 +50,7 @@ public async Task SynchronizeUser([FromBody] KUserSynchronization
try
{
- registerResult = await userManager.CreateAsync(newUser, Guid.NewGuid().ToString());
+ registerResult = await userManager.CreateAsync(newUser, PasswordHelper.GeneratePassword(32));
}
catch (Exception e)
{
@@ -71,4 +70,15 @@ public async Task SynchronizeUser([FromBody] KUserSynchronization
return ValidationProblem();
}
+
+
+ ///
+ /// Checks if user exists.
+ ///
+ /// User name
+ /// True if exists
+ [HttpGet("user-exists")]
+ [Produces(MediaTypeNames.Application.Json)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task UserExists([FromQuery][Required] string userName) => await userManager.FindByNameAsync(userName) is not null;
}
diff --git a/test/Kentico.Xperience.K13Ecommerce.IntegrationTests/StoreApiTestBase.cs b/test/Kentico.Xperience.K13Ecommerce.IntegrationTests/StoreApiTestBase.cs
index aefe5a4..e860812 100644
--- a/test/Kentico.Xperience.K13Ecommerce.IntegrationTests/StoreApiTestBase.cs
+++ b/test/Kentico.Xperience.K13Ecommerce.IntegrationTests/StoreApiTestBase.cs
@@ -32,9 +32,11 @@ public async Task Setup()
StoreApiClient = new KenticoStoreApiClient(HttpClient);
+ //Fill your K13 Store API client credentials
var tokenResponse = await StoreApiClient.TokenAsync("client_credentials",
- client_id: "3ef7fe1b-696c-4afa-8b56-d3176b7bea95",
- client_secret: "dgKQeq0y3E59qCcSICAl", username: "public");
+ client_id: "",
+ client_secret: "",
+ user_email: string.Empty);
HttpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", tokenResponse.Access_token);