From 05cd8191e74512348271a669c7dbf2d59c6e515f Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Mon, 27 May 2024 18:09:27 +0200 Subject: [PATCH 1/8] docs review changes --- README.md | 20 ++++++++--------- docs/Usage-Guide.md | 53 +++++++++++++++++++++++---------------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index b6474a07..4dbd84f6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ## Description -This integration is primary intended for existing Kentico 13 (KX 13) E-Commerce projects to enable them to migrate +This integration is primary intended for existing Kentico Xperience 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. @@ -30,20 +30,20 @@ is located in [Dancing Goat XbyK example project](./examples/DancingGoat-K13Ecom ![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)). + - Product data (with variants and images) is [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). + - Orders are created from cart, order related data is saved on KX 13 side. + - Products need to be manually linked from Content hub to pages in website channel applications. You can use CI to restore examples of content types for pages that display products. + See [this section of User Guide](./docs/Usage-Guide.md#dancing-goat-example---setup) for detailed information. ![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: +The integration provides an API with services for implementing the following scenarios: - 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) + - Listing of orders (currently suitable for implementing listing orders in administration) - **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 @@ -70,7 +70,7 @@ REST Store API on your own KX 13 e-commerce 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 + - Contains only libraries without sample sites, used for publishing of NuGet packages ## Library Version Matrix @@ -89,11 +89,11 @@ Summary of libraries which are supported by the following versions Xperince by K 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) +- [Xperience by Kentico](https://docs.kentico.com/x/6wocCQ) Kentico Xperience 13 application (or standalone API app): - [ASP.NET Core 6.0](https://dotnet.microsoft.com/en-us/download) -- [Kentico Xperience 13 Refresh 11](https://docs.kentico.com/13/release-notes-xperience-13) +- [Kentico Xperience 13 Refresh 11](https://docs.kentico.com/x/GQeRBg) ## Package Installation diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 5e658b02..af5af065 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -11,7 +11,7 @@ to create E-Commerce solution on XbyK. ## 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 +Store API (library `Kentico.Xperience.StoreApi`) is a REST API which exposes KX 13 E-Commerce features, and allows them to be consumed from other sources. (primary intended for Xperience By Kentico, but you are completely free to use it any way you want). API is exposed via [Swagger](https://swagger.io/) ([Open API 3 standard](https://swagger.io/specification/)) on relative path `/swagger/storeapi/swagger.json` @@ -28,7 +28,7 @@ already generated there. 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's existence is validated and then embedded in token as `sub` and `name` claims. All subsequent +The endpoint validates that the username exists, and then embeds it into the 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 @@ -47,11 +47,11 @@ 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 (XbyK) needs to manage this identifier (this already covers -`Kentico.Xperience.K13Ecommerce` library for XByK applications). +in HTTP header to identify current shopping cart. +Management of this identifier is automatically handled in client (XByK) applications by the `Kentico.Xperience.K13Ecommerce` package. 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. +notable customizations to handle [retrieving cart](https://docs.kentico.com/x/gQuRBg) in RESTful manner. 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. @@ -86,13 +86,13 @@ via API. ### 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. +(user identity is generated in JWT). +Before you start using the Store API, you need to synchronize all website members between the client (XbyK) and your KX 13 application. Complete synchronization is not part of this PoC solution. - Endpoint `api/store/synchronization/user-synchronization` creates new user - - 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 + - Client app (XbyK) should use this to be ensured that all new members on client's are synchronized to KX 13, this is necessary when client's +e-commerce solution allows visitors to log in. KX 13 users are created with random generated password and are used only for API authorization and assigning to MembershipContext. #### Current known limitations @@ -109,7 +109,7 @@ when your KX 13 live site is not running) dotnet add package Kentico.Xperience.StoreApi ``` -1. Set up your own [settings](..\examples\Kentico13_DancingGoatStore\appsettings.json) 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": { @@ -214,6 +214,7 @@ and to browser cookie (uses `IShoppingCartClientStorage`) where customer's addresses are retrieved in cart's second step. - `IOrderService` - List of orders - currently suitable for implementing listing orders in administration + - **Order updates and listing for specific customers are under development** - `ISiteStoreService` - 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 @@ -230,19 +231,19 @@ Library also implements product synchronization to Content hub. These are 3 enti - Product images - Content type `K13Store.ProductImage` - Main SKU images (from SKUImagePath column) -Synchronization running in background thread worker periodically and can be disabled (`ProductSyncEnabled` setting). -Interval can be set in minutes (`ProductSyncInterval` setting). Synchronized data are updated when source value +The synchronization runs in a background thread worker periodically and can be disabled (`ProductSyncEnabled` setting). +Interval can be set in minutes (`ProductSyncInterval` setting). Synchronized data is updated when source value 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 need +No price data is synced, because catalog prices need calculator evaluation in context of user's cart and standalone requests via `IProductService` are required. #### Limitations 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) +When you are using `IShoppingService` for shopping cart actions, these actions are logged to XByK [Online marketing activities](https://docs.kentico.com/x/dY3WCQ) for current contact: | Activity display name | Activity name | Description | @@ -252,13 +253,13 @@ for current contact: | 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) +You need to ensure these [custom activity types](https://docs.kentico.com/x/xoouCw) 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). +You need to have [configured email sending](https://docs.kentico.com/x/IQ_RBg) and +[e-commerce email templates](https://docs.kentico.com/x/-wuRBg). ### Product listing widget - We recommend to use this widget for simple scenarios such as Landing page offers, etc. @@ -338,15 +339,15 @@ dotnet run --kxp-ci-restore ``` 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: +Except reusable content types used in product synchronization, additional content types for pages are restored: -These page types are restored for Store page, categories and product detail pages: +These content 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 +- `K13Store.CategoryPage` - Content type for category pages (with linking product pages) +- `K13Store.ProductPage` - Content type for product detail page - only linking product SKU from content hub -For checkout process these page types are restored: +For checkout process these content types (for pages) are restored: - `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 @@ -373,11 +374,11 @@ The product synchronization creates reusable content items for products, product 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. +(of type `K13Store.ProductSKU`). You can use `K13Store.ProductPage` content 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 +2. Create Store page (use `K13Store.StorePage` content 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. +3. Create pages for categories (use `K13Store.CategoryPage` content 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? @@ -395,7 +396,7 @@ on Dancing Goat example site. Products pages are retrieved for current category 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. + This approach has the advantage that you can use [page builder features](https://docs.kentico.com/x/6QWiCQ) 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: From 6755acb064117980d6f7cefce2a00d6e320e4f72 Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Tue, 28 May 2024 12:46:57 +0200 Subject: [PATCH 2/8] members better explanation, new project diagram as svg --- README.md | 2 +- docs/Usage-Guide.md | 10 +++++----- images/kentico_ecommerce_diagram.svg | 4 ++++ images/project_diagram.png | Bin 89418 -> 0 bytes 4 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 images/kentico_ecommerce_diagram.svg delete mode 100644 images/project_diagram.png diff --git a/README.md b/README.md index 4dbd84f6..7c4078f3 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ create simple e-shop with product listing, product detail and checkout process o 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") +![Project diagram](./images/kentico_ecommerce_diagram.svg "Project diagram") #### Kentico.Xperience.K13Ecommerce.Libs.sln - Contains only libraries without sample sites, used for publishing of NuGet packages diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index af5af065..e21e2019 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -84,19 +84,19 @@ via API. - Endpoint `api/store/site/cultures` returns all enabled site cultures - 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). +### Members synchronization +When [member](https://docs.kentico.com/x/BIsuCw) is created on XbyK, this member needs to be synchronized to KX 13 as user. +It is subsequently used for API authorization (member/user identity is generated in JWT). Before you start using the Store API, you need to synchronize all website members between the client (XbyK) and your KX 13 application. Complete synchronization is not part of this PoC solution. -- Endpoint `api/store/synchronization/user-synchronization` creates new user +- Endpoint `api/store/synchronization/user-synchronization` creates new user in KX 13 - Client app (XbyK) should use this to be ensured that all new members on client's are synchronized to KX 13, this is necessary when client's e-commerce solution allows visitors to log in. KX 13 users are created with random generated password and are used only for API authorization and assigning to MembershipContext. #### Current known limitations -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. +Roles synchronization isn't currently supported. We assume website members to be already synchronized between client (XbyK) and KX app before starting using this API. ### Setup diff --git a/images/kentico_ecommerce_diagram.svg b/images/kentico_ecommerce_diagram.svg new file mode 100644 index 00000000..f30c07b6 --- /dev/null +++ b/images/kentico_ecommerce_diagram.svg @@ -0,0 +1,4 @@ + + + +

KX13 
DB

KX13...
Kentico Xperience 13 
Administration
Kentico Xperience 13...
Kentico Xperience 13
live site application
Kentico Xperience 13...
Store configuration and management
Store configuration and management
Store API
(REST)
Store API...
Xperience By Kentico
application
Xperience By Kentico...
Kentico.Xperience.K13Ecommerce
Kentico.Xperience.K13Ecommerce
Kentico.Xperience.Store.Rcl
Kentico.Xperience.Store.Rcl
Visitor / Customer
Visit...
Kentico.Xperience.StoreApi
Kentico.Xperience.StoreApi
\ No newline at end of file diff --git a/images/project_diagram.png b/images/project_diagram.png deleted file mode 100644 index 1a6db5955059daca065eb6c085278d58f1437b9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89418 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 R73}^%Q%(0y+3g1*{{b5oph^G$ From 09e4fc6fc899bf962fb4999356d5dad5f627ac52 Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Tue, 28 May 2024 13:10:37 +0200 Subject: [PATCH 3/8] typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c4078f3..15a38568 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ REST Store API on your own KX 13 e-commerce solution. ## Library Version Matrix -Summary of libraries which are supported by the following versions Xperince by Kentico / Kentico Xperience 13 +Summary of libraries which are supported by the following versions Xperience by Kentico / Kentico Xperience 13 ### Kentico Xperience 13 E-Commerce integration From c6518cc51ff21a4366ec87e98a272669a1a1e243 Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Tue, 28 May 2024 17:09:26 +0200 Subject: [PATCH 4/8] Split endpoints for current customer vs admin usage --- docs/Usage-Guide.md | 2 +- .../Controllers/TestController.cs | 2 +- .../Services/CheckoutService.cs | 4 +- .../Customers/CustomerService.cs | 9 +- .../Customers/ICustomerService.cs | 13 ++- .../Orders/IOrderService.cs | 12 ++- .../Orders/OrderService.cs | 9 +- .../StoreApi/swagger.json | 102 +++++++++++++++++- .../Customers/CustomerController.cs | 29 ++++- .../Orders/OrderController.cs | 56 +++++++++- 10 files changed, 210 insertions(+), 28 deletions(-) diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 852b28f4..09c467df 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -96,7 +96,7 @@ e-commerce solution allows visitors to log in. KX 13 users are created with rand 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. +> 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 Roles synchronization isn't currently supported. We assume website members to be already synchronized between client (XbyK) and KX app before starting using this API. diff --git a/examples/DancingGoat-K13Ecommerce/Controllers/TestController.cs b/examples/DancingGoat-K13Ecommerce/Controllers/TestController.cs index 9afa6500..de833ceb 100644 --- a/examples/DancingGoat-K13Ecommerce/Controllers/TestController.cs +++ b/examples/DancingGoat-K13Ecommerce/Controllers/TestController.cs @@ -28,6 +28,6 @@ public async Task TestSetCurrency(string currencyCode) } public async Task TestOrders([FromServices] IOrderService orderService) - => Json(await orderService.GetOrderList(new OrderListRequest { Page = 1, PageSize = 10, OrderBy = "OrderID DESC" })); + => Json(await orderService.GetCurrentCustomerOrderList(new OrderListRequest { Page = 1, PageSize = 10, OrderBy = "OrderID DESC" })); } #endif diff --git a/examples/DancingGoat-K13Ecommerce/Services/CheckoutService.cs b/examples/DancingGoat-K13Ecommerce/Services/CheckoutService.cs index 99f5a220..bd083e87 100644 --- a/examples/DancingGoat-K13Ecommerce/Services/CheckoutService.cs +++ b/examples/DancingGoat-K13Ecommerce/Services/CheckoutService.cs @@ -65,7 +65,7 @@ public async Task PrepareDeliveryDetailsViewModel(Cust customer ??= new CustomerViewModel(await shoppingService.GetCustomerOrCreateFromAuthenticatedUser(cartDetails.Customer)); var addresses = (cartDetails.Customer != null) - ? await customerService.GetCustomerAddresses(cartDetails.Customer.CustomerId) + ? await customerService.GetCurrentCustomerAddresses() : Enumerable.Empty(); var billingAddresses = new SelectList(addresses, nameof(KAddress.AddressId), nameof(KAddress.AddressName)); @@ -114,7 +114,7 @@ public async Task IsStateValid(int countryId, int? stateId) public async Task GetAddress(int customerId, int addressId) => customerId > 0 && addressId > 0 - ? (await customerService.GetCustomerAddresses(customerId)).FirstOrDefault(a => a.AddressId == addressId) + ? (await customerService.GetCurrentCustomerAddresses()).FirstOrDefault(a => a.AddressId == addressId) : null; diff --git a/src/Kentico.Xperience.K13Ecommerce/Customers/CustomerService.cs b/src/Kentico.Xperience.K13Ecommerce/Customers/CustomerService.cs index 725a3881..1674f1dc 100644 --- a/src/Kentico.Xperience.K13Ecommerce/Customers/CustomerService.cs +++ b/src/Kentico.Xperience.K13Ecommerce/Customers/CustomerService.cs @@ -5,6 +5,11 @@ namespace Kentico.Xperience.K13Ecommerce.Customers; internal class CustomerService(IKenticoStoreApiClient apiClient) : ICustomerService { /// - public async Task> GetCustomerAddresses(int customerId) => - await apiClient.CustomerAddressesAsync(customerId); + public async Task> GetCurrentCustomerAddresses() => + await apiClient.CurrentCustomerAddressesAsync(); + + + /// + public async Task> GetAdminCustomerAddresses(int customerId) => + await apiClient.AdminCustomerAddressesAsync(customerId); } diff --git a/src/Kentico.Xperience.K13Ecommerce/Customers/ICustomerService.cs b/src/Kentico.Xperience.K13Ecommerce/Customers/ICustomerService.cs index 2b4ea632..8c857c89 100644 --- a/src/Kentico.Xperience.K13Ecommerce/Customers/ICustomerService.cs +++ b/src/Kentico.Xperience.K13Ecommerce/Customers/ICustomerService.cs @@ -8,9 +8,14 @@ namespace Kentico.Xperience.K13Ecommerce.Customers; public interface ICustomerService { /// - /// Get list of addresses for given customer. + /// Get list of addresses for current customer. + /// + Task> GetCurrentCustomerAddresses(); + + + /// + /// Get list of addresses for customer with specified ID to display in XByK administration. /// - /// Customer ID. - /// - Task> GetCustomerAddresses(int customerId); + /// Customer ID. + Task> GetAdminCustomerAddresses(int customerId); } diff --git a/src/Kentico.Xperience.K13Ecommerce/Orders/IOrderService.cs b/src/Kentico.Xperience.K13Ecommerce/Orders/IOrderService.cs index 8f2ed855..fd397946 100644 --- a/src/Kentico.Xperience.K13Ecommerce/Orders/IOrderService.cs +++ b/src/Kentico.Xperience.K13Ecommerce/Orders/IOrderService.cs @@ -8,9 +8,17 @@ namespace Kentico.Xperience.K13Ecommerce.Orders; public interface IOrderService { /// - /// Get orders based on parameters. + /// Get orders based on parameters for current customer. /// /// Request parameters for order listing. /// Paged list of orders. - Task GetOrderList(OrderListRequest request); + Task GetCurrentCustomerOrderList(OrderListRequest request); + + + /// + /// Get orders based on parameters to display in XbyK administration (for all customers). + /// + /// Request parameters for order listing. + /// Paged list of orders. + Task GetAdminOrderList(OrderListRequest request); } diff --git a/src/Kentico.Xperience.K13Ecommerce/Orders/OrderService.cs b/src/Kentico.Xperience.K13Ecommerce/Orders/OrderService.cs index daf1786a..4680615e 100644 --- a/src/Kentico.Xperience.K13Ecommerce/Orders/OrderService.cs +++ b/src/Kentico.Xperience.K13Ecommerce/Orders/OrderService.cs @@ -5,6 +5,11 @@ namespace Kentico.Xperience.K13Ecommerce.Orders; internal class OrderService(IKenticoStoreApiClient storeApiClient) : IOrderService { /// - public async Task GetOrderList(OrderListRequest request) - => await storeApiClient.OrderListAsync(request.Page, request.PageSize, request.OrderBy); + public async Task GetCurrentCustomerOrderList(OrderListRequest request) + => await storeApiClient.CurrentCustomerOrderListAsync(request.Page, request.PageSize, request.OrderBy); + + + /// + public async Task GetAdminOrderList(OrderListRequest request) + => await storeApiClient.AdminOrderListAsync(request.Page, request.PageSize, request.OrderBy); } diff --git a/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json b/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json index 008536d7..ba166abd 100644 --- a/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json +++ b/src/Kentico.Xperience.K13Ecommerce/StoreApi/swagger.json @@ -136,13 +136,47 @@ "tags": [ "Customer" ], - "summary": "Endpoint for customer addresses.", - "operationId": "CustomerAddresses", + "summary": "Endpoint for current customer addresses.", + "operationId": "CurrentCustomerAddresses", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KAddress" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/api/store/customer/admin/addresses": { + "get": { + "tags": [ + "Customer" + ], + "summary": "Endpoint for given customer addresses to display in XbyK administration.", + "operationId": "AdminCustomerAddresses", "parameters": [ { "name": "customerId", "in": "query", - "description": "", + "description": "Customer ID", "required": true, "schema": { "type": "integer", @@ -182,8 +216,66 @@ "tags": [ "Order" ], - "summary": "Endpoint for getting list of orders based on request.", - "operationId": "OrderList", + "summary": "Endpoint for getting list of current customer's orders based on request.", + "operationId": "CurrentCustomerOrderList", + "parameters": [ + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "maximum": 100, + "minimum": 1, + "type": "integer", + "format": "int32" + } + }, + { + "name": "OrderBy", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrderListResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/api/store/order/admin/list": { + "get": { + "tags": [ + "Order" + ], + "summary": "Endpoint for getting list of orders based on request to display in XbyK administration.", + "operationId": "AdminOrderList", "parameters": [ { "name": "Page", diff --git a/src/Kentico.Xperience.StoreApi/Customers/CustomerController.cs b/src/Kentico.Xperience.StoreApi/Customers/CustomerController.cs index 214be365..2880b84f 100644 --- a/src/Kentico.Xperience.StoreApi/Customers/CustomerController.cs +++ b/src/Kentico.Xperience.StoreApi/Customers/CustomerController.cs @@ -23,20 +23,39 @@ public class CustomerController : ControllerBase { private readonly IAddressInfoProvider addressInfoProvider; private readonly IMapper mapper; + private readonly IShoppingService shoppingService; - public CustomerController(IAddressInfoProvider addressInfoProvider, IMapper mapper) + public CustomerController(IAddressInfoProvider addressInfoProvider, IMapper mapper, IShoppingService shoppingService) { this.addressInfoProvider = addressInfoProvider; this.mapper = mapper; + this.shoppingService = shoppingService; } /// - /// Endpoint for customer addresses. + /// Endpoint for current customer addresses. /// - /// /// - [HttpGet("addresses", Name = nameof(CustomerAddresses))] - public ActionResult> CustomerAddresses([FromQuery][Required] int customerId) + [HttpGet("addresses", Name = nameof(CurrentCustomerAddresses))] + public ActionResult> CurrentCustomerAddresses() + { + var cart = shoppingService.GetCurrentShoppingCart(); + if (cart.Customer is null) + { + return Ok(Enumerable.Empty()); + } + var addresses = mapper.Map>(addressInfoProvider.GetByCustomer(cart.Customer.CustomerID)); + return Ok(addresses); + } + + + /// + /// Endpoint for given customer addresses to display in XbyK administration. + /// + /// Customer ID + /// + [HttpGet("admin/addresses", Name = nameof(AdminCustomerAddresses))] + public ActionResult> AdminCustomerAddresses([FromQuery][Required] int customerId) { var addresses = mapper.Map>(addressInfoProvider.GetByCustomer(customerId)); return Ok(addresses); diff --git a/src/Kentico.Xperience.StoreApi/Orders/OrderController.cs b/src/Kentico.Xperience.StoreApi/Orders/OrderController.cs index 138796b5..2a0b78d2 100644 --- a/src/Kentico.Xperience.StoreApi/Orders/OrderController.cs +++ b/src/Kentico.Xperience.StoreApi/Orders/OrderController.cs @@ -24,21 +24,69 @@ public class OrderController : ControllerBase private readonly IOrderInfoProvider orderInfoProvider; private readonly IMapper mapper; private readonly ISiteService siteService; + private readonly IShoppingService shoppingService; - public OrderController(IOrderInfoProvider orderInfoProvider, IMapper mapper, ISiteService siteService) + public OrderController( + IOrderInfoProvider orderInfoProvider, + IMapper mapper, + ISiteService siteService, + IShoppingService shoppingService) { this.orderInfoProvider = orderInfoProvider; this.mapper = mapper; this.siteService = siteService; + this.shoppingService = shoppingService; } /// - /// Endpoint for getting list of orders based on request. + /// Endpoint for getting list of current customer's orders based on request. /// /// /// - [HttpGet("list", Name = nameof(OrderList))] - public async Task> OrderList([FromQuery] OrderListRequest request) + [HttpGet("list", Name = nameof(CurrentCustomerOrderList))] + public async Task> CurrentCustomerOrderList([FromQuery] OrderListRequest request) + { + int page = request.Page > 0 ? request.Page - 1 : 0; + if (string.IsNullOrWhiteSpace(request.OrderBy)) + { + request.OrderBy = $"{nameof(OrderInfo.OrderDate)} DESC"; + } + + var cart = shoppingService.GetCurrentShoppingCart(); + if (cart.Customer is null) + { + return Ok(new OrderListResponse + { + Orders = Enumerable.Empty(), + MaxPage = 1, + Page = 1 + }); + } + + var orderQuery = orderInfoProvider.Get() + .WhereEquals(nameof(OrderInfo.OrderCustomerID), cart.Customer.CustomerID) + .OnSite(siteService.CurrentSite.SiteID) + .Page(page, request.PageSize) + .OrderBy(request.OrderBy); + + var orders = mapper.Map>(await orderQuery.GetEnumerableTypedResultAsync()); + + return Ok(new OrderListResponse + { + Orders = orders, + Page = page + 1, + MaxPage = (orderQuery.TotalRecords / request.PageSize) + 1 + }); + } + + + /// + /// Endpoint for getting list of orders based on request to display in XbyK administration. + /// + /// + /// + [HttpGet("admin/list", Name = nameof(AdminOrderList))] + public async Task> AdminOrderList([FromQuery] OrderListRequest request) { int page = request.Page > 0 ? request.Page - 1 : 0; if (string.IsNullOrWhiteSpace(request.OrderBy)) From a4d410a5c1d328a65dcd1df8517e94ea6157ef9b Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Tue, 28 May 2024 17:37:17 +0200 Subject: [PATCH 5/8] updated docs --- docs/Usage-Guide.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 09c467df..297154b5 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -75,10 +75,12 @@ Not all cart's data can be changed, e.g. custom data (properties like ShoppingCa via API. ### Orders -- Endpoint `api/store/order/list` for retrieving list of orders based on request (supports paging) +- Endpoint `api/store/order/list` for retrieving list of orders for current customer based on request (supports paging) +- Endpoint `api/store/order/admin/list` for retrieving list of orders (for all customers) based on request (supports paging) to display in XbyK administration (supports paging) ### Customers -- Endpoint `api/store/customer/addresses` for retrieving customer's addresses +- Endpoint `api/store/customer/addresses` for retrieving current customer's addresses +- Endpoint `api/store/customer/admin/addresses` for retrieving addresses of specific customer to display in XbyK administration ### Store site - Endpoint `api/store/site/cultures` returns all enabled site cultures From c2ff5c62e728aac4f036f769be97b877dd94e5d2 Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Tue, 28 May 2024 17:59:06 +0200 Subject: [PATCH 6/8] docs updated after next review --- README.md | 4 ++-- docs/Usage-Guide.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15a38568..754b66c7 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ dotnet add package Kentico.Xperience.StoreApi **First set up your Kentico 13 ASP.NET Core application**: -1. Set up your own [settings](.\examples\Kentico13_DancingGoatStore\appsettings.json) 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": { @@ -161,7 +161,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) **Then set up your Xperience By Kentico application** -1. Fill [settings](.\examples\DancingGoat-K13Ecommerce\appsettings.json) to connect your Kentico Xperience 13 instance +1. Fill [settings](./examples/DancingGoat-K13Ecommerce/appsettings.json) to connect your Kentico Xperience 13 instance ```json { "CMSKenticoStoreConfig": { diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 852b28f4..b23cadf2 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -223,6 +223,8 @@ and to browser cookie (uses `IShoppingCartClientStorage`) - 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](../src/Kentico.Xperience.K13Ecommerce/Countries/ICountryService.cs) - these objects are already on XByK, there is no Store API call + > **_NOTE:_** Countries and states are not synchronized between KX 13 and XbyK. As a result, any modifications or + additions to countries and states in KX 13 are currently not supported. ### Products synchronization From 3660ce1f4ebb679ed7be82a9177e27d2ec57e806 Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Tue, 28 May 2024 18:09:38 +0200 Subject: [PATCH 7/8] docs changes for users --- docs/Usage-Guide.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index b23cadf2..026ea55d 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -27,13 +27,13 @@ already generated there. 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. -The endpoint validates that the username exists, and then embeds it into the token as `sub` and `name` claims. All subsequent +Token request can contain `user_email` parameter to identify for which user token is generated. +The endpoint validates that the user for given email exists, and then embeds it into the 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, is then assigned to `MembershipContext.AuthenticatedUser`. When -specific user name isn't provided, AuthenticatedUser remains as public user. +specific user email isn't provided, AuthenticatedUser remains as public user. ### Products @@ -95,8 +95,8 @@ Complete synchronization is not part of this PoC solution. e-commerce solution allows visitors to log in. KX 13 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. +> **_NOTE:_** Please implement double opt-in mechanism for user registration to ensure the users are paired safely between +> XbyK and KX 13. In current Dancing Goat example, we don't have double opt-in mechanism implemented, but we recommend it as best practice. #### Current known limitations Roles synchronization isn't currently supported. We assume website members to be already synchronized between client (XbyK) and KX app before starting using this API. From c9305198dc1f98b75459e1ad944c8d18c3251437 Mon Sep 17 00:00:00 2001 From: Martin Foukal Date: Wed, 29 May 2024 11:09:23 +0200 Subject: [PATCH 8/8] docs members sync minor changes --- docs/Usage-Guide.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 026ea55d..d8471a1b 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -85,18 +85,18 @@ via API. - Endpoint `api/store/site/currencies` returns all enabled site currencies ### Members synchronization -When [member](https://docs.kentico.com/x/BIsuCw) is created on XbyK, this member needs to be synchronized to KX 13 as user. +When [member](https://docs.kentico.com/x/BIsuCw) is created on XbyK (for example when a new customer registers), this member needs to be synchronized to KX 13 as a user. It is subsequently used for API authorization (member/user identity is generated in JWT). Before you start using the Store API, you need to synchronize all website members between the client (XbyK) and your KX 13 application. Complete synchronization is not part of this PoC solution. -- Endpoint `api/store/synchronization/user-synchronization` creates new user in KX 13 - - Client app (XbyK) should use this to be ensured that all new members on client's are synchronized to KX 13, this is necessary when client's -e-commerce solution allows visitors to log in. KX 13 users are created with random generated password and are used only for +- Endpoint `api/store/synchronization/user-synchronization` can be used to create a new user in KX 13 + - The client application (XbyK) should use this to ensure that all new members are synchronized to KX 13. This is necessary when client's +e-commerce solution allows visitors to sign in. KX 13 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 the users are paired safely between -> XbyK and KX 13. In current Dancing Goat example, we don't have double opt-in mechanism implemented, but we recommend it as best practice. +> XbyK and KX 13. The current Dancing Goat example does not have a double opt-in mechanism implemented, but we recommend it as a best practice. #### Current known limitations Roles synchronization isn't currently supported. We assume website members to be already synchronized between client (XbyK) and KX app before starting using this API.