diff --git a/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs b/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs index e3ee25ff42..f3e3a6a434 100644 --- a/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs @@ -218,7 +218,7 @@ await IsVatExempt(calculateTaxRequest.Address, calculateTaxRequest.Customer)) else { foreach (var error in calculateTaxResult.Errors) - _logger.LogError("{FriendlyName} - {Error}", activeTaxProvider.FriendlyName, error); + _logger.LogError("{SystemName} - {Error}", activeTaxProvider.SystemName, error); } return (taxRate, isTaxable); diff --git a/src/Business/Grand.Business.Checkout/Services/Shipping/ShippingService.cs b/src/Business/Grand.Business.Checkout/Services/Shipping/ShippingService.cs index 4aeee8ca9b..96962436fd 100644 --- a/src/Business/Grand.Business.Checkout/Services/Shipping/ShippingService.cs +++ b/src/Business/Grand.Business.Checkout/Services/Shipping/ShippingService.cs @@ -200,7 +200,7 @@ public virtual async Task GetShippingOptions(Customer foreach (var error in getShippingOptionResponse.Errors) { result.AddError(error); - _logger.LogWarning("Shipping ({FriendlyName}) {Error}", shippingRateMethod.FriendlyName, error); + _logger.LogWarning("Shipping ({SystemName}) {Error}", shippingRateMethod.SystemName, error); } //clear the shipping options in this case diff --git a/src/Business/Grand.Business.Core/Extensions/PaymentExtensions.cs b/src/Business/Grand.Business.Core/Extensions/PaymentExtensions.cs index 5fbd259c83..1e407d6492 100644 --- a/src/Business/Grand.Business.Core/Extensions/PaymentExtensions.cs +++ b/src/Business/Grand.Business.Core/Extensions/PaymentExtensions.cs @@ -1,4 +1,5 @@ using Grand.Business.Core.Interfaces.Checkout.Payments; +using Grand.Domain.Orders; using Grand.Domain.Payments; namespace Grand.Business.Core.Extensions; @@ -24,4 +25,29 @@ public static bool IsPaymentMethodActive(this IPaymentProvider paymentMethod, paymentSettings.ActivePaymentProviderSystemNames.Any(activeMethodSystemName => paymentMethod.SystemName.Equals(activeMethodSystemName, StringComparison.OrdinalIgnoreCase)); } + + /// + /// Gets the name of the order's payment method in the order's language, for text that does not follow + /// the current request (e-mails, PDF). Falls back to the friendly name when the order has no language, + /// and to the system name when the provider is missing or gives no name. + /// + /// Payment method, or null when it is no longer installed + /// Order + /// Payment method name + public static string GetPaymentMethodName(this IPaymentProvider paymentMethod, Order order) + { + ArgumentNullException.ThrowIfNull(order); + + if (paymentMethod == null) + return order.PaymentMethodSystemName; + + var name = string.IsNullOrEmpty(order.CustomerLanguageId) + ? paymentMethod.FriendlyName + : paymentMethod.GetFriendlyName(order.CustomerLanguageId); + + if (string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(order.CustomerLanguageId)) + name = paymentMethod.FriendlyName; + + return string.IsNullOrEmpty(name) ? order.PaymentMethodSystemName : name; + } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Interfaces/Checkout/Payments/IPaymentProvider.cs b/src/Business/Grand.Business.Core/Interfaces/Checkout/Payments/IPaymentProvider.cs index d248ed523e..b983b92532 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Checkout/Payments/IPaymentProvider.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Checkout/Payments/IPaymentProvider.cs @@ -106,6 +106,13 @@ public interface IPaymentProvider : IProvider /// Task GetControllerRouteName(); + /// + /// Gets a friendly name in the given language, for text that does not follow the current request + /// (e.g. order e-mails). Defaults to . + /// + /// Language identifier + string GetFriendlyName(string languageId) => FriendlyName; + #endregion #region Properties diff --git a/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs b/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs index fefe67d106..e93a6b99e2 100644 --- a/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs +++ b/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs @@ -129,8 +129,7 @@ public async Task Handle(GetOrderTokensCommand request, Cancellatio await _addressAttributeParser.FormatAttributes(language, request.Order.ShippingAddress?.Attributes); var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(request.Order.PaymentMethodSystemName); - liquidOrder.PaymentMethod = - paymentMethod != null ? paymentMethod.FriendlyName : request.Order.PaymentMethodSystemName; + liquidOrder.PaymentMethod = paymentMethod.GetPaymentMethodName(request.Order); var dict = new Dictionary(); foreach (var item in request.Order.OrderTaxes) diff --git a/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs b/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs index dce6645233..6a57cb995a 100644 --- a/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs +++ b/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs @@ -347,6 +347,9 @@ public async Task SupportVoid() public string FriendlyName => _translationService.GetResource(BrainTreeDefaults.FriendlyName); + public string GetFriendlyName(string languageId) => + _translationService.GetResource(BrainTreeDefaults.FriendlyName, languageId, returnEmptyIfNotFound: true); + public int Priority => _brainTreePaymentSettings.DisplayOrder; public IList LimitedToStores => new List(); diff --git a/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs b/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs index 88b07a5c0e..6027133c69 100644 --- a/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs +++ b/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs @@ -41,6 +41,9 @@ public CashOnDeliveryPaymentProvider( public string FriendlyName => _translationService.GetResource(CashOnDeliveryPaymentDefaults.FriendlyName); + public string GetFriendlyName(string languageId) => + _translationService.GetResource(CashOnDeliveryPaymentDefaults.FriendlyName, languageId, returnEmptyIfNotFound: true); + public int Priority => _cashOnDeliveryPaymentSettings.DisplayOrder; public IList LimitedToStores => new List(); diff --git a/src/Plugins/Payments.StripeCheckout/StripeCheckoutPaymentProvider.cs b/src/Plugins/Payments.StripeCheckout/StripeCheckoutPaymentProvider.cs index 724ec1456d..291178637a 100644 --- a/src/Plugins/Payments.StripeCheckout/StripeCheckoutPaymentProvider.cs +++ b/src/Plugins/Payments.StripeCheckout/StripeCheckoutPaymentProvider.cs @@ -41,6 +41,9 @@ public StripeCheckoutPaymentProvider( public string FriendlyName => _translationService.GetResource(StripeCheckoutDefaults.FriendlyName); + public string GetFriendlyName(string languageId) => + _translationService.GetResource(StripeCheckoutDefaults.FriendlyName, languageId, returnEmptyIfNotFound: true); + public int Priority => _stripeCheckoutPaymentSettings.DisplayOrder; public IList LimitedToStores => new List(); diff --git a/src/Tests/Grand.Business.Checkout.Tests/Extensions/PaymentExtensionsTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Extensions/PaymentExtensionsTests.cs index a798d5adeb..0570bb140d 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Extensions/PaymentExtensionsTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Extensions/PaymentExtensionsTests.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Checkout.Payments; +using Grand.Domain.Orders; using Grand.Domain.Payments; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -42,4 +43,48 @@ public void IsPaymentMethodActive_NullProvider_ThrowException() var settings = new PaymentSettings(); Assert.ThrowsExactly(() => provider.IsPaymentMethodActive(settings)); } -} \ No newline at end of file + + [TestMethod] + public void GetPaymentMethodName_OrderLanguage_ReturnNameInOrderLanguage() + { + _providerMock.Setup(c => c.FriendlyName).Returns("Request language"); + _providerMock.Setup(c => c.GetFriendlyName("1")).Returns("Order language"); + var order = new Order { CustomerLanguageId = "1", PaymentMethodSystemName = "Payments.Test" }; + Assert.AreEqual("Order language", _providerMock.Object.GetPaymentMethodName(order)); + } + + [TestMethod] + public void GetPaymentMethodName_NoOrderLanguage_ReturnFriendlyName() + { + _providerMock.Setup(c => c.FriendlyName).Returns("Request language"); + var order = new Order { CustomerLanguageId = null, PaymentMethodSystemName = "Payments.Test" }; + Assert.AreEqual("Request language", _providerMock.Object.GetPaymentMethodName(order)); + _providerMock.Verify(c => c.GetFriendlyName(It.IsAny()), Times.Never); + } + + [TestMethod] + public void GetPaymentMethodName_NoTranslation_ReturnFriendlyName() + { + _providerMock.Setup(c => c.FriendlyName).Returns("Request language"); + _providerMock.Setup(c => c.GetFriendlyName("1")).Returns(""); + var order = new Order { CustomerLanguageId = "1", PaymentMethodSystemName = "Payments.Test" }; + Assert.AreEqual("Request language", _providerMock.Object.GetPaymentMethodName(order)); + } + + [TestMethod] + public void GetPaymentMethodName_NoName_ReturnSystemName() + { + _providerMock.Setup(c => c.FriendlyName).Returns(""); + _providerMock.Setup(c => c.GetFriendlyName("1")).Returns(""); + var order = new Order { CustomerLanguageId = "1", PaymentMethodSystemName = "Payments.Test" }; + Assert.AreEqual("Payments.Test", _providerMock.Object.GetPaymentMethodName(order)); + } + + [TestMethod] + public void GetPaymentMethodName_NullProvider_ReturnSystemName() + { + IPaymentProvider provider = null; + var order = new Order { PaymentMethodSystemName = "Payments.Test" }; + Assert.AreEqual("Payments.Test", provider.GetPaymentMethodName(order)); + } +} diff --git a/src/Tests/Grand.Business.Messages.Tests/Commands/GetOrderTokensCommandHandlerTests.cs b/src/Tests/Grand.Business.Messages.Tests/Commands/GetOrderTokensCommandHandlerTests.cs index 08816ea40c..6e666dc5b2 100644 --- a/src/Tests/Grand.Business.Messages.Tests/Commands/GetOrderTokensCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Messages.Tests/Commands/GetOrderTokensCommandHandlerTests.cs @@ -24,6 +24,7 @@ public class GetOrderTokensCommandHandlerTests { private readonly Currency _orderCurrency = new() { CurrencyCode = "EUR" }; private GetOrderTokensCommandHandler _handler; + private Mock _paymentServiceMock; private Mock _priceFormatterMock; [TestInitialize] @@ -41,10 +42,12 @@ public void Init() _priceFormatterMock.Setup(x => x.FormatPrice(It.IsAny(), It.IsAny())) .Returns((double price, Currency currency) => $"{currency?.CurrencyCode}{price}"); + _paymentServiceMock = new Mock(); + _handler = new GetOrderTokensCommandHandler(languageService.Object, currencyService.Object, new Mock().Object, new Mock().Object, _priceFormatterMock.Object, new Mock().Object, new Mock().Object, - new Mock().Object, giftVoucherService.Object, new TaxSettings()); + _paymentServiceMock.Object, giftVoucherService.Object, new TaxSettings()); } [TestMethod] @@ -62,4 +65,26 @@ public async Task Handle_FormatsTaxRatesInTheOrderCurrency() //Assert Assert.AreEqual("EUR10", result.TaxRates["23"]); } + + [TestMethod] + public async Task Handle_PaymentMethodNameInTheCustomerLanguage() + { + //Arrange + var paymentProvider = new Mock(); + paymentProvider.Setup(x => x.FriendlyName).Returns("Request language"); + paymentProvider.Setup(x => x.GetFriendlyName("1")).Returns("Customer language"); + _paymentServiceMock.Setup(x => x.LoadPaymentMethodBySystemName("Payments.Test")) + .Returns(paymentProvider.Object); + var order = new Order { + CustomerCurrencyCode = "EUR", CustomerLanguageId = "1", PaymentMethodSystemName = "Payments.Test" + }; + + //Act + var result = await _handler.Handle(new GetOrderTokensCommand { + Order = order, Customer = new Customer(), Store = new Store(), Host = new DomainHost { Url = "https://localhost/" } + }, CancellationToken.None); + + //Assert + Assert.AreEqual("Customer language", result.PaymentMethod); + } } diff --git a/src/Web/Grand.Web.Vendor/Areas/Vendor/Views/PdfTemplates/OrderPdfTemplate.cshtml b/src/Web/Grand.Web.Vendor/Areas/Vendor/Views/PdfTemplates/OrderPdfTemplate.cshtml index a13dc71806..1b644c3cf7 100644 --- a/src/Web/Grand.Web.Vendor/Areas/Vendor/Views/PdfTemplates/OrderPdfTemplate.cshtml +++ b/src/Web/Grand.Web.Vendor/Areas/Vendor/Views/PdfTemplates/OrderPdfTemplate.cshtml @@ -1,3 +1,4 @@ +@using Grand.Business.Core.Extensions @using Grand.Business.Core.Interfaces.Catalog.Products @using Grand.Business.Core.Interfaces.Checkout.GiftVouchers @using Grand.Business.Core.Interfaces.Checkout.Payments @@ -152,7 +153,7 @@ @((await _countryService.GetCountryById(order.BillingAddress.CountryId))?.Name)

var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); - var paymentMethodStr = paymentMethod != null ? paymentMethod.FriendlyName : order.PaymentMethodSystemName; + var paymentMethodStr = paymentMethod.GetPaymentMethodName(order); @(string.Format(_translationService.GetResource("PDFInvoice.PaymentMethod", order.CustomerLanguageId), paymentMethodStr))
@await Component.InvokeAsync("Widget", new { widgetZone = "pdf_payment_method_below", additionalData = order.Id }) diff --git a/src/Web/Grand.Web/Views/PdfTemplates/OrderPdfTemplate.cshtml b/src/Web/Grand.Web/Views/PdfTemplates/OrderPdfTemplate.cshtml index f6de98c094..6b42eb2305 100644 --- a/src/Web/Grand.Web/Views/PdfTemplates/OrderPdfTemplate.cshtml +++ b/src/Web/Grand.Web/Views/PdfTemplates/OrderPdfTemplate.cshtml @@ -1,3 +1,4 @@ +@using Grand.Business.Core.Extensions @using Grand.Business.Core.Interfaces.Catalog.Products @using Grand.Business.Core.Interfaces.Checkout.GiftVouchers @using Grand.Business.Core.Interfaces.Checkout.Payments @@ -152,7 +153,7 @@ @((await _countryService.GetCountryById(order.BillingAddress.CountryId))?.Name)

var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); - var paymentMethodStr = paymentMethod != null ? paymentMethod.FriendlyName : order.PaymentMethodSystemName; + var paymentMethodStr = paymentMethod.GetPaymentMethodName(order); @(string.Format(_translationService.GetResource("PDFInvoice.PaymentMethod", order.CustomerLanguageId), paymentMethodStr))
@await Component.InvokeAsync("Widget", new { widgetZone = "pdf_payment_method_below", additionalData = order.Id })