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);