using Grand.Business.Core.Enums.Checkout; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Checkout.Payments; using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Utilities.Checkout; using Grand.Domain.Customers; using Grand.Domain.Orders; using Grand.Domain.Payments; using Grand.Infrastructure; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Payments.BTCPayServer.Models; using Payments.BTCPayServer.Services; #nullable enable namespace Payments.BTCPayServer { public class BTCPayServerPaymentProvider : IPaymentProvider { private readonly ITranslationService _translationService; private readonly BtcPaySettings _btcPaySettings; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IServiceProvider _serviceProvider; private readonly IWorkContext _workContext; private readonly ILogger _logger; private readonly IOrderService _orderService; private readonly LinkGenerator _linkGenerator; private readonly Func _btcPayService; public BTCPayServerPaymentProvider( IHttpContextAccessor httpContextAccessor, ITranslationService translationService, IServiceProvider serviceProvider, IWorkContext workContext, BtcPaySettings btcPaySettings, LinkGenerator linkGenerator, IOrderService orderService, ILogger logger, Func btcPayService) { _httpContextAccessor = httpContextAccessor; _translationService = translationService; _serviceProvider = serviceProvider; _workContext = workContext; _btcPaySettings = btcPaySettings; _linkGenerator = linkGenerator; _orderService = orderService; _logger = logger; _btcPayService = btcPayService; } public PaymentMethodType PaymentMethodType => PaymentMethodType.Redirection; public string LogoURL => "/Plugins/Payments.BTCPayServer/logo.jpg"; public string ConfigurationUrl => "/Admin/BTCPayServer/Configure"; public string SystemName => "Payments.BTCPayServer"; public string FriendlyName => "BTCPay - Pay in bitcoins"; public int Priority => _btcPaySettings.DisplayOrder; public IList LimitedToStores => new List(); public IList LimitedToGroups => new List(); Task IPaymentProvider.CancelPayment(PaymentTransaction paymentTransaction) { return Task.CompletedTask; } /// /// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods) /// /// Result public async Task CanRePostRedirectPayment(PaymentTransaction paymentTransaction) { if (paymentTransaction == null) throw new ArgumentNullException(nameof(paymentTransaction)); if (paymentTransaction.TransactionStatus == TransactionStatus.Pending) return await Task.FromResult(true); return await Task.FromResult(false); } public async Task SupportCapture() { return await Task.FromResult(false); } public async Task Capture(PaymentTransaction paymentTransaction) { var result = new CapturePaymentResult(); result.AddError("Capture method not supported"); return await Task.FromResult(result); } public async Task Description() { return await Task.FromResult(_translationService.GetResource("Plugins.Payments.BTCPayServer.PaymentMethodDescription2")); } public async Task GetAdditionalHandlingFee(IList cart) { if (_btcPaySettings.AdditionalFee <= 0) return _btcPaySettings.AdditionalFee; double result; if (_btcPaySettings.AdditionalFeePercentage) { //percentage var orderTotalCalculationService = _serviceProvider.GetRequiredService(); var subtotal = await orderTotalCalculationService.GetShoppingCartSubTotal(cart, true); result = (float)subtotal.subTotalWithDiscount * (float)_btcPaySettings.AdditionalFee / 100f; } else { result = _btcPaySettings.AdditionalFee; } if (!(result > 0)) return result; var currencyService = _serviceProvider.GetRequiredService(); result = await currencyService.ConvertFromPrimaryStoreCurrency(result, _workContext.WorkingCurrency); return result; } public Task GetControllerRouteName() { return Task.FromResult("Plugin.PaymentBTCPayServer"); } public async Task HidePaymentMethod(IList cart) { //you can put any logic here //for example, hide this payment method if all products in the cart are downloadable //or hide this payment method if current customer is from certain country return await Task.FromResult(false); } public Task InitPaymentTransaction() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. return Task.FromResult(null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } public async Task ProcessPayment(PaymentTransaction paymentTransaction) { var result = new ProcessPaymentResult(); return await Task.FromResult(result); } public Task PostProcessPayment(PaymentTransaction paymentTransaction) { //nothing return Task.CompletedTask; } public async Task PostRedirectPayment(PaymentTransaction paymentTransaction) { // implement process payment Order? order = null; var myStore = _workContext.CurrentStore; try { var lang = _workContext.WorkingLanguage; var langCode = (lang == null) ? "en" : lang.UniqueSeoCode; Customer? myCustomer = _workContext.CurrentCustomer; var invoice = await _btcPayService().CreateInvoice(_btcPaySettings, new PaymentDataModel() { CurrencyCode = paymentTransaction.CurrencyCode, Amount = (decimal)paymentTransaction.TransactionAmount, BuyerEmail = myCustomer.Email ?? (myCustomer.BillingAddress.Email ?? myCustomer.ShippingAddress.Email), BuyerName = myCustomer.GetFullName(), OrderID = paymentTransaction.OrderGuid.ToString(), StoreID = myStore.Id, CustomerID = myCustomer.Id, Description = "From " + myStore.Name, RedirectionURL = myStore.Url + "checkout/completed", Lang = langCode, OrderUrl = new Uri(new Uri(myStore.Url), _linkGenerator.GetPathByAction("Index", "OrderBTCPayServer", new { id = paymentTransaction.OrderGuid })).ToString() }); order = await _orderService.GetOrderByGuid(paymentTransaction.OrderGuid); order.OrderTags.Add($"AuthorizationTransactionResult#{invoice.CheckoutLink}"); order.OrderTags.Add($"AuthorizationTransactionId#{invoice.Id}"); order.OrderTags.Add($"CaptureTransactionResult#{(invoice.Receipt?.Enabled is true ? invoice.CheckoutLink + "/receipt" : null)}"); await _orderService.UpdateOrder(order); // _httpContextAccessor.HttpContext?.Response.Redirect(invoice.CheckoutLink); return invoice.CheckoutLink; } catch (Exception ex) { _logger.LogError(ex.Message); if (order == null) { order = await _orderService.GetOrderByGuid(paymentTransaction.OrderGuid); } await _orderService.InsertOrderNote(new OrderNote { OrderId = order.Id, Note = $"Error creating BTCPay payment.", DisplayToCustomer = true, CreatedOnUtc = DateTime.UtcNow }); return string.Empty; } } public async Task Refund(RefundPaymentRequest refundPaymentRequest) { var result = new RefundPaymentResult(); try { var order = await _orderService.GetOrderByGuid(refundPaymentRequest.PaymentTransaction.OrderGuid); var sUrl = await _btcPayService().CreateRefund(_btcPaySettings, refundPaymentRequest); if (sUrl == null) { throw new Exception("Refund : Error with BTCPay"); } result.NewTransactionStatus = refundPaymentRequest.IsPartialRefund ? TransactionStatus.PartiallyRefunded : TransactionStatus.Refunded; await _orderService.InsertOrderNote(new OrderNote { OrderId = order.Id, Note = _translationService.GetResource("Plugins.Payments.BTCPayServer.NoteRefund") + $" {sUrl}", DisplayToCustomer = true, CreatedOnUtc = DateTime.UtcNow }); } catch (Exception ex) { _logger.LogError(ex.Message); result.AddError(ex.Message); } return await Task.FromResult(result); } public async Task SavePaymentInfo(IDictionary model) { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. return await Task.FromResult(null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } public async Task SkipPaymentInfo() { return await Task.FromResult(false); } public async Task SupportPartiallyRefund() { return await Task.FromResult(true); } public async Task SupportRefund() { return await Task.FromResult(true); } public async Task SupportVoid() { return await Task.FromResult(true); } public async Task Void(PaymentTransaction paymentTransaction) { try { return new VoidPaymentResult() { NewTransactionStatus = paymentTransaction.TransactionStatus }; } catch (Exception ex) { _logger.LogError(ex.Message); return new VoidPaymentResult() { NewTransactionStatus = TransactionStatus.Voided }; } } public async Task> ValidatePaymentForm(IDictionary model) { return await Task.FromResult(new List()); } } }