From 95a06f1b56686fcbeb1e68494936032b3aad166f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 8 Feb 2026 04:49:43 +0000 Subject: [PATCH 01/38] Update All Dependencies --- Dockerfile | 4 ++-- Dockerfile-CI | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 14e079cd0..c21d49a23 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # TARGETARCH and TARGETOS are set automatically when --platform is provided. -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0@sha256:aa05b91be697b83229cb000b90120f0783604ad74ed92a0b45cdf3d1a9c873de AS build-env +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0@sha256:1682f3cd0cb51464568de4fa88c719bd3be55539653d97540718bdd5b87062d4 AS build-env ARG TARGETOS LABEL stage=build-env WORKDIR /source @@ -15,7 +15,7 @@ RUN dotnet publish src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj # build runtime image FROM mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809@sha256:454026ec875dc2415853bb6376b4d642bf08aa3e9f0478be33e0000a2897307f AS build_windows -FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:4b8f0b08534833b39bb662fb19a65e78cb086f5ca8dd35de3f87026de8885be4 AS build_linux +FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:0d4a76c0a692c7419acf59529ba6dae8552f4fd59e8453538b5291710a29fb9d AS build_linux FROM build_${TARGETOS} AS aspnet EXPOSE 80 diff --git a/Dockerfile-CI b/Dockerfile-CI index f7a9c18f3..66e5168a9 100644 --- a/Dockerfile-CI +++ b/Dockerfile-CI @@ -1,10 +1,10 @@ # This Docker file is intended for the CI # A prerequisite is a published application in the .build/release -FROM --platform=$BUILDPLATFORM golang:alpine@sha256:98e6cffc31ccc44c7c15d83df1d69891efee8115a5bb7ede2bf30a38af3e3c92 AS build +FROM --platform=$BUILDPLATFORM golang:alpine@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4ced AS build ARG TARGETOS FROM mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809@sha256:454026ec875dc2415853bb6376b4d642bf08aa3e9f0478be33e0000a2897307f AS build_windows -FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:4b8f0b08534833b39bb662fb19a65e78cb086f5ca8dd35de3f87026de8885be4 AS build_linux +FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:0d4a76c0a692c7419acf59529ba6dae8552f4fd59e8453538b5291710a29fb9d AS build_linux FROM build_${TARGETOS} AS aspnet EXPOSE 80 From 858179006858f2bbaa59b17913a430f182f7ea6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 21 Feb 2026 20:19:27 +0100 Subject: [PATCH 02/38] Update OC preview and fix build errors. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 46b794370..31cf942ab 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - 3.0.0-preview-18914 + 3.0.0-preview-18923 From ba5ae83b1e5d873d8ebeefbdfe1a708f23db7332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 22 Feb 2026 10:17:18 +0100 Subject: [PATCH 03/38] Update OC preview. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 31cf942ab..08fded31c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - 3.0.0-preview-18923 + 3.0.0-preview-18924 From 554a17a5ae907e07843abc051e046e2fbe1cef22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 7 Mar 2026 11:20:50 +0100 Subject: [PATCH 04/38] Update OC preview. --- Directory.Packages.props | 2 +- src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 08fded31c..aa1f37170 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - 3.0.0-preview-18924 + 3.0.0-preview-18937 diff --git a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj index 269360aca..39590d844 100644 --- a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj +++ b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj @@ -4,7 +4,7 @@ net10.0 true false - 3.0.0-preview-18914 + 3.0.0-preview-18937 + + + true + From adbdc05c8e4eac02beb462b592570c21f696139a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 7 Mar 2026 12:37:59 +0100 Subject: [PATCH 06/38] Update Lombiq nugets. --- Directory.Packages.props | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index aa1f37170..f289bd776 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,14 +14,14 @@ - - - + + + - - - - + + + + From 0efaa3100c2010a81166d352606eb7bdc01de580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 7 Mar 2026 12:58:25 +0100 Subject: [PATCH 07/38] Update docker files. --- Directory.Build.props | 4 ---- Dockerfile | 6 +++--- Dockerfile-CI | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3f3d5b71c..abfca1cf5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,8 +22,4 @@ - - - true - diff --git a/Dockerfile b/Dockerfile index c21d49a23..bcbd361a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # TARGETARCH and TARGETOS are set automatically when --platform is provided. -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0@sha256:1682f3cd0cb51464568de4fa88c719bd3be55539653d97540718bdd5b87062d4 AS build-env +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0@sha256:e362a8dbcd691522456da26a5198b8f3ca1d7641c95624fadc5e3e82678bd08a AS build-env ARG TARGETOS LABEL stage=build-env WORKDIR /source @@ -14,8 +14,8 @@ COPY Directory.Packages.props . RUN dotnet publish src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj -c Release -o /app --framework net8.0 /p:RunAnalyzers=false # build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809@sha256:454026ec875dc2415853bb6376b4d642bf08aa3e9f0478be33e0000a2897307f AS build_windows -FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:0d4a76c0a692c7419acf59529ba6dae8552f4fd59e8453538b5291710a29fb9d AS build_linux +FROM mcr.microsoft.com/dotnet/aspnet:10.0-nanoserver-ltsc2022@sha256:5e0c69d771061e8edcd4951ddafeff807945978086dc0c41e35ba89a46e58914 AS build_windows +FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:aec87aa74ddf129da573fa69f42f229a23c953a1c6fdecedea1aa6b1fe147d76 AS build_linux FROM build_${TARGETOS} AS aspnet EXPOSE 80 diff --git a/Dockerfile-CI b/Dockerfile-CI index 66e5168a9..0d84da64d 100644 --- a/Dockerfile-CI +++ b/Dockerfile-CI @@ -3,8 +3,8 @@ FROM --platform=$BUILDPLATFORM golang:alpine@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4ced AS build ARG TARGETOS -FROM mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809@sha256:454026ec875dc2415853bb6376b4d642bf08aa3e9f0478be33e0000a2897307f AS build_windows -FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:0d4a76c0a692c7419acf59529ba6dae8552f4fd59e8453538b5291710a29fb9d AS build_linux +FROM mcr.microsoft.com/dotnet/aspnet:10.0-nanoserver-ltsc2022@sha256:5e0c69d771061e8edcd4951ddafeff807945978086dc0c41e35ba89a46e58914 AS build_windows +FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:aec87aa74ddf129da573fa69f42f229a23c953a1c6fdecedea1aa6b1fe147d76 AS build_linux FROM build_${TARGETOS} AS aspnet EXPOSE 80 From 7444e21af7a463ec85d9fd8b55d2ab22ef0ce71d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 7 Mar 2026 13:14:12 +0100 Subject: [PATCH 08/38] Fix Dockerfile. --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bcbd361a5..9fc751f85 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,9 +9,10 @@ WORKDIR /source COPY ./src ./src COPY Directory.Build.props . COPY Directory.Packages.props . +COPY NuGet.config . # build, results are placed in /app -RUN dotnet publish src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj -c Release -o /app --framework net8.0 /p:RunAnalyzers=false +RUN dotnet publish src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj -c Release -o /app --framework net10.0 /p:RunAnalyzers=false # build runtime image FROM mcr.microsoft.com/dotnet/aspnet:10.0-nanoserver-ltsc2022@sha256:5e0c69d771061e8edcd4951ddafeff807945978086dc0c41e35ba89a46e58914 AS build_windows From e936e57859b48d12078bcdd946da1f62c8866451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sat, 7 Mar 2026 13:43:38 +0100 Subject: [PATCH 09/38] Block false positive. --- .../Tests/BasicTests/SecurityScanningTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/OrchardCore.Commerce.Tests.UI/Tests/BasicTests/SecurityScanningTests.cs b/test/OrchardCore.Commerce.Tests.UI/Tests/BasicTests/SecurityScanningTests.cs index 2c99ab0fe..67448b866 100644 --- a/test/OrchardCore.Commerce.Tests.UI/Tests/BasicTests/SecurityScanningTests.cs +++ b/test/OrchardCore.Commerce.Tests.UI/Tests/BasicTests/SecurityScanningTests.cs @@ -40,6 +40,14 @@ public Task FullSecurityScanShouldPass() => "is already handled internally by OC.", @".*/shoppingcart/AddItem.*"); + FalsePositive( + configuration, + 40018, + "SQL Injection", + "It has nothing to do with SQL manipulation, it just gave a query that returns different " + + "results (nothing, because we don't have a product called \"ZAP\").", + @".*pagenum=.*products.title=ZAP.*"); + FalsePositive( configuration, 10202, From 1007dae59d933067520a22da650fdf49c93300cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 11 Mar 2026 06:26:36 +0100 Subject: [PATCH 10/38] Use MediaTypeNames. --- Directory.Packages.props | 2 +- .../Services/ExactlyApiHandler.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f289bd776..c7af1bcf1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - 3.0.0-preview-18937 + 3.0.0-preview-18941 diff --git a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyApiHandler.cs b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyApiHandler.cs index 9d82ef8b7..39b3a997d 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyApiHandler.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyApiHandler.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Options; using OrchardCore.Commerce.Payment.Exactly.Models; +using OrchardCore.Infrastructure; using System; using System.Net.Http; using System.Net.Http.Headers; @@ -25,7 +26,7 @@ protected override Task SendAsync(HttpRequestMessage reques const BindingFlags privateFieldFlags = BindingFlags.Instance | BindingFlags.NonPublic; var allowedHeaderTypes = typeof(HttpHeaders).GetField("_allowedHeaderTypes", privateFieldFlags); allowedHeaderTypes!.SetValue(request.Headers, Enum.Parse(allowedHeaderTypes.FieldType, "All")); - request.Headers.Add("Content-Type", "application/vnd.api+json"); + request.Headers.Add("Content-Type", MediaTypeNamesExtended.Application.JsonVendeorPrefix); #pragma warning restore S3011 return base.SendAsync(request, cancellationToken); From e851d5e887c387eb177df909508a6185e33e8969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Thu, 12 Mar 2026 13:53:29 +0100 Subject: [PATCH 11/38] Fix package downgrades. --- Directory.Packages.props | 2 +- src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c7af1bcf1..ec86a2abb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,7 +21,7 @@ - + diff --git a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj index 39590d844..d347c9a2a 100644 --- a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj +++ b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj @@ -4,7 +4,7 @@ net10.0 true false - 3.0.0-preview-18937 + 3.0.0-preview-18941 - 3.0.0-preview-18960 + 3.0.0-preview-18973 From d491781d0dec19d4b8572c3a982708a70e5b5c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Sun, 12 Apr 2026 03:06:45 +0200 Subject: [PATCH 16/38] Post upgrade fixes. --- Directory.Packages.props | 8 +++--- .../Abstractions/IShoppingCartHelpers.cs | 2 +- .../Abstractions/IUserService.cs | 4 +-- .../Controllers/ExactlyController.cs | 4 +-- .../Services/ExactlyPaymentProvider.cs | 4 +-- .../Services/ExactlySettingsConfiguration.cs | 11 +++----- .../Controllers/WebhookController.cs | 2 +- .../Services/RequestOptionsService.cs | 8 +++--- .../StripeApiSettingsConfiguration.cs | 2 +- .../Services/StripePaymentProvider.cs | 2 +- .../Services/StripePaymentService.cs | 16 +++++------ .../Controllers/PaymentController.cs | 3 +- .../Endpoints/Api/PaymentEndpoint.cs | 3 +- .../Services/CheckoutAddressService.cs | 11 +++----- .../Services/PaymentService.cs | 17 ++++------- .../Extensions/AmountExtensions.cs | 2 +- .../Services/TaxRateSettingsConfiguration.cs | 2 +- .../Controllers/ShoppingCartController.cs | 4 ++- .../Controllers/UserController.cs | 4 +-- .../Drivers/DiscountPartDisplayDriver.cs | 4 +-- .../Drivers/PriceVariantsPartDisplayDriver.cs | 2 +- .../Drivers/ProductPartDisplayDriver.cs | 4 +-- .../Drivers/TaxRateTaxPartDisplayDriver.cs | 2 +- .../Endpoints/Services/ShoppingCartService.cs | 2 +- .../Events/InventoryShoppingCartEvents.cs | 4 +-- .../Events/TaxShoppingCartEvents.cs | 2 +- .../Events/UserSettingsOrderEvents.cs | 2 +- .../Extensions/UserManagerExtensions.cs | 4 +-- .../Handlers/DiscountPartHandler.cs | 10 +++---- .../Handlers/OrderPartHandler.cs | 4 +-- .../OrderPermissionsAuthorizationHandler.cs | 2 +- .../Handlers/TaxPartAndPricePartHandler.cs | 2 +- .../Indexes/PriceIndex.cs | 4 +-- .../Indexes/ProductPartIndex.cs | 28 ++++++------------- .../Indexes/SubscriptionPartIndex.cs | 2 +- .../OrderPartToOrderSummaryLiquidFilter.cs | 2 +- .../LocalizationCurrencyRedirectMiddleware.cs | 9 +++--- .../ContentLocalizationProductService.cs | 4 +-- .../Services/GlobalDiscountProvider.cs | 2 +- .../Services/LocalInventoryProvider.cs | 16 +++++------ .../Services/LocalTaxProvider.cs | 2 +- .../LocalizationDuplicateSkuResolver.cs | 4 +-- .../Services/OrderLineItemService.cs | 2 +- .../Services/PriceVariantProvider.cs | 2 +- .../Services/ProductInventoryService.cs | 4 +-- .../Services/ProductService.cs | 4 +-- .../Services/RegionConfiguration.cs | 2 +- .../Services/RegionService.cs | 2 +- .../Services/TaxRateTaxProvider.cs | 8 +++--- .../Services/TieredPriceProvider.cs | 2 +- .../Settings/CurrencySettingsConfiguration.cs | 2 +- ...nventoryProductEstimationContextUpdater.cs | 2 +- .../Views/ProductPart.cshtml | 3 +- .../Views/ShoppingCartCell_Quantity.cshtml | 3 +- 54 files changed, 119 insertions(+), 143 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0953dd5a6..29a459c22 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,10 +14,10 @@ - - - - + + + + diff --git a/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IShoppingCartHelpers.cs b/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IShoppingCartHelpers.cs index 3c27a69ee..2b58c6ad9 100644 --- a/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IShoppingCartHelpers.cs +++ b/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IShoppingCartHelpers.cs @@ -84,7 +84,7 @@ public static Task CreateShoppingCartViewModelAsync( string shoppingCartId, IContent order) { - var orderPart = order as OrderPart ?? order.As(); + var orderPart = order as OrderPart ?? order.GetOrCreate(); return service.CreateShoppingCartViewModelAsync( shoppingCartId, diff --git a/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IUserService.cs b/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IUserService.cs index 10235b1fc..820d756f6 100644 --- a/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IUserService.cs +++ b/src/Libraries/OrchardCore.Commerce.Abstractions/Abstractions/IUserService.cs @@ -38,9 +38,9 @@ public static Task GetCurrentFullUserAsync(this IUserService service, IHtt : Task.FromResult(null); public static TPart GetUserSetting(this IUserService service, User user, string contentType = null) - where TPart : ContentPart + where TPart : ContentPart, new() { contentType ??= typeof(TPart).Name.RegexReplace("Part$", string.Empty); - return service.GetUserSetting(user, contentType)?.As(); + return service.GetUserSetting(user, contentType)?.GetMaybe(); } } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Controllers/ExactlyController.cs b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Controllers/ExactlyController.cs index f11bdc5d7..ff7c3e779 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Controllers/ExactlyController.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Controllers/ExactlyController.cs @@ -58,7 +58,7 @@ await this.SafeJsonAsync(async () => shoppingCartId, notifyOnError: false, throwOnError: true); - return await _exactlyService.CreateTransactionAsync(order.As()); + return await _exactlyService.CreateTransactionAsync(order.GetOrCreate()); }); public async Task GetRedirectUrl(string transactionId) => @@ -84,7 +84,7 @@ public async Task VerifyApi() order.DisplayText = S["Exactly API test order"]; await _contentManager.CreateAsync(order); - var result = await _exactlyService.CreateTransactionAsync(order.As(), testAmount); + var result = await _exactlyService.CreateTransactionAsync(order.GetOrCreate(), testAmount); var action = await GetActionRedirectRequestedAsync(result.Id); await _notifier.SuccessAsync( diff --git a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyPaymentProvider.cs b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyPaymentProvider.cs index 6118d4c74..ed539593e 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyPaymentProvider.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyPaymentProvider.cs @@ -63,8 +63,8 @@ public ExactlyPaymentProvider( public async Task CreatePaymentProviderDataAsync(IPaymentViewModel model, bool isPaymentRequest = false, string shoppingCartId = null) { - var settings = (await _siteService.GetSiteSettingsAsync())?.As(); - return string.IsNullOrEmpty(settings?.ApiKey) || string.IsNullOrEmpty(settings.ProjectId) ? null : new object(); + var settings = await _siteService.GetSettingsAsync(); + return string.IsNullOrEmpty(settings.ApiKey) || string.IsNullOrEmpty(settings.ProjectId) ? null : new object(); } public async Task UpdateAndRedirectToFinishedOrderAsync( diff --git a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlySettingsConfiguration.cs b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlySettingsConfiguration.cs index ec197d711..d13a38618 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlySettingsConfiguration.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlySettingsConfiguration.cs @@ -10,12 +10,9 @@ public class ExactlySettingsConfiguration : IConfigureOptions public ExactlySettingsConfiguration(ISiteService siteService) => _siteService = siteService; - public void Configure(ExactlySettings options) - { - var siteSettings = _siteService + public void Configure(ExactlySettings options) => + _siteService .GetSiteSettings() - .As(); - - siteSettings.CopyTo(options); - } + .GetOrCreate() + .CopyTo(options); } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs index 2cedf80f2..481f918c2 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs @@ -44,7 +44,7 @@ public async Task Index([FromHeader(Name = "Stripe-Signature")] s var json = await streamReader.ReadToEndAsync(HttpContext.RequestAborted); try { - var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).As(); + var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).GetOrCreate(); var webhookSigningKey = stripeApiSettings.DecryptWebhookSigningSecret(_dataProtectionProvider, _logger); var stripeEvent = _stripeHelperService.PrepareStripeEvent( diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/RequestOptionsService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/RequestOptionsService.cs index e5edf551d..58b5af660 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/RequestOptionsService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/RequestOptionsService.cs @@ -27,7 +27,7 @@ ILogger logger _apiKeyAccessor = siteSettings => siteSettings - .As() + .GetOrCreate() .SecretKey .DecryptStripeApiKey(dataProtectionProvider, logger); } @@ -41,9 +41,9 @@ public async Task SetIdempotencyKeyAsync() var requestOptions = await GetOrCreateRequestOptionsAsync(); requestOptions.IdempotencyKey = Guid.NewGuid().ToString(); - if (siteSettings.As().AccountId != null) + if (siteSettings.GetOrCreate().AccountId != null) { - requestOptions.StripeAccount = siteSettings.As().AccountId; + requestOptions.StripeAccount = siteSettings.GetOrCreate().AccountId; } return requestOptions; @@ -53,7 +53,7 @@ private async Task CreateRequestOptionsAsync() { var siteSettings = await _siteService.GetSiteSettingsAsync(); var apiKey = _apiKeyAccessor(siteSettings); - var accountId = siteSettings.As().AccountId; + var accountId = siteSettings.GetOrCreate().AccountId; _requestOptions = accountId != null diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeApiSettingsConfiguration.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeApiSettingsConfiguration.cs index 2ac50f0bc..b002d80cd 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeApiSettingsConfiguration.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeApiSettingsConfiguration.cs @@ -26,7 +26,7 @@ public void Configure(StripeApiSettings options) { var settings = _siteService .GetSiteSettings() - .As(); + .GetOrCreate(); options.PublishableKey = settings.PublishableKey; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs index d3ad54dd4..9d0431261 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs @@ -61,7 +61,7 @@ await _session.SaveAsync(new OrderPayment }); } - var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).As(); + var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).GetOrCreate(); return new StripePaymentProviderData { diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs index 1343d03db..6d13509f3 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs @@ -66,13 +66,13 @@ public StripePaymentService( public async Task GetPublicKeyAsync() { - var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).As(); + var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).GetOrCreate(); return stripeApiSettings.PublishableKey; } public async Task CreateClientSecretAsync(Amount total, ShoppingCartViewModel cart) { - var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).As(); + var stripeApiSettings = (await _siteService.GetSiteSettingsAsync()).GetOrCreate(); if (string.IsNullOrEmpty(stripeApiSettings.PublishableKey) || string.IsNullOrEmpty(stripeApiSettings.SecretKey) || @@ -194,7 +194,7 @@ public async Task CreateOrUpdateOrderFromShoppingCartAsync( }, orderPart); - if (!order.As().LineItems.Any() && updateModelAccessor != null) + if (!order.GetOrCreate().LineItems.Any() && updateModelAccessor != null) { updateModelAccessor.ModelUpdater.ModelState.AddModelError( nameof(OrderPart.LineItems), @@ -220,7 +220,7 @@ public async Task PaymentConfirmationAsync( return new PaymentOperationStatusViewModel { Status = PaymentOperationStatus.WaitingForRedirect, - Url = _hca.HttpContext.Request.GetDisplayUrl(), + Url = _hca.HttpContext?.Request.GetDisplayUrl(), }; } @@ -239,7 +239,7 @@ await _contentManager.GetAsync(orderId) is not { } order) }; } - var part = order.As() ?? new OrderPart(); + var part = order.GetOrCreate(); var succeeded = fetchedPaymentIntent.Status == PaymentIntentStatuses.Succeeded; // Looks like there is nothing to do here. @@ -277,12 +277,12 @@ await _contentManager.GetAsync(orderId) is not { } order) }); await _contentManager.UpdateAsync(order); - if (order.As().RetryCounter <= 10) + if (order.GetOrCreate().RetryCounter <= 10) { return new PaymentOperationStatusViewModel { Status = PaymentOperationStatus.WaitingForRedirect, - Url = _hca.HttpContext.Request.GetDisplayUrl(), + Url = _hca.HttpContext?.Request.GetDisplayUrl(), }; } @@ -305,7 +305,7 @@ public async Task GetStripeConfirmParametersAsync( await _paymentService.UpdateOrderWithDriversAsync(order); } - var part = order.As(); + var part = order.GetOrCreate(); var billing = part.BillingAddress.Address ?? new Address(); var shipping = part.ShippingAddress.Address ?? new Address(); diff --git a/src/Modules/OrchardCore.Commerce.Payment/Controllers/PaymentController.cs b/src/Modules/OrchardCore.Commerce.Payment/Controllers/PaymentController.cs index 2c79b35ab..93a3679ca 100644 --- a/src/Modules/OrchardCore.Commerce.Payment/Controllers/PaymentController.cs +++ b/src/Modules/OrchardCore.Commerce.Payment/Controllers/PaymentController.cs @@ -102,8 +102,7 @@ public async Task Validate(string providerName, string paymentId, [HttpGet("checkout/paymentrequest/{orderId}")] public async Task PaymentRequest(string orderId) { - if (await _contentManager.GetAsync(orderId) is not { } order || - order.As() is not { } orderPart) + if (await _contentManager.GetAsync(orderId) is not { } order || !order.TryGet(out var orderPart)) { return NotFound(); } diff --git a/src/Modules/OrchardCore.Commerce.Payment/Endpoints/Api/PaymentEndpoint.cs b/src/Modules/OrchardCore.Commerce.Payment/Endpoints/Api/PaymentEndpoint.cs index af9df08ce..6c4f10903 100644 --- a/src/Modules/OrchardCore.Commerce.Payment/Endpoints/Api/PaymentEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce.Payment/Endpoints/Api/PaymentEndpoint.cs @@ -100,8 +100,7 @@ private static async Task PaymentRequestAsync( return httpContext.ChallengeOrForbidApi(); } - if (await contentManager.GetAsync(orderId) is not { } order || - order.As() is not { } orderPart) + if (await contentManager.GetAsync(orderId) is not { } order || !order.TryGet(out var orderPart)) { return TypedResults.BadRequest(); } diff --git a/src/Modules/OrchardCore.Commerce.Payment/Services/CheckoutAddressService.cs b/src/Modules/OrchardCore.Commerce.Payment/Services/CheckoutAddressService.cs index 202719f98..dd7842908 100644 --- a/src/Modules/OrchardCore.Commerce.Payment/Services/CheckoutAddressService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment/Services/CheckoutAddressService.cs @@ -11,11 +11,8 @@ public class CheckoutAddressService : ICheckoutAddressService public CheckoutAddressService(ISiteService siteService) => _siteService = siteService; - public virtual async Task ShouldIgnoreAddressAsync(CheckoutViewModel checkoutViewModel) - { - var settings = (await _siteService.GetSiteSettingsAsync()).As(); - var shouldIgnore = settings.ShouldIgnoreAddress; - - return shouldIgnore; - } + public virtual async Task ShouldIgnoreAddressAsync(CheckoutViewModel checkoutViewModel) => + (await _siteService.GetSiteSettingsAsync()) + .GetOrCreate() + .ShouldIgnoreAddress; } diff --git a/src/Modules/OrchardCore.Commerce.Payment/Services/PaymentService.cs b/src/Modules/OrchardCore.Commerce.Payment/Services/PaymentService.cs index 4ac07d574..bee430e03 100644 --- a/src/Modules/OrchardCore.Commerce.Payment/Services/PaymentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment/Services/PaymentService.cs @@ -263,16 +263,11 @@ public async Task CheckoutWithoutPaymentAsync(s { try { - return mustBeFree ? - await PaymentServiceExtensions.UpdateAndRedirectToFinishedOrderAsync( - this, - order, + return mustBeFree + ? await this.UpdateAndRedirectToFinishedOrderAsync(order, shoppingCartId, FeatureIds.WithoutPaymentProvider) - : - await PaymentServiceExtensions.UpdateAndRedirectToFinishedOrderAsync( - this, - order, + : await this.UpdateAndRedirectToFinishedOrderAsync(order, shoppingCartId, FeatureIds.NoNecessaryPaymentProvider); } @@ -311,7 +306,7 @@ public async Task CallBackAsync(string paymentP Status = PaymentOperationStatus.NotFound, }; - var status = order.As()?.Status?.Text ?? OrderStatusCodes.Pending; + var status = order.GetMaybe()?.Status?.Text ?? OrderStatusCodes.Pending; if (status is not OrderStatusCodes.Pending and not OrderStatusCodes.PaymentFailed) { @@ -394,10 +389,10 @@ public async Task UpdateOrderToOrderedAsync( { var order = await _contentManager.GetAsync(orderId) ?? await _contentManager.NewAsync(Order); var isNew = order.IsNew(); - var part = order.As(); + var part = order.GetOrCreate(); var cart = await _shoppingCartHelpers.RetrieveAsync(shoppingCartId); - if (cart.Items.Any() && !order.As().LineItems.Any() && updateModelAccessor != null) + if (cart.Items.Any() && !order.GetOrCreate().LineItems.Any() && updateModelAccessor != null) { await _contentItemDisplayManager.UpdateEditorAsync(order, updateModelAccessor.ModelUpdater, isNew: false); diff --git a/src/Modules/OrchardCore.Commerce.Tax/Extensions/AmountExtensions.cs b/src/Modules/OrchardCore.Commerce.Tax/Extensions/AmountExtensions.cs index d2e519039..a56dd13de 100644 --- a/src/Modules/OrchardCore.Commerce.Tax/Extensions/AmountExtensions.cs +++ b/src/Modules/OrchardCore.Commerce.Tax/Extensions/AmountExtensions.cs @@ -9,7 +9,7 @@ public static Amount WithTax(this Amount netAmount, decimal taxRate) => new(netAmount.Value * ToMultiplier(taxRate), netAmount.Currency); public static Amount WithTax(this Amount netAmount, IContent contentWithTaxPart) => - WithTax(netAmount, contentWithTaxPart.As()?.TaxRate.Value ?? 0); + WithTax(netAmount, contentWithTaxPart.GetMaybe()?.TaxRate.Value ?? 0); public static Amount WithoutTax(this Amount grossAmount, decimal taxRate) => new(grossAmount.Value / ToMultiplier(taxRate), grossAmount.Currency); diff --git a/src/Modules/OrchardCore.Commerce.Tax/Services/TaxRateSettingsConfiguration.cs b/src/Modules/OrchardCore.Commerce.Tax/Services/TaxRateSettingsConfiguration.cs index 800e6633d..330cac638 100644 --- a/src/Modules/OrchardCore.Commerce.Tax/Services/TaxRateSettingsConfiguration.cs +++ b/src/Modules/OrchardCore.Commerce.Tax/Services/TaxRateSettingsConfiguration.cs @@ -14,7 +14,7 @@ public void Configure(TaxRateSettings options) { var settings = _siteService .GetSiteSettings() - .As(); + .GetOrCreate(); options.CopyFrom(settings); } diff --git a/src/Modules/OrchardCore.Commerce/Controllers/ShoppingCartController.cs b/src/Modules/OrchardCore.Commerce/Controllers/ShoppingCartController.cs index 3e7201ae7..c369106d7 100644 --- a/src/Modules/OrchardCore.Commerce/Controllers/ShoppingCartController.cs +++ b/src/Modules/OrchardCore.Commerce/Controllers/ShoppingCartController.cs @@ -163,7 +163,9 @@ await _workflowManagers.TriggerEventAsync( if (!isValid) { var minOrderQuantity = (await _productService.GetProductAsync(line.ProductSku))? - .As()?.MinimumOrderQuantity.Value ?? 0; + .GetMaybe()? + .MinimumOrderQuantity + .Value ?? 0; // Choose new quantity based on whether Minimum Order Quantity has a value. line.Quantity = (int)(minOrderQuantity > 0 ? minOrderQuantity : 1); diff --git a/src/Modules/OrchardCore.Commerce/Controllers/UserController.cs b/src/Modules/OrchardCore.Commerce/Controllers/UserController.cs index 47a0f7650..a6e969566 100644 --- a/src/Modules/OrchardCore.Commerce/Controllers/UserController.cs +++ b/src/Modules/OrchardCore.Commerce/Controllers/UserController.cs @@ -128,9 +128,9 @@ public async Task DetailsPost() private async Task GetUserContentItemAsync(User user, string contentType) { - var contentItem = user.As(contentType); + var contentItem = user.GetOrCreate(contentType); - return string.IsNullOrEmpty(contentItem?.ContentType) + return string.IsNullOrEmpty(contentItem.ContentType) ? await _contentManager.NewAsync(contentType) : contentItem; } diff --git a/src/Modules/OrchardCore.Commerce/Drivers/DiscountPartDisplayDriver.cs b/src/Modules/OrchardCore.Commerce/Drivers/DiscountPartDisplayDriver.cs index f4211040a..128f20f81 100644 --- a/src/Modules/OrchardCore.Commerce/Drivers/DiscountPartDisplayDriver.cs +++ b/src/Modules/OrchardCore.Commerce/Drivers/DiscountPartDisplayDriver.cs @@ -48,9 +48,9 @@ private static void BuildViewModel(DiscountPartViewModel model, DiscountInformat private static Amount? CalculateNewPrice(DiscountInformation discount, DiscountPart part) { var contentItem = part?.ContentItem; - var newPrice = contentItem?.As()?.GrossPrice?.Amount is { IsValid: true } grossPrice + var newPrice = contentItem?.GetMaybe()?.GrossPrice?.Amount is { IsValid: true } grossPrice ? grossPrice - : contentItem?.As()?.Price; + : contentItem?.GetMaybe()?.Price; if (newPrice is not { } notNullPrice) return null; if (discount.DiscountPercentage > 0) return notNullPrice.WithDiscount(discount.DiscountPercentage); diff --git a/src/Modules/OrchardCore.Commerce/Drivers/PriceVariantsPartDisplayDriver.cs b/src/Modules/OrchardCore.Commerce/Drivers/PriceVariantsPartDisplayDriver.cs index 1496fc71c..c001cb4b0 100644 --- a/src/Modules/OrchardCore.Commerce/Drivers/PriceVariantsPartDisplayDriver.cs +++ b/src/Modules/OrchardCore.Commerce/Drivers/PriceVariantsPartDisplayDriver.cs @@ -99,7 +99,7 @@ private async Task BuildViewModelAsync(PriceVariantsPartViewModel model, PriceVa model.InitializeVariants(variants, values, currencies); // When creating a new PriceVariantsProduct item, initialize default inventories. - if (part.ContentItem.As() is { } inventoryPart && !inventoryPart.Inventory.Any()) + if (part.ContentItem.TryGet(out var inventoryPart) && !inventoryPart.Inventory.Any()) { foreach (var variantKey in allVariantsKeys) { diff --git a/src/Modules/OrchardCore.Commerce/Drivers/ProductPartDisplayDriver.cs b/src/Modules/OrchardCore.Commerce/Drivers/ProductPartDisplayDriver.cs index c31841374..c4629d0fd 100644 --- a/src/Modules/OrchardCore.Commerce/Drivers/ProductPartDisplayDriver.cs +++ b/src/Modules/OrchardCore.Commerce/Drivers/ProductPartDisplayDriver.cs @@ -59,7 +59,7 @@ public override async Task UpdateAsync( // If the SKU is read-only then editing should not be possible, but here we undo any POST trickery just in case. part.Sku = IsSkuReadOnly ? skuBefore : part.Sku.ToUpperInvariant(); - if (part.ContentItem.As() is { } inventoryPart) + if (part.ContentItem.TryGet(out var inventoryPart)) { part.CanBeBought.Clear(); @@ -105,7 +105,7 @@ private async Task BuildViewModelAsync(ProductPartViewModel viewModel, ProductPa viewModel.IsSkuReadOnly = IsSkuReadOnly; viewModel.ProductPart = part; - if (part.ContentItem.As() is { } inventoryPart) + if (part.ContentItem.TryGet(out var inventoryPart)) { foreach (var (key, value) in inventoryPart.FilterOutdatedEntries()) { diff --git a/src/Modules/OrchardCore.Commerce/Drivers/TaxRateTaxPartDisplayDriver.cs b/src/Modules/OrchardCore.Commerce/Drivers/TaxRateTaxPartDisplayDriver.cs index c184c85a7..5936bdca0 100644 --- a/src/Modules/OrchardCore.Commerce/Drivers/TaxRateTaxPartDisplayDriver.cs +++ b/src/Modules/OrchardCore.Commerce/Drivers/TaxRateTaxPartDisplayDriver.cs @@ -35,7 +35,7 @@ public TaxRateTaxPartDisplayDriver( public override async Task DisplayAsync(TaxPart part, BuildPartDisplayContext context) { - if (part.As() is not { } product || _hca.HttpContext is not { } httpContext) return null; + if (!part.ContentItem.TryGet(out var product) || _hca.HttpContext is not { } httpContext) return null; try { diff --git a/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs b/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs index c2676ea12..5a6ca6dd4 100644 --- a/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs +++ b/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs @@ -133,7 +133,7 @@ await _workflowManagers.TriggerEventAsync( if (!string.IsNullOrEmpty(errored)) { var minOrderQuantity = (await _productService.GetProductAsync(line.ProductSku)) - .As().MinimumOrderQuantity.Value; + .GetOrCreate().MinimumOrderQuantity.Value; // Choose new quantity based on whether Minimum Order Quantity has a value. line.Quantity = (int)(minOrderQuantity > 0 ? minOrderQuantity : 1); diff --git a/src/Modules/OrchardCore.Commerce/Events/InventoryShoppingCartEvents.cs b/src/Modules/OrchardCore.Commerce/Events/InventoryShoppingCartEvents.cs index 24ccd57a6..d33ee69da 100644 --- a/src/Modules/OrchardCore.Commerce/Events/InventoryShoppingCartEvents.cs +++ b/src/Modules/OrchardCore.Commerce/Events/InventoryShoppingCartEvents.cs @@ -40,7 +40,7 @@ public override async Task VerifyingItemAsync(ShoppingCartI { // If the product doesn't have InventoryPart then this event is not applicable. if (await _productService.GetProductAsync(item.ProductSku) is not { } productPart || - productPart.ContentItem.As() is not { } inventoryPart) + !productPart.ContentItem.TryGet(out var inventoryPart)) { return null; } @@ -52,7 +52,7 @@ public override async Task VerifyingItemAsync(ShoppingCartI } // If there are no attributes on a Price Variant Product, there's no need for the below checks. - if (productPart.ContentItem.As() is not null && !item.Attributes.Any()) + if (productPart.ContentItem.Has() && !item.Attributes.Any()) { return null; } diff --git a/src/Modules/OrchardCore.Commerce/Events/TaxShoppingCartEvents.cs b/src/Modules/OrchardCore.Commerce/Events/TaxShoppingCartEvents.cs index 0e7154a60..61b9cdfcb 100644 --- a/src/Modules/OrchardCore.Commerce/Events/TaxShoppingCartEvents.cs +++ b/src/Modules/OrchardCore.Commerce/Events/TaxShoppingCartEvents.cs @@ -64,7 +64,7 @@ public TaxShoppingCartEvents( .ToList(); // When taxes are specified, Gross Price is always applicable, while Net Price is optional. - var priceDisplaySettings = (await _siteService.GetSiteSettingsAsync()).As(); + var priceDisplaySettings = (await _siteService.GetSiteSettingsAsync()).GetOrCreate(); if (priceDisplaySettings.UseNetPriceDisplay) { var grossIndex = newHeaders.FindIndex(header => header.Name == "Gross Price"); diff --git a/src/Modules/OrchardCore.Commerce/Events/UserSettingsOrderEvents.cs b/src/Modules/OrchardCore.Commerce/Events/UserSettingsOrderEvents.cs index 06c0fe7c3..6472ff746 100644 --- a/src/Modules/OrchardCore.Commerce/Events/UserSettingsOrderEvents.cs +++ b/src/Modules/OrchardCore.Commerce/Events/UserSettingsOrderEvents.cs @@ -23,7 +23,7 @@ public UserSettingsOrderEvents(IHttpContextAccessor hca, IUserService userServic public async Task FinalizeAsync(ContentItem order, string shoppingCartId, string paymentProviderName) { // Saving addresses. - var orderPart = order.As(); + var orderPart = order.GetOrCreate(); if (_hca.HttpContext != null && await _userService.GetFullUserAsync(_hca.HttpContext.User) is { } user) { diff --git a/src/Modules/OrchardCore.Commerce/Extensions/UserManagerExtensions.cs b/src/Modules/OrchardCore.Commerce/Extensions/UserManagerExtensions.cs index c2c538963..907fa8408 100644 --- a/src/Modules/OrchardCore.Commerce/Extensions/UserManagerExtensions.cs +++ b/src/Modules/OrchardCore.Commerce/Extensions/UserManagerExtensions.cs @@ -16,12 +16,12 @@ public static class UserManagerExtensions /// if the corresponding is not , returns otherwise. /// public static async Task GetUserAddressAsync(this UserManager userManager, ClaimsPrincipal principal) => - (await userManager.GetUserAsync(principal) as User)?.As(UserAddresses)?.As(); + (await userManager.GetUserAsync(principal) as User)?.GetOrCreate(UserAddresses).GetMaybe(); /// /// Returns the custom user setting of for the given /// if the corresponding is not , returns otherwise. /// public static async Task GetUserDetailsAsync(this UserManager userManager, ClaimsPrincipal principal) => - (await userManager.GetUserAsync(principal) as User)?.As(UserDetails)?.As(); + (await userManager.GetUserAsync(principal) as User)?.GetOrCreate(UserDetails).GetMaybe(); } diff --git a/src/Modules/OrchardCore.Commerce/Handlers/DiscountPartHandler.cs b/src/Modules/OrchardCore.Commerce/Handlers/DiscountPartHandler.cs index a470cc680..00975819b 100644 --- a/src/Modules/OrchardCore.Commerce/Handlers/DiscountPartHandler.cs +++ b/src/Modules/OrchardCore.Commerce/Handlers/DiscountPartHandler.cs @@ -29,7 +29,7 @@ public DiscountPartHandler( protected override async Task CreatingOrUpdatingAsync(DiscountPart part) { - if (part.ContentItem.As() is not { } discountPart) return; + if (!part.ContentItem.TryGet(out var discountPart)) return; var discountPercentage = discountPart.DiscountPercentage?.Value ?? 0; var discountAmount = discountPart.DiscountAmount.Amount; @@ -41,12 +41,12 @@ protected override async Task CreatingOrUpdatingAsync(DiscountPart part) await InvalidateEvenStateAsync(); } - if ((part.ContentItem.As()?.Price is { } pricePartPrice && + if (part.ContentItem.GetMaybe()?.Price is { } pricePartPrice && pricePartPrice.Currency.Equals(discountAmount.Currency) && - pricePartPrice < discountAmount) || - (part.ContentItem.As()?.GrossPrice.Amount is { IsValid: true } taxPartGrossPriceAmount && + pricePartPrice < discountAmount || + part.ContentItem.GetMaybe()?.GrossPrice.Amount is { IsValid: true } taxPartGrossPriceAmount && taxPartGrossPriceAmount.Currency.Equals(discountAmount.Currency) && - taxPartGrossPriceAmount < discountAmount)) + taxPartGrossPriceAmount < discountAmount) { await InvalidateNegativePriceStateAsync(); } diff --git a/src/Modules/OrchardCore.Commerce/Handlers/OrderPartHandler.cs b/src/Modules/OrchardCore.Commerce/Handlers/OrderPartHandler.cs index 906508ead..056f0f2be 100644 --- a/src/Modules/OrchardCore.Commerce/Handlers/OrderPartHandler.cs +++ b/src/Modules/OrchardCore.Commerce/Handlers/OrderPartHandler.cs @@ -20,7 +20,7 @@ public OrderPartHandler(IStringLocalizer stringLocalizer, ISes protected override async Task CreatingOrUpdatingAsync(OrderPart part) { - if (part.ContentItem.As() is not { } orderPart) return; + if (!part.ContentItem.TryGet(out var orderPart)) return; var guid = orderPart.OrderId.Text ?? Guid.NewGuid().ToString(); orderPart.OrderId.Text = guid; @@ -32,7 +32,5 @@ protected override async Task CreatingOrUpdatingAsync(OrderPart part) orderPart.Apply(); await _session.SaveAsync(orderPart.ContentItem); - - return; } } diff --git a/src/Modules/OrchardCore.Commerce/Handlers/OrderPermissionsAuthorizationHandler.cs b/src/Modules/OrchardCore.Commerce/Handlers/OrderPermissionsAuthorizationHandler.cs index cd7d84bcb..b23b2b737 100644 --- a/src/Modules/OrchardCore.Commerce/Handlers/OrderPermissionsAuthorizationHandler.cs +++ b/src/Modules/OrchardCore.Commerce/Handlers/OrderPermissionsAuthorizationHandler.cs @@ -16,7 +16,7 @@ public OrderPermissionsAuthorizationHandler(Lazy authoriz protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { - if (context.Resource is not IContent order || order.As() is null) + if (context.Resource is not IContent order || !order.ContentItem.Has()) { return; } diff --git a/src/Modules/OrchardCore.Commerce/Handlers/TaxPartAndPricePartHandler.cs b/src/Modules/OrchardCore.Commerce/Handlers/TaxPartAndPricePartHandler.cs index 5ececbfa2..895957cc7 100644 --- a/src/Modules/OrchardCore.Commerce/Handlers/TaxPartAndPricePartHandler.cs +++ b/src/Modules/OrchardCore.Commerce/Handlers/TaxPartAndPricePartHandler.cs @@ -33,7 +33,7 @@ public TaxPartAndPricePartHandler( protected override async Task CreatingOrUpdatingAsync(PricePart part) { - if (part.ContentItem.As() is not { } taxPart) return; + if (!part.ContentItem.TryGet(out var taxPart)) return; var taxRate = taxPart.TaxRate?.Value ?? 0; diff --git a/src/Modules/OrchardCore.Commerce/Indexes/PriceIndex.cs b/src/Modules/OrchardCore.Commerce/Indexes/PriceIndex.cs index 7d820edc3..f26ac0fae 100644 --- a/src/Modules/OrchardCore.Commerce/Indexes/PriceIndex.cs +++ b/src/Modules/OrchardCore.Commerce/Indexes/PriceIndex.cs @@ -22,7 +22,7 @@ public override void Describe(DescribeContext context) => { if (!contentItem.Published || !contentItem.Latest) return null; - if (contentItem.As() is { Price.Value: var price }) + if (contentItem.GetMaybe() is { Price.Value: var price }) { return new PriceIndex { @@ -31,7 +31,7 @@ public override void Describe(DescribeContext context) => }; } - var variants = contentItem.As()?.Variants; + var variants = contentItem.GetMaybe()?.Variants; if (variants?.Any() == true) { var amounts = variants.Values.Select(amount => amount.Value).ToList(); diff --git a/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs b/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs index f54e89b57..b07234b8c 100644 --- a/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs +++ b/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs @@ -19,23 +19,13 @@ public class ProductPartIndexProvider : IndexProvider public override void Describe(DescribeContext context) => context.For() .Map(contentItem => - { - if (!contentItem.IsPublished()) - { - return null; - } - - var productPart = contentItem.As(); - - if (productPart?.Sku == null) - { - return null; - } - - return new ProductPartIndex - { - Sku = productPart.Sku.ToUpperInvariant(), - ContentItemId = contentItem.ContentItemId, - }; - }); + contentItem.IsPublished() && + contentItem.TryGet(out var productPart) && + !string.IsNullOrEmpty(productPart.Sku) + ? new ProductPartIndex + { + Sku = productPart.Sku.ToUpperInvariant(), + ContentItemId = contentItem.ContentItemId, + } + : null); } diff --git a/src/Modules/OrchardCore.Commerce/Indexes/SubscriptionPartIndex.cs b/src/Modules/OrchardCore.Commerce/Indexes/SubscriptionPartIndex.cs index 853578570..9822f6903 100644 --- a/src/Modules/OrchardCore.Commerce/Indexes/SubscriptionPartIndex.cs +++ b/src/Modules/OrchardCore.Commerce/Indexes/SubscriptionPartIndex.cs @@ -28,7 +28,7 @@ public override void Describe(DescribeContext context) => .When(contentItem => contentItem.Has()) .Map(contentItem => { - var subscriptionPart = contentItem.As(); + var subscriptionPart = contentItem.GetOrCreate(); return new SubscriptionPartIndex { diff --git a/src/Modules/OrchardCore.Commerce/Liquid/OrderPartToOrderSummaryLiquidFilter.cs b/src/Modules/OrchardCore.Commerce/Liquid/OrderPartToOrderSummaryLiquidFilter.cs index 11a8d3f84..8f4fa7203 100644 --- a/src/Modules/OrchardCore.Commerce/Liquid/OrderPartToOrderSummaryLiquidFilter.cs +++ b/src/Modules/OrchardCore.Commerce/Liquid/OrderPartToOrderSummaryLiquidFilter.cs @@ -50,7 +50,7 @@ private async ValueTask ProcessInnerAsync(OrderPart orderPart) .Select(viewModel => new { ViewModel = viewModel, - TaxRate = viewModel.ProductPart.ContentItem?.As()?.TaxRate?.Value, + TaxRate = viewModel.ProductPart.ContentItem?.GetMaybe()?.TaxRate?.Value, UnitTax = viewModel.UnitPrice - Round(viewModel.UnitPriceValue, viewModel), SubTotal = subTotal, TaxTotal = total - subTotal, diff --git a/src/Modules/OrchardCore.Commerce/Middlewares/LocalizationCurrencyRedirectMiddleware.cs b/src/Modules/OrchardCore.Commerce/Middlewares/LocalizationCurrencyRedirectMiddleware.cs index 7e316ee53..eed4f23af 100644 --- a/src/Modules/OrchardCore.Commerce/Middlewares/LocalizationCurrencyRedirectMiddleware.cs +++ b/src/Modules/OrchardCore.Commerce/Middlewares/LocalizationCurrencyRedirectMiddleware.cs @@ -41,10 +41,11 @@ private async Task QueryAndRedirectAsync(HttpContext context, string id) var contentManager = context.RequestServices.GetRequiredService(); var item = await contentManager.GetAsync(id); - if (item?.As() is { } pricePart && - item.As() is { } localizationPart && + if (item?.TryGet(out var pricePart) == true && + item.TryGet(out var localizationPart) && await context.RequestServices.GetRequiredService().GetSiteSettingsAsync() is { } settings && - settings.As().CurrentDisplayCurrency is { } displayCurrency && + settings.TryGet(out var currencySettings) && + currencySettings.CurrentDisplayCurrency is { } displayCurrency && displayCurrency != pricePart.Price.Currency.CurrencyIsoCode) { var session = context.RequestServices.GetRequiredService(); @@ -55,7 +56,7 @@ await context.RequestServices.GetRequiredService().GetSiteSettings .ListAsync(); var applicable = localizationSet - .As() + .GetOrCreate() .FirstOrDefault(part => part.Price.Currency.CurrencyIsoCode == displayCurrency); if (applicable != null) diff --git a/src/Modules/OrchardCore.Commerce/Services/ContentLocalizationProductService.cs b/src/Modules/OrchardCore.Commerce/Services/ContentLocalizationProductService.cs index 2505f787d..d4c0763af 100644 --- a/src/Modules/OrchardCore.Commerce/Services/ContentLocalizationProductService.cs +++ b/src/Modules/OrchardCore.Commerce/Services/ContentLocalizationProductService.cs @@ -34,7 +34,7 @@ public override async Task> GetProductsAsync(IEnumerabl // Shortcut if there are no duplicate localized products. if (skuList.Count == products.Count || - (await _siteService.GetSiteSettingsAsync()).As() is not { } localizationSettings) + !(await _siteService.GetSiteSettingsAsync()).TryGet(out var localizationSettings)) { return products; } @@ -53,7 +53,7 @@ public override async Task> GetProductsAsync(IEnumerabl } result.Add(parts - .OrderByDescending(part => priority.IndexOf(part.ContentItem.As()?.Culture)) + .OrderByDescending(part => priority.IndexOf(part.ContentItem.GetMaybe()?.Culture)) .First()); } diff --git a/src/Modules/OrchardCore.Commerce/Services/GlobalDiscountProvider.cs b/src/Modules/OrchardCore.Commerce/Services/GlobalDiscountProvider.cs index b6dd9be8d..14430d920 100644 --- a/src/Modules/OrchardCore.Commerce/Services/GlobalDiscountProvider.cs +++ b/src/Modules/OrchardCore.Commerce/Services/GlobalDiscountProvider.cs @@ -73,7 +73,7 @@ private async Task> QueryDiscountPartsAsync(Pro int totalQuantity = model.Items.Sum(item => item.Quantity); return globalDiscountItems - .As() + .GetOrCreate() .Where(part => part.IsApplicable(totalQuantity, model.PurchaseDateTime ?? _clock.UtcNow)) .Select(part => (DiscountInformation)part); } diff --git a/src/Modules/OrchardCore.Commerce/Services/LocalInventoryProvider.cs b/src/Modules/OrchardCore.Commerce/Services/LocalInventoryProvider.cs index 47837bbac..729ada022 100644 --- a/src/Modules/OrchardCore.Commerce/Services/LocalInventoryProvider.cs +++ b/src/Modules/OrchardCore.Commerce/Services/LocalInventoryProvider.cs @@ -31,15 +31,12 @@ public LocalInventoryProvider(IProductService productService, ISession session) public Task IsApplicableAsync(IList model) => Task.FromResult(true); - public async Task> QueryAllInventoriesAsync(string sku) - { - var inventoryPart = (await _productService.GetProductAsync(sku))?.As(); - return inventoryPart?.Inventory; - } + public async Task> QueryAllInventoriesAsync(string sku) => + (await _productService.GetProductAsync(sku))?.GetMaybe()?.Inventory; public async Task QueryInventoryAsync(string sku, string fullSku = null) { - var inventoryPart = (await _productService.GetProductAsync(sku))?.As(); + var inventoryPart = (await _productService.GetProductAsync(sku))?.GetMaybe(); // If fullSku is specified, look for Price Variant Product's inventory. var inventoryIdentifier = string.IsNullOrEmpty(fullSku) ? sku : fullSku; @@ -70,8 +67,11 @@ private async Task UpdateInventoryAsync(ProductPart productPart, int difference, try { - var inventoryPart = productPart?.ContentItem.As(); - if (inventoryPart == null || inventoryPart.IgnoreInventory.Value) return; + if (productPart?.ContentItem.TryGet(out var inventoryPart) != true || + inventoryPart.IgnoreInventory.Value) + { + return; + } var inventoryIdentifier = string.IsNullOrEmpty(fullSku) ? productPart.Sku : fullSku; var inventoryRootIdentifier = inventoryIdentifier.Contains('-') diff --git a/src/Modules/OrchardCore.Commerce/Services/LocalTaxProvider.cs b/src/Modules/OrchardCore.Commerce/Services/LocalTaxProvider.cs index 2a19d6267..6f7294026 100644 --- a/src/Modules/OrchardCore.Commerce/Services/LocalTaxProvider.cs +++ b/src/Modules/OrchardCore.Commerce/Services/LocalTaxProvider.cs @@ -47,6 +47,6 @@ public async Task IsApplicableAsync(PromotionAndTaxProviderContext model) await ITaxProvider.AllOrNoneAsync(model, items => Task.FromResult(HasTaxRate(items))); private static int HasTaxRate(IList items) => items - .SelectWhere(item => item.Content.ContentItem.As()) + .SelectWhere(item => item.Content.ContentItem.GetMaybe()) .Count(taxPart => taxPart.TaxRate.Value == 0 || (taxPart.GrossPrice.Amount.IsValid && taxPart.TaxRate.Value > 0)); } diff --git a/src/Modules/OrchardCore.Commerce/Services/LocalizationDuplicateSkuResolver.cs b/src/Modules/OrchardCore.Commerce/Services/LocalizationDuplicateSkuResolver.cs index fafeceff0..6f484b93a 100644 --- a/src/Modules/OrchardCore.Commerce/Services/LocalizationDuplicateSkuResolver.cs +++ b/src/Modules/OrchardCore.Commerce/Services/LocalizationDuplicateSkuResolver.cs @@ -8,9 +8,9 @@ namespace OrchardCore.Commerce.Services; public class LocalizationDuplicateSkuResolver : IDuplicateSkuResolver { public IList UpdateDuplicatesList(ContentItem current, IList otherProducts) => - current.As()?.LocalizationSet is { } currentLocalizationSet + current.GetMaybe()?.LocalizationSet is { } currentLocalizationSet ? otherProducts - .WhereNot(other => other.As()?.LocalizationSet == currentLocalizationSet) + .WhereNot(other => other.GetMaybe()?.LocalizationSet == currentLocalizationSet) .ToList() : otherProducts; } diff --git a/src/Modules/OrchardCore.Commerce/Services/OrderLineItemService.cs b/src/Modules/OrchardCore.Commerce/Services/OrderLineItemService.cs index 9cd931e98..282747c35 100644 --- a/src/Modules/OrchardCore.Commerce/Services/OrderLineItemService.cs +++ b/src/Modules/OrchardCore.Commerce/Services/OrderLineItemService.cs @@ -139,7 +139,7 @@ await _taxProviders.GetFirstApplicableProviderAsync(promotionAndTaxContext) is { var allProducts = await _session.Query().ListAsync(); foreach (var product in allProducts) { - var productSku = product.As().Sku; + var productSku = product.GetOrCreate().Sku; var booleanAttributes = (await _productAttributeService.GetProductAttributeFieldsAsync(product)) .Where(attribute => attribute.Field is BooleanProductAttributeField) diff --git a/src/Modules/OrchardCore.Commerce/Services/PriceVariantProvider.cs b/src/Modules/OrchardCore.Commerce/Services/PriceVariantProvider.cs index f4c80fc17..8a407c7db 100644 --- a/src/Modules/OrchardCore.Commerce/Services/PriceVariantProvider.cs +++ b/src/Modules/OrchardCore.Commerce/Services/PriceVariantProvider.cs @@ -55,7 +55,7 @@ public async Task> UpdateAsync(IList m private async Task AddPriceToShoppingCartItemAsync(ShoppingCartItem item, ProductPart productPart) { - var priceVariantsPart = productPart.ContentItem.As(); + var priceVariantsPart = productPart.ContentItem.GetMaybe(); if (priceVariantsPart is { Variants: { } variants } && variants.Any()) { diff --git a/src/Modules/OrchardCore.Commerce/Services/ProductInventoryService.cs b/src/Modules/OrchardCore.Commerce/Services/ProductInventoryService.cs index c03496e4e..03240ea6b 100644 --- a/src/Modules/OrchardCore.Commerce/Services/ProductInventoryService.cs +++ b/src/Modules/OrchardCore.Commerce/Services/ProductInventoryService.cs @@ -38,8 +38,8 @@ public async Task VerifyLinesAsync(IList lines) { foreach (var line in lines) { - var productPart = line.Product.ContentItem.As(); - if (productPart.As() is not { } inventoryPart) + var productPart = line.Product.ContentItem.GetOrCreate(); + if (productPart.ContentItem?.TryGet(out var inventoryPart) != true) { continue; } diff --git a/src/Modules/OrchardCore.Commerce/Services/ProductService.cs b/src/Modules/OrchardCore.Commerce/Services/ProductService.cs index 2006fc5d4..1b381c319 100644 --- a/src/Modules/OrchardCore.Commerce/Services/ProductService.cs +++ b/src/Modules/OrchardCore.Commerce/Services/ProductService.cs @@ -94,7 +94,7 @@ public string GetVariantKey(string sku) => public async Task<(PriceVariantsPart Part, string VariantKey)> GetExactVariantAsync(string sku) { var productPart = await this.GetProductAsync(sku); - var priceVariantsPart = productPart?.ContentItem.As(); + var priceVariantsPart = productPart?.ContentItem.GetMaybe(); return (priceVariantsPart, GetVariantKey(sku)); } @@ -135,7 +135,7 @@ private async Task> FillContentItemsAndGetProductPartsAsync(IE } } - results.Add(contentItem.As()); + results.Add(contentItem.GetMaybe()); } return results; diff --git a/src/Modules/OrchardCore.Commerce/Services/RegionConfiguration.cs b/src/Modules/OrchardCore.Commerce/Services/RegionConfiguration.cs index 3283c530c..eb85c6230 100644 --- a/src/Modules/OrchardCore.Commerce/Services/RegionConfiguration.cs +++ b/src/Modules/OrchardCore.Commerce/Services/RegionConfiguration.cs @@ -15,7 +15,7 @@ public void Configure(RegionSettings options) { var settings = _siteService .GetSiteSettings() - .As(); + .GetOrCreate(); options.AllowedRegions = settings.AllowedRegions; } diff --git a/src/Modules/OrchardCore.Commerce/Services/RegionService.cs b/src/Modules/OrchardCore.Commerce/Services/RegionService.cs index 888771ff7..0ee90f28b 100644 --- a/src/Modules/OrchardCore.Commerce/Services/RegionService.cs +++ b/src/Modules/OrchardCore.Commerce/Services/RegionService.cs @@ -26,7 +26,7 @@ public IEnumerable GetAllRegions() => public async Task> GetAvailableRegionsAsync() { var settings = await _siteService.GetSiteSettingsAsync(); - var allowedRegionCodes = (settings.As()?.AllowedRegions ?? []).AsList(); + var allowedRegionCodes = (settings.GetOrCreate()?.AllowedRegions ?? []).AsList(); var allRegions = GetAllRegions(); diff --git a/src/Modules/OrchardCore.Commerce/Services/TaxRateTaxProvider.cs b/src/Modules/OrchardCore.Commerce/Services/TaxRateTaxProvider.cs index 2f9828540..5248549af 100644 --- a/src/Modules/OrchardCore.Commerce/Services/TaxRateTaxProvider.cs +++ b/src/Modules/OrchardCore.Commerce/Services/TaxRateTaxProvider.cs @@ -32,7 +32,7 @@ public async Task UpdateAsync(PromotionAndTaxPro var taxRate = MatchTaxRate( taxRates.Rates, model.ShippingAddress, - item.Content.As()?.ProductTaxCode?.Text, + item.Content.GetMaybe()?.ProductTaxCode?.Text, model.VatNumber, model.IsCorporation); @@ -51,14 +51,14 @@ public async Task IsApplicableAsync(PromotionAndTaxProviderContext model) await ITaxProvider.AllOrNoneAsync(model, async items => { var siteSettings = await _siteService.GetSiteSettingsAsync(); - var taxRates = siteSettings.As(); - if (taxRates?.Rates.Any() != true) return 0; + var taxRates = siteSettings.GetOrCreate(); + if (!taxRates.Rates.Any()) return 0; return items.Count(item => MatchTaxRate( taxRates.Rates, model.ShippingAddress, - item.Content.As()?.ProductTaxCode?.Text, + item.Content.GetMaybe()?.ProductTaxCode?.Text, model.VatNumber, model.IsCorporation) >= 0); }); diff --git a/src/Modules/OrchardCore.Commerce/Services/TieredPriceProvider.cs b/src/Modules/OrchardCore.Commerce/Services/TieredPriceProvider.cs index 08eaecb13..91f9755d2 100644 --- a/src/Modules/OrchardCore.Commerce/Services/TieredPriceProvider.cs +++ b/src/Modules/OrchardCore.Commerce/Services/TieredPriceProvider.cs @@ -41,7 +41,7 @@ public async Task> UpdateAsync(IList m private ShoppingCartItem AddPriceToShoppingCartItem(ShoppingCartItem item, ProductPart productPart) { - if (productPart.ContentItem.As() is not { } tieredPricePart) return null; + if (!productPart.ContentItem.TryGet(out var tieredPricePart)) return null; return item.WithPrice( new PrioritizedPrice(1, tieredPricePart.GetPriceForQuantity(_moneyService, item.Quantity))); diff --git a/src/Modules/OrchardCore.Commerce/Settings/CurrencySettingsConfiguration.cs b/src/Modules/OrchardCore.Commerce/Settings/CurrencySettingsConfiguration.cs index d6fd5d0e4..dfc559592 100644 --- a/src/Modules/OrchardCore.Commerce/Settings/CurrencySettingsConfiguration.cs +++ b/src/Modules/OrchardCore.Commerce/Settings/CurrencySettingsConfiguration.cs @@ -13,7 +13,7 @@ public void Configure(CurrencySettings options) { var settings = _site .GetSiteSettings() - .As(); + .GetOrCreate(); options.DefaultCurrency = settings.DefaultCurrency; options.CurrentDisplayCurrency = settings.CurrentDisplayCurrency; diff --git a/src/Modules/OrchardCore.Commerce/Settings/InventoryProductEstimationContextUpdater.cs b/src/Modules/OrchardCore.Commerce/Settings/InventoryProductEstimationContextUpdater.cs index eec970d98..95ead18bc 100644 --- a/src/Modules/OrchardCore.Commerce/Settings/InventoryProductEstimationContextUpdater.cs +++ b/src/Modules/OrchardCore.Commerce/Settings/InventoryProductEstimationContextUpdater.cs @@ -24,7 +24,7 @@ public async Task UpdateAsync(ProductEstimationContext { // If the product doesn't have InventoryPart then this event is not applicable. if (await _productService.GetProductAsync(model.ShoppingCartItem.ProductSku) is not { } product || - product.ContentItem.As() is not { } inventory) + !product.ContentItem.TryGet(out var inventory)) { return model; } diff --git a/src/Modules/OrchardCore.Commerce/Views/ProductPart.cshtml b/src/Modules/OrchardCore.Commerce/Views/ProductPart.cshtml index 06b6fe536..80cfa8d6e 100644 --- a/src/Modules/OrchardCore.Commerce/Views/ProductPart.cshtml +++ b/src/Modules/OrchardCore.Commerce/Views/ProductPart.cshtml @@ -14,8 +14,7 @@ var minOrderQuantity = 1; var maxOrderQuantity = 1; - var inventoryPart = Model.ProductPart.ContentItem.As(); - if (inventoryPart != null) + if (Model.ProductPart.ContentItem.TryGet(out var inventoryPart)) { minOrderQuantity = (int)(inventoryPart.MinimumOrderQuantity.Value > 0 && !inventoryPart.IgnoreInventory.Value ? inventoryPart.MinimumOrderQuantity.Value diff --git a/src/Modules/OrchardCore.Commerce/Views/ShoppingCartCell_Quantity.cshtml b/src/Modules/OrchardCore.Commerce/Views/ShoppingCartCell_Quantity.cshtml index 99acb2941..e7143666a 100644 --- a/src/Modules/OrchardCore.Commerce/Views/ShoppingCartCell_Quantity.cshtml +++ b/src/Modules/OrchardCore.Commerce/Views/ShoppingCartCell_Quantity.cshtml @@ -10,8 +10,7 @@ var minOrderQuantity = 0; var maxOrderQuantity = 1; - var inventoryPart = line.Product.ContentItem.As(); - if (inventoryPart != null) + if (line.Product.ContentItem.TryGet(out var inventoryPart)) { minOrderQuantity = (int)(inventoryPart.MinimumOrderQuantity.Value > 0 && !inventoryPart.IgnoreInventory.Value ? inventoryPart.MinimumOrderQuantity.Value From a8466a35ec5afe695fa9b5cb994e54aabe0dc178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 14 Apr 2026 03:13:27 +0200 Subject: [PATCH 17/38] Fix package versions. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 29a459c22..b50542e7d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - 3.0.0-preview-18973 + 3.0.0-preview-18975 From 6761663fe3fcb3e6b9cfb7d36e89b40c4092fdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 28 Apr 2026 12:39:27 +0200 Subject: [PATCH 18/38] Update OC preview. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b50542e7d..feceb7871 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - 3.0.0-preview-18975 + 3.0.0-preview-18991 From 3b853f4d1f84fe77206d8feb50522b7663487097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 29 Apr 2026 15:52:16 +0200 Subject: [PATCH 19/38] Minor JS call cleanup. --- .../Views/CheckoutStripe.cshtml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml index 32b1c19db..373a407ff 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml @@ -45,18 +45,10 @@ } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/wwwroot/js/stripe-payment-form.js b/src/Modules/OrchardCore.Commerce.Payment.Stripe/wwwroot/js/stripe-payment-form.js index c254b9fdf..91439e467 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/wwwroot/js/stripe-payment-form.js +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/wwwroot/js/stripe-payment-form.js @@ -1,164 +1,167 @@ -window.stripePaymentForm = function stripePaymentForm( - stripe, - clientSecret, - paymentIntentId, - antiForgeryToken, - urlPrefix, - errorText, - missingText, - updatePaymentIntentUrl, - validateUrl = 'checkout/validate/stripe', - paramsUrl = 'stripe/params', - priceUrl = 'checkout/price', - errorContainerSelector = '.message-error', - stripeFieldErrorSelector = '.stripe-field-error', - paymentFormSelector = '.payment-form', - payButtonSelector = '.pay-button-stripe', - payTextSelector = '.pay-text', - paymentProcessingContainerSelector = '.payment-processing-container', - placeOfPaymentSelector = '#payment-form_payment', - payButtonValueSelector = '.pay-button-value', - addressesSelector = '*[id^="OrderPart_ShippingAddress_"], *[id^="OrderPart_BillingAddress_"]', - addressSelector = '.address', - addressTitleSelector = '.address__title' -) { - const allErrorContainers = [document.querySelector(errorContainerSelector)]; - const form = document.querySelector(paymentFormSelector); - const submitButton = form.querySelector(payButtonSelector); - const payText = submitButton.querySelector(payTextSelector); - const paymentProcessingContainer = submitButton.querySelector(paymentProcessingContainerSelector); - const stripeElements = stripe.elements({ clientSecret }); - const payment = stripeElements.create('payment', { fields: { billingDetails: 'never' } }); - const placeOfPayment = document.querySelector(placeOfPaymentSelector); - - let formElements = Array.from(form.elements); - - function toggleInputs(enable) { - formElements.forEach((element) => { - element.readOnly = !enable; - }); - - payment.update({ readOnly: !enable }); - submitButton.disabled = !enable; - paymentProcessingContainer.hidden = enable; - payText.hidden = !enable; +(function(Stripe) { + function toString(data, key, defaultValue) { + if (!data[key]) return defaultValue; + const value = `${data[key]}`.trim(); + return value.length > 0 ? value : defaultValue; } - - function displayError(errors, container = allErrorContainers[0]) { - allErrorContainers.forEach((element) => { element.hidden = true; }); - if (!errors || errors.length === 0) return; - - const err = Array.isArray(errors) ? errors.filter((error) => error) : [errors]; - - container.innerHTML = '
    '; - const ul = container.querySelector('ul'); - err.forEach((error) => { - const li = document.createElement('li'); - li.textContent = error.message || error; - ul.appendChild(li); - }); - - toggleInputs(true); - container.hidden = false; - container.scrollIntoView({ block: 'center' }); - } - - function fetchPost(path) { - return fetch(`${urlPrefix}/${path}`, { method: 'POST', body: new FormData(form) }) - .then((response) => response.json()); - } - - function getText(element) { - return element?.textContent.trim(); - } - - function registerElements() { - // Displaying payment input error. - const stripeFieldError = document.querySelector(stripeFieldErrorSelector); - allErrorContainers.push(stripeFieldError); - payment.on('change', (event) => { - displayError(event?.error, stripeFieldError); - }); - - submitButton.addEventListener('click', async (event) => { - // We don't want to let default form submission happen here, which would refresh the page. - event.preventDefault(); - toggleInputs(false); - - let result; - try { - const emptyRequiredFields = Array.from(form.querySelectorAll('input')) - .filter((element) => element.required && !element.hidden) - .filter((element) => !element.value?.match(/\S+/)); - - if (emptyRequiredFields.length) { - toggleInputs(true); - throw emptyRequiredFields - .map((element) => document.querySelector(`label[for="${element.id}"]`)) - .filter(getText) - .filter((label) => !label.closest(addressSelector)?.hidden) - .map((label) => { - const title = getText(label.closest(addressSelector)?.querySelector(addressTitleSelector)); - const name = title ? `${title} ${getText(label)}` : getText(label); - return missingText.replace('%LABEL%', name); - }); + + function stripePaymentForm(data) { + const stripe = Stripe(data.publishableKey, data.stripeAccountId); + const validateUrl = toString(data, 'validateUrl', 'checkout/validate/stripe'); + const paramsUrl = toString(data, 'paramsUrl', 'stripe/params'); + const priceUrl = toString(data, 'priceUrl', 'checkout/price'); + const errorContainerSelector = toString(data, 'errorContainerSelector', '.message-error'); + const stripeFieldErrorSelector = toString(data, 'stripeFieldErrorSelector', '.stripe-field-error'); + const paymentFormSelector = toString(data, 'paymentFormSelector', '.payment-form'); + const payButtonSelector = toString(data, 'payButtonSelector', '.pay-button-stripe'); + const payTextSelector = toString(data, 'payTextSelector', '.pay-text'); + const paymentProcessingContainerSelector = toString(data, 'paymentProcessingContainerSelector', '.payment-processing-container'); + const placeOfPaymentSelector = toString(data, 'placeOfPaymentSelector', '#payment-form_payment'); + const payButtonValueSelector = toString(data, 'payButtonValueSelector', '.pay-button-value'); + const addressesSelector = toString(data, 'addressesSelector', '*[id^="OrderPart_ShippingAddress_"], *[id^="OrderPart_BillingAddress_"]'); + const addressSelector = toString(data, 'addressSelector', '.address'); + const addressTitleSelector = toString(data, 'addressTitleSelector', '.address__title'); + + const allErrorContainers = [document.querySelector(errorContainerSelector)]; + const form = document.querySelector(paymentFormSelector); + const submitButton = form.querySelector(payButtonSelector); + const payText = submitButton.querySelector(payTextSelector); + const paymentProcessingContainer = submitButton.querySelector(paymentProcessingContainerSelector); + const stripeElements = stripe.elements({ clientSecret: data.clientSecret }); + const payment = stripeElements.create('payment', { fields: { billingDetails: 'never' } }); + const placeOfPayment = document.querySelector(placeOfPaymentSelector); + + let formElements = Array.from(form.elements); + + function toggleInputs(enable) { + formElements.forEach((element) => { + element.readOnly = !enable; + }); + + payment.update({ readOnly: !enable }); + submitButton.disabled = !enable; + paymentProcessingContainer.hidden = enable; + payText.hidden = !enable; + } + + function displayError(errors, container = allErrorContainers[0]) { + allErrorContainers.forEach((element) => { element.hidden = true; }); + if (!errors || errors.length === 0) return; + + const err = Array.isArray(errors) ? errors.filter((error) => error) : [errors]; + + container.innerHTML = '
      '; + const ul = container.querySelector('ul'); + err.forEach((error) => { + const li = document.createElement('li'); + li.textContent = error.message || error; + ul.appendChild(li); + }); + + toggleInputs(true); + container.hidden = false; + container.scrollIntoView({ block: 'center' }); + } + + function fetchPost(path) { + return fetch(`${data.urlPrefix}/${path}`, { method: 'POST', body: new FormData(form) }) + .then((response) => response.json()); + } + + function getText(element) { + return element?.textContent.trim(); + } + + function registerElements() { + // Displaying payment input error. + const stripeFieldError = document.querySelector(stripeFieldErrorSelector); + allErrorContainers.push(stripeFieldError); + payment.on('change', (event) => { + displayError(event?.error, stripeFieldError); + }); + + submitButton.addEventListener('click', async (event) => { + // We don't want to let default form submission happen here, which would refresh the page. + event.preventDefault(); + toggleInputs(false); + + let result; + try { + const emptyRequiredFields = Array.from(form.querySelectorAll('input')) + .filter((element) => element.required && !element.hidden) + .filter((element) => !element.value?.match(/\S+/)); + + if (emptyRequiredFields.length) { + toggleInputs(true); + throw emptyRequiredFields + .map((element) => document.querySelector(`label[for="${element.id}"]`)) + .filter(getText) + .filter((label) => !label.closest(addressSelector)?.hidden) + .map((label) => { + const title = getText(label.closest(addressSelector)?.querySelector(addressTitleSelector)); + const name = title ? `${title} ${getText(label)}` : getText(label); + return data.missingText.replace('%LABEL%', name); + }); + } + + const validationJson = await fetchPost(`${validateUrl}/${data.paymentIntentId}`); + if (validationJson?.errors?.length) { + toggleInputs(true); + throw validationJson.errors; + } + + const confirmPaymentOptions = { + elements: stripeElements, + confirmParams: await fetchPost(paramsUrl), + }; + result = await stripe.confirmPayment(confirmPaymentOptions); + + displayError(result.error); } - - const validationJson = await fetchPost(`${validateUrl}/${paymentIntentId}`); - if (validationJson?.errors?.length) { - toggleInputs(true); - throw validationJson.errors; + catch (error) { + result = { error }; + displayError(result.error); } - - const confirmPaymentOptions = { - elements: stripeElements, - confirmParams: await fetchPost(paramsUrl), - }; - result = await stripe.confirmPayment(confirmPaymentOptions); - - displayError(result.error); - } - catch (error) { - result = { error }; - displayError(result.error); - } - }); - } - - function registerPriceUpdater() { - let debounce = false; - Array.from(document.querySelectorAll(addressesSelector)) - .forEach((element) => element.addEventListener('change', () => { - if (debounce) return; - - const payButtonValue = document.querySelector(payButtonValueSelector); - if (!payButtonValue) return; - - debounce = true; - submitButton.disabled = true; - - setTimeout(async () => { - const priceJson = await fetchPost(priceUrl); - debounce = false; - submitButton.disabled = false; - - // This is not essential if it fails so we intentionally don't catch it. This way if there is an error it can still be seen in the - // browser log during development or UI testing. - if ('error' in priceJson) throw priceJson; - - payButtonValue.setAttribute('data-value', priceJson.value); - payButtonValue.setAttribute('data-currency', priceJson.currency); - payButtonValue.textContent = priceJson.text; - }, 50); // Prevent multiple requests when several fields are updated at once. - })); - } - - if (placeOfPayment) { - payment.mount(placeOfPayment); - - // Refreshing form elements with the payment input. - formElements = Array.from(form.elements); - registerElements(); - registerPriceUpdater(); + }); + } + + function registerPriceUpdater() { + let debounce = false; + Array.from(document.querySelectorAll(addressesSelector)) + .forEach((element) => element.addEventListener('change', () => { + if (debounce) return; + + const payButtonValue = document.querySelector(payButtonValueSelector); + if (!payButtonValue) return; + + debounce = true; + submitButton.disabled = true; + + setTimeout(async () => { + const priceJson = await fetchPost(priceUrl); + debounce = false; + submitButton.disabled = false; + + // This is not essential if it fails so we intentionally don't catch it. This way if there is an error it can still be seen in the + // browser log during development or UI testing. + if ('error' in priceJson) throw priceJson; + + payButtonValue.setAttribute('data-value', priceJson.value); + payButtonValue.setAttribute('data-currency', priceJson.currency); + payButtonValue.textContent = priceJson.text; + }, 50); // Prevent multiple requests when several fields are updated at once. + })); + } + + if (placeOfPayment) { + payment.mount(placeOfPayment); + + // Refreshing form elements with the payment input. + formElements = Array.from(form.elements); + registerElements(); + registerPriceUpdater(); + } } -}; + + window.stripePaymentForm = stripePaymentForm; +})(window.Stripe); \ No newline at end of file From 7f3654cfeb429d5b778b4db03df5f1cb1a7d185c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 29 Apr 2026 20:09:27 +0200 Subject: [PATCH 21/38] Get rid of NRE warnings. --- .../Services/StripePaymentIntentService.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs index 4f7ebe94d..f2b0f9cc7 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs @@ -7,6 +7,7 @@ using OrchardCore.Commerce.Payment.Stripe.Helpers; using OrchardCore.Settings; using Stripe; +using System.Threading; using System.Threading.Tasks; namespace OrchardCore.Commerce.Payment.Stripe.Services; @@ -20,6 +21,8 @@ public class StripePaymentIntentService : IStripePaymentIntentService private readonly IPaymentIntentPersistence _paymentIntentPersistence; private readonly IStringLocalizer T; + private CancellationToken Aborted => _hca.HttpContext?.RequestAborted ?? default; + public StripePaymentIntentService( PaymentIntentService paymentIntentService, IHttpContextAccessor httpContextAccessor, @@ -44,7 +47,7 @@ public async Task GetPaymentIntentAsync(string paymentIntentId) paymentIntentId, paymentIntentGetOptions, await _requestOptionsService.SetIdempotencyKeyAsync(), - _hca.HttpContext.RequestAborted); + Aborted); } public async Task CreatePaymentIntentAsync(Amount total, string shoppingCartId = null) @@ -69,7 +72,7 @@ public async Task CreatePaymentIntentAsync(PaymentIntentCreateOpt await _paymentIntentService.CreateAsync( options, await _requestOptionsService.SetIdempotencyKeyAsync(), - _hca.HttpContext.RequestAborted); + Aborted); public async Task GetOrUpdatePaymentIntentAsync( string paymentIntentId, @@ -93,6 +96,6 @@ public async Task GetOrUpdatePaymentIntentAsync( paymentIntentId, updateOptions, await _requestOptionsService.SetIdempotencyKeyAsync(), - _hca.HttpContext.RequestAborted); + Aborted); } } From 05c300e0a7a88b6c360da64d6ab2ac9c769aa29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 29 Apr 2026 20:10:09 +0200 Subject: [PATCH 22/38] Remove unused action. --- .../Controllers/StripeController.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs index 5f428fab3..92f558c81 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs @@ -27,12 +27,6 @@ public StripeController( _stripePaymentService = stripePaymentService; } - public async Task UpdatePaymentIntent(string paymentIntent) - { - await _paymentIntentPersistence.StoreAsync(paymentIntent); - return Ok(); - } - [AllowAnonymous] [HttpGet("stripe/middleware")] public async Task PaymentConfirmation( From 1dec6b2cda5c7d9a6bb65870120129df788d2549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 29 Apr 2026 20:39:50 +0200 Subject: [PATCH 23/38] Refactor payment intent storage. --- .../Abstractions/IPaymentIntentPersistence.cs | 14 +++-- .../Models/PaymentIntentPersistenceInfo.cs | 5 ++ .../Services/PaymentIntentPersistence.cs | 59 ++++++++++++++----- .../Services/StripePaymentIntentService.cs | 3 +- .../Services/StripePaymentService.cs | 6 +- 5 files changed, 61 insertions(+), 26 deletions(-) create mode 100644 src/Modules/OrchardCore.Commerce.Payment.Stripe/Models/PaymentIntentPersistenceInfo.cs diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IPaymentIntentPersistence.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IPaymentIntentPersistence.cs index 25bd4517e..061bc2ae4 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IPaymentIntentPersistence.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IPaymentIntentPersistence.cs @@ -1,3 +1,5 @@ +#nullable enable +using OrchardCore.Commerce.Payment.Stripe.Models; using System.Threading.Tasks; namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; @@ -8,17 +10,17 @@ namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; public interface IPaymentIntentPersistence { /// - /// Returns the payment intent Id stored in the current session. + /// Returns the payment intent information stored in the current session. /// - Task RetrieveAsync(string shoppingCartId = null); + Task RetrieveAsync(string? shoppingCartId); /// - /// Saves a payment intent Id to the session. + /// Saves a payment intent information to the session. /// - Task StoreAsync(string paymentIntentId, string shoppingCartId = null); + Task StoreAsync(string? shoppingCartId, PaymentIntentPersistenceInfo info); /// - /// Removes the payment intent Id stored in the current session. + /// Removes the payment intent information stored in the current session. /// - Task RemoveAsync(string shoppingCartId = null); + Task RemoveAsync(string? shoppingCartId); } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Models/PaymentIntentPersistenceInfo.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Models/PaymentIntentPersistenceInfo.cs new file mode 100644 index 000000000..aec28f457 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Models/PaymentIntentPersistenceInfo.cs @@ -0,0 +1,5 @@ +using OrchardCore.Commerce.MoneyDataType; + +namespace OrchardCore.Commerce.Payment.Stripe.Models; + +public record PaymentIntentPersistenceInfo(string PaymentIntentId, Amount Amount); diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/PaymentIntentPersistence.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/PaymentIntentPersistence.cs index f484dd1d5..ee1d3e521 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/PaymentIntentPersistence.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/PaymentIntentPersistence.cs @@ -1,5 +1,9 @@ +#nullable enable + using Microsoft.AspNetCore.Http; using OrchardCore.Commerce.Payment.Stripe.Abstractions; +using OrchardCore.Commerce.Payment.Stripe.Models; +using System.Text.Json; using System.Threading.Tasks; namespace OrchardCore.Commerce.Payment.Stripe.Services; @@ -7,46 +11,71 @@ namespace OrchardCore.Commerce.Payment.Stripe.Services; public class PaymentIntentPersistence : IPaymentIntentPersistence { // Using _ as a separator to avoid separator character conflicts. - private const string PaymentIntentKeyPrefix = "OrchardCore_Commerce_PaymentIntent"; + private const string PaymentIntentKeyPrefix = "OrchardCore_Commerce_" + nameof(PaymentIntentPersistenceInfo); private readonly IHttpContextAccessor _httpContextAccessor; - private ISession Session => _httpContextAccessor.HttpContext?.Session; + + private ISession? Session => _httpContextAccessor.HttpContext?.Session; + private HttpRequest? Request => _httpContextAccessor.HttpContext?.Request; public PaymentIntentPersistence(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor; - public Task RetrieveAsync(string shoppingCartId = null) + public Task RetrieveAsync(string? shoppingCartId) { var key = GetCacheId(shoppingCartId); - var serialized = Session.GetString(key); - if (serialized == null && _httpContextAccessor.HttpContext != null) + + if (Session?.GetString(key)?.Trim() is { Length: > 0 } serializedFromSession && + TryParse(serializedFromSession, out var sessionResult)) { - _httpContextAccessor.HttpContext.Request.Cookies.TryGetValue(key, out var serializedCart); - return Task.FromResult(serializedCart); + return Task.FromResult(sessionResult); } - return Task.FromResult(serialized); + if (Request != null && + Request.Cookies.TryGetValue(key, out var serializedFromCookie) && + TryParse(serializedFromCookie, out var cookieResult)) + { + return Task.FromResult(cookieResult); + } + + return Task.FromResult(null); } - public Task StoreAsync(string paymentIntentId, string shoppingCartId = null) + public Task StoreAsync(string? shoppingCartId, PaymentIntentPersistenceInfo info) { var key = GetCacheId(shoppingCartId); - if (Session.GetString(key) == paymentIntentId) return Task.CompletedTask; + var serialized = JsonSerializer.Serialize(info); - Session.SetString(key, paymentIntentId); - _httpContextAccessor.SetCookieForever(key, paymentIntentId); + Session?.SetString(key, serialized); + _httpContextAccessor.SetCookieForever(key, serialized); return Task.CompletedTask; } - public Task RemoveAsync(string shoppingCartId = null) + public Task RemoveAsync(string? shoppingCartId) { var key = GetCacheId(shoppingCartId); - Session.Remove(key); + Session?.Remove(key); _httpContextAccessor.HttpContext?.Response.Cookies.Delete(key); return Task.CompletedTask; } - protected string GetCacheId(string shoppingCartId) => + protected string GetCacheId(string? shoppingCartId) => string.IsNullOrEmpty(shoppingCartId) ? PaymentIntentKeyPrefix : $"{PaymentIntentKeyPrefix}_{shoppingCartId}"; + + private static bool TryParse(string serialized, out PaymentIntentPersistenceInfo? result) + { + result = null; + if (string.IsNullOrWhiteSpace(serialized)) return false; + + try + { + result = JsonSerializer.Deserialize(serialized); + return !string.IsNullOrWhiteSpace(result?.PaymentIntentId); + } + catch + { + return false; + } + } } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs index f2b0f9cc7..a452ce162 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs @@ -62,8 +62,7 @@ public async Task CreatePaymentIntentAsync(Amount total, string s }; var paymentIntent = await CreatePaymentIntentAsync(paymentIntentOptions); - - await _paymentIntentPersistence.StoreAsync(paymentIntent.Id, shoppingCartId); + await _paymentIntentPersistence.StoreAsync(shoppingCartId, new(paymentIntent.Id, total)); return paymentIntent; } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs index 1343d03db..776b2228e 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs @@ -81,7 +81,7 @@ public async Task CreateClientSecretAsync(Amount total, ShoppingCartView return null; } - var paymentIntentId = await _paymentIntentPersistence.RetrieveAsync(cart.Id); + var paymentIntentId = (await _paymentIntentPersistence.RetrieveAsync(cart.Id))?.PaymentIntentId; var totals = cart.GetTotalsOrThrowIfEmpty(); // Same here as on the checkout page: Later we have to figure out what to do if there are multiple @@ -157,7 +157,7 @@ public async Task CreateOrUpdateOrderFromShoppingCartAsync( string paymentIntentId = null, OrderPart orderPart = null) { - var innerPaymentIntentId = paymentIntentId ?? await _paymentIntentPersistence.RetrieveAsync(shoppingCartId); + var innerPaymentIntentId = paymentIntentId ?? (await _paymentIntentPersistence.RetrieveAsync(shoppingCartId))?.PaymentIntentId; var paymentIntent = await _stripePaymentIntentService.GetPaymentIntentAsync(innerPaymentIntentId); // Stripe doesn't support multiple shopping cart IDs because we can't send that info to the middleware anyway. @@ -215,7 +215,7 @@ public async Task PaymentConfirmationAsync( bool needToJudgeIntentStorage = true) { // If it is null it means the session was not loaded yet and a redirect is needed. - if (needToJudgeIntentStorage && string.IsNullOrEmpty(await _paymentIntentPersistence.RetrieveAsync(shoppingCartId))) + if (needToJudgeIntentStorage && string.IsNullOrEmpty((await _paymentIntentPersistence.RetrieveAsync(shoppingCartId))?.PaymentIntentId)) { return new PaymentOperationStatusViewModel { From 12a1727d25ba37a933c3095773c6eddc9ff62485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 29 Apr 2026 20:40:58 +0200 Subject: [PATCH 24/38] Don't create new payment intents when editing the addresses during checkout. This is caused by XHR calls to ~/checkout/price in the background. --- .../Services/StripePaymentIntentService.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs index a452ce162..5d6bc9328 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs @@ -9,6 +9,7 @@ using Stripe; using System.Threading; using System.Threading.Tasks; +using static OrchardCore.Commerce.Payment.Stripe.Constants.PaymentIntentStatuses; namespace OrchardCore.Commerce.Payment.Stripe.Services; @@ -52,6 +53,13 @@ await _requestOptionsService.SetIdempotencyKeyAsync(), public async Task CreatePaymentIntentAsync(Amount total, string shoppingCartId = null) { + var paymentIntentInfo = await _paymentIntentPersistence.RetrieveAsync(shoppingCartId); + if (paymentIntentInfo?.Amount == total && + await GetPaymentIntentAsync(paymentIntentInfo.PaymentIntentId) is { Status: RequiresPaymentMethod } storedPaymentIntent) + { + return storedPaymentIntent; + } + var siteSettings = await _siteService.GetSiteSettingsAsync(); var paymentIntentOptions = new PaymentIntentCreateOptions { From 160be2e6add88c0f08f3f0969e3290a06458c0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Wed, 29 Apr 2026 20:53:17 +0200 Subject: [PATCH 25/38] OC NuGet package versions. --- Directory.Packages.props | 4 ++-- src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c4c8c8f41..3c75fe1d6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - 3.0.0-preview-18960 + 3.0.0-preview-18991 @@ -22,7 +22,7 @@ - + diff --git a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj index c79b596d2..ae5916486 100644 --- a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj +++ b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj @@ -4,7 +4,7 @@ net10.0 true false - 3.0.0-preview-18960 + 3.0.0-preview-18991 - 3.0.0-preview-18991 + 3.0.0-preview-18997 - - + + - - - - - + + + + + - + @@ -46,7 +46,7 @@ - + diff --git a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj index ae5916486..02fcd953e 100644 --- a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj +++ b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj @@ -4,7 +4,7 @@ net10.0 true false - 3.0.0-preview-18991 + 3.0.0-preview-18997