diff --git a/4.0.0.md b/4.0.0.md index 6bef6b465..fa60da2fc 100644 --- a/4.0.0.md +++ b/4.0.0.md @@ -15,3 +15,7 @@ Please check the GitHub release entry [here](https://github.com/OrchardCMS/Orcha ### SKU Generators Now it's possible to automatically generate the product SKU. Manual entry remains the default for backwards compatibility, but if the SKU doesn't have to be a specific value you can enable the "Orchard Core Commerce - SKU Generator - GUID" feature. This makes the SKU field read-only even during product creation. Instead, when you publish the product a new GUID string is automatically generated and filled as the SKU. You can also implement your own SKU generator by implementing the `ISkuGenerator` service. + +### Stripe + +The client script in _stripe-payment-form.js_ has changed. Now `stripePaymentForm` accepts an object instead of a lot of individual positional parameters. Additionally, some parameters that are no longer used have been removed. If you have overridden the `CheckoutStripe` shape on your site, make sure to update it based on the one in OCC. diff --git a/Directory.Packages.props b/Directory.Packages.props index c4c8c8f41..871f8f321 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,28 +6,28 @@ - 3.0.0-preview-18960 + 3.0.0-preview-18997 - - - - - - + + + + + + - - - - - + + + + + - + @@ -46,7 +46,7 @@ - + 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/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/Controllers/StripeController.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs index 5f428fab3..77fb1184e 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs @@ -13,25 +13,14 @@ namespace OrchardCore.Commerce.Payment.Stripe.Controllers; public class StripeController : PaymentBaseController { - private readonly IPaymentIntentPersistence _paymentIntentPersistence; private readonly IStripePaymentService _stripePaymentService; public StripeController( IOrchardServices services, INotifier notifier, - IPaymentIntentPersistence paymentIntentPersistence, IStripePaymentService stripePaymentService) - : base(notifier, services.Logger.Value) - { - _paymentIntentPersistence = paymentIntentPersistence; + : base(notifier, services.Logger.Value) => _stripePaymentService = stripePaymentService; - } - - public async Task UpdatePaymentIntent(string paymentIntent) - { - await _paymentIntentPersistence.StoreAsync(paymentIntent); - return Ok(); - } [AllowAnonymous] [HttpGet("stripe/middleware")] 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/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/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/StripePaymentIntentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs index 4f7ebe94d..4bdaa988f 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs @@ -7,7 +7,9 @@ using OrchardCore.Commerce.Payment.Stripe.Helpers; using OrchardCore.Settings; using Stripe; +using System.Threading; using System.Threading.Tasks; +using static OrchardCore.Commerce.Payment.Stripe.Constants.PaymentIntentStatuses; namespace OrchardCore.Commerce.Payment.Stripe.Services; @@ -20,6 +22,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,11 +48,19 @@ public async Task GetPaymentIntentAsync(string paymentIntentId) paymentIntentId, paymentIntentGetOptions, await _requestOptionsService.SetIdempotencyKeyAsync(), - _hca.HttpContext.RequestAborted); + Aborted); } 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 && + storedPaymentIntent.Amount == AmountHelpers.GetPaymentAmount(total)) + { + return storedPaymentIntent; + } + var siteSettings = await _siteService.GetSiteSettingsAsync(); var paymentIntentOptions = new PaymentIntentCreateOptions { @@ -59,8 +71,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; } @@ -69,7 +80,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 +104,6 @@ public async Task GetOrUpdatePaymentIntentAsync( paymentIntentId, updateOptions, await _requestOptionsService.SetIdempotencyKeyAsync(), - _hca.HttpContext.RequestAborted); + Aborted); } } 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..6c6905b1e 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) || @@ -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. @@ -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), @@ -215,12 +215,12 @@ 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 { 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.Stripe/Views/CheckoutStripe.cshtml b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml index 32b1c19db..c1803433d 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml @@ -13,6 +13,17 @@ string clientSecret = Model.PaymentProviderData?.ClientSecret ?? string.Empty; string paymentIntentId = Model.PaymentProviderData?.PaymentIntentId ?? string.Empty; string accountId = Model.PaymentProviderData?.AccountId?.Trim(); + + var urlPrefix = Url.Content("~/").TrimEnd('/'); + var missingText = T["A value is required for %LABEL%."].Value; + var stripeData = new + { + clientSecret, + paymentIntentId, + urlPrefix, + publishableKey, + missingText, + }; } @@ -45,24 +56,8 @@ } 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..d2c88a0fc 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,173 @@ -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 setupStripePaymentForm(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. - })); + }); + } + + 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(); + } } - if (placeOfPayment) { - payment.mount(placeOfPayment); - - // Refreshing form elements with the payment input. - formElements = Array.from(form.elements); - registerElements(); - registerPriceUpdater(); - } -}; + window.stripePaymentForm = stripePaymentForm; +})(window.Stripe); 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..ac1b07e55 100644 --- a/src/Modules/OrchardCore.Commerce.Payment/Services/PaymentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment/Services/PaymentService.cs @@ -263,15 +263,12 @@ public async Task CheckoutWithoutPaymentAsync(s { try { - return mustBeFree ? - await PaymentServiceExtensions.UpdateAndRedirectToFinishedOrderAsync( - this, + return mustBeFree + ? await this.UpdateAndRedirectToFinishedOrderAsync( order, shoppingCartId, FeatureIds.WithoutPaymentProvider) - : - await PaymentServiceExtensions.UpdateAndRedirectToFinishedOrderAsync( - this, + : await this.UpdateAndRedirectToFinishedOrderAsync( order, shoppingCartId, FeatureIds.NoNecessaryPaymentProvider); @@ -311,7 +308,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 +391,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..b4a2fb159 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,14 @@ protected override async Task CreatingOrUpdatingAsync(DiscountPart part) await InvalidateEvenStateAsync(); } - if ((part.ContentItem.As()?.Price is { } pricePartPrice && + var isPricePartInvalid = part.ContentItem.GetMaybe()?.Price is { } pricePartPrice && pricePartPrice.Currency.Equals(discountAmount.Currency) && - pricePartPrice < discountAmount) || - (part.ContentItem.As()?.GrossPrice.Amount is { IsValid: true } taxPartGrossPriceAmount && + pricePartPrice < discountAmount; + var isTaxPartInvalid = part.ContentItem.GetMaybe()?.GrossPrice.Amount is { IsValid: true } taxPartGrossPriceAmount && taxPartGrossPriceAmount.Currency.Equals(discountAmount.Currency) && - taxPartGrossPriceAmount < discountAmount)) + taxPartGrossPriceAmount < discountAmount; + + if (isPricePartInvalid || isTaxPartInvalid) { 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 diff --git a/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj b/src/OrchardCore.Commerce.Web/OrchardCore.Commerce.Web.csproj index c79b596d2..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-18960 + 3.0.0-preview-18997