using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Nop.Core; using Nop.Core.Domain.Orders; using Nop.Plugin.Payments.NopCliGeneric.Components; using Nop.Plugin.Payments.NopCliGeneric.Models; using Nop.Plugin.Payments.NopCliGeneric.Validators; using Nop.Services.Configuration; using Nop.Services.Localization; using Nop.Services.Orders; using Nop.Services.Payments; using Nop.Services.Plugins; using Nop.Services.Security; using RestSharp; namespace Nop.Plugin.Payments.NopCliGeneric { /// /// NopCliGeneric payment processor /// public class NopCliGenericPaymentProcessor : BasePlugin, IPaymentMethod { #region Fields private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILocalizationService _localizationService; private readonly ISettingService _settingService; private readonly IWebHelper _webHelper; private readonly NopCliGenericPaymentSettings _nopCliGenericPaymentSettings; private readonly IOrderProcessingService _orderProcessingService; private readonly IEncryptionService _encryptionService; private readonly IOrderTotalCalculationService _orderTotalCalculationService; #endregion #region Ctor public NopCliGenericPaymentProcessor( IHttpContextAccessor httpContextAccessor, ILocalizationService localizationService, ISettingService settingService, IWebHelper webHelper, NopCliGenericPaymentSettings nopCliGenericPaymentSettings, IOrderProcessingService orderProcessingService, IEncryptionService encryptionService, IOrderTotalCalculationService orderTotalCalculationService) { _httpContextAccessor = httpContextAccessor; _localizationService = localizationService; _settingService = settingService; _webHelper = webHelper; _nopCliGenericPaymentSettings = nopCliGenericPaymentSettings; _orderProcessingService = orderProcessingService; _encryptionService = encryptionService; _orderTotalCalculationService = orderTotalCalculationService; } #endregion #region Utilities /// /// Gets PDT details /// /// TX /// Values /// Response /// Result public Dictionary GetPdtDetails(string response) { var values = new Dictionary(StringComparer.OrdinalIgnoreCase); var firstLine = true; foreach (var l in response.Split('\n')) { var line = l.Trim(); if (firstLine) { firstLine = false; } else { var equalPox = line.IndexOf('='); if (equalPox >= 0) values.Add(line[..equalPox], line[(equalPox + 1)..]); } } return values; } /// /// Verifies IPN /// /// Form string /// Values /// Result public async Task VerifyIpnAsync(string formString, Dictionary values) { return await Task.Run(() => { var success = formString.Trim().ToLower().Contains("ResponseMessage".ToLower()); if (!success) success = formString.Trim().ToLower().Contains("ResponseCode".ToLower()); values = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var l in formString.Split('&')) { var line = l.Trim(); var equalPox = line.IndexOf('='); if (equalPox >= 0 && !values.Any(v => v.Key.Equals(line[..equalPox], StringComparison.OrdinalIgnoreCase))) { values.Add(line[..equalPox], line[(equalPox + 1)..]); } } return success; }); } #endregion #region Methods /// /// Process a payment /// /// Payment info required for an order processing /// Process payment result public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) { return new ProcessPaymentResult() { AllowStoringCreditCardNumber = _nopCliGenericPaymentSettings.IsStandard == false }; } public Task ProcessPaymentAsync(ProcessPaymentRequest processPaymentRequest) { throw new NotImplementedException(); } /// /// Post process payment (used by payment gateways that require redirecting to a third-party URL) /// /// Payment info required for an order processing public async Task PostProcessPaymentAsync(PostProcessPaymentRequest postProcessPaymentRequest) { if (_nopCliGenericPaymentSettings.IsStandard) { var url = $"{_webHelper.GetStoreLocation()}PaymentNopCliGeneric/Authorize"; if (_httpContextAccessor.HttpContext != null) _httpContextAccessor.HttpContext.Response.Redirect(url); } else { await ProcessPaymentAsync(postProcessPaymentRequest); } } public Task HidePaymentMethodAsync(IList cart) { throw new NotImplementedException(); } /// /// Returns a value indicating whether payment method should be hidden during checkout /// /// Shopping cart /// true - hide; false - display. public bool 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 false; } /// /// Gets additional handling fee /// /// Shopping cart /// Additional handling fee public async Task GetAdditionalHandlingFeeAsync(IList cart) { return await _orderTotalCalculationService.CalculatePaymentAdditionalFeeAsync(cart, _nopCliGenericPaymentSettings.AdditionalFee, _nopCliGenericPaymentSettings.AdditionalFeePercentage); } public Task ProcessRecurringPaymentAsync(ProcessPaymentRequest processPaymentRequest) { return Task.FromResult(new ProcessPaymentResult { Errors = new[] { "Recurring payment not supported" } }); } /// /// Captures payment /// /// Capture payment request /// Capture payment result public Task CaptureAsync(CapturePaymentRequest capturePaymentRequest) { return Task.FromResult(new CapturePaymentResult { Errors = new[] { "Capture method not supported" } }); } /// /// Refunds a payment /// /// Request /// Result public Task RefundAsync(RefundPaymentRequest refundPaymentRequest) { return Task.FromResult(new RefundPaymentResult { Errors = new[] { "Capture method not supported" } }); } /// /// Voids a payment /// /// Request /// Result public Task VoidAsync(VoidPaymentRequest voidPaymentRequest) { return Task.FromResult(new VoidPaymentResult { Errors = new[] { "Capture method not supported" } }); } /// /// Process recurring payment /// /// Payment info required for an order processing /// Process payment result public Task ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest) { return Task.FromResult(new ProcessPaymentResult { Errors = new[] { "Capture method not supported" } }); } /// /// Cancels a recurring payment /// /// Request /// Result public Task CancelRecurringPaymentAsync( CancelRecurringPaymentRequest cancelPaymentRequest) { return Task.FromResult( new CancelRecurringPaymentResult { Errors = new[] { "Recurring payment not supported" } }); } /// /// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods) /// /// Order /// Result public async Task CanRePostProcessPaymentAsync(Order order) { return await Task.Run(() => { if (order == null) throw new ArgumentNullException(nameof(order)); //let's ensure that at least 5 seconds passed after order is placed //P.S. there's no any particular reason for that. we just do it return !((DateTime.UtcNow - order.CreatedOnUtc).TotalSeconds < 5); }); } /// /// Validate payment form /// /// The parsed form values /// List of validating errors public async Task> ValidatePaymentFormAsync(IFormCollection form) { return await Task.Run(() => { var warnings = new List(); if (_nopCliGenericPaymentSettings.IsStandard) { return warnings; } //validate var validator = new PaymentInfoValidator(_localizationService); var model = new PaymentInfoModel { CardholderName = form["CardholderName"], CardNumber = form["CardNumber"], CardCode = form["CardCode"], ExpireMonth = form["ExpireMonth"], ExpireYear = form["ExpireYear"] }; var validationResult = validator.Validate(model); if (!validationResult.IsValid) warnings.AddRange(validationResult.Errors.Select(error => error.ErrorMessage)); return warnings; }); } internal PaymentResponseDto CreateBaseRequest(object data) { var nopCliGenericPaymentSettings = _settingService.LoadSettingAsync(0); var restClient = new RestClient(_nopCliGenericPaymentSettings.ApiUrl) { }; var restRequest = new RestRequest("payment", Method.POST); restRequest.AddJsonBody(data); restRequest.AddHeader("Cache-Control", "no-cache"); restRequest.AddHeader("Accept", "application/json"); restRequest.AddHeader("Content-Type", "application/json"); var response = restClient.Execute(restRequest); return JsonConvert.DeserializeObject(response.Content); } public async Task ProcessPaymentAsync(PostProcessPaymentRequest paymentRequest) { var response = CreateBaseRequest(new { Store = paymentRequest.Order.StoreId, CardNumber = _encryptionService.DecryptText(paymentRequest.Order.CardNumber), Expiration = _encryptionService.DecryptText(paymentRequest.Order.CardExpirationYear) + _encryptionService.DecryptText(paymentRequest.Order.CardExpirationMonth), CVC = _encryptionService.DecryptText(paymentRequest.Order.CardCvv2), Amount = paymentRequest.Order.OrderTotal, Itbis = paymentRequest.Order.OrderTax, OrderId = paymentRequest.Order.Id }); if (string.IsNullOrEmpty(response.ResponseMessage) || !response.ResponseMessage.Equals("APROBADA")) return; if (!string.IsNullOrEmpty(response.AuthorizationCode)) { paymentRequest.Order.AuthorizationTransactionCode = response.AuthorizationCode; await _orderProcessingService.MarkAsAuthorizedAsync(paymentRequest.Order); } paymentRequest.Order.AuthorizationTransactionId = response.OrderId; await _orderProcessingService.MarkOrderAsPaidAsync(paymentRequest.Order); } /// /// Get payment information /// /// The parsed form values /// Payment info holder public async Task GetPaymentInfoAsync(IFormCollection form) { return await Task.Run(() => { if (_nopCliGenericPaymentSettings.IsStandard) return new ProcessPaymentRequest(); return new ProcessPaymentRequest { CreditCardType = form["CreditCardType"], CreditCardName = form["CardholderName"], CreditCardNumber = form["CardNumber"], CreditCardExpireMonth = int.Parse(form["ExpireMonth"]), CreditCardExpireYear = int.Parse(form["ExpireYear"]), CreditCardCvv2 = form["CardCode"] }; }); } /// /// Gets a configuration page URL /// public override string GetConfigurationPageUrl() { return $"{_webHelper.GetStoreLocation()}Admin/PaymentNopCliGeneric/Configure"; } public string GetPublicViewComponentName() { return "PaymentNopCliGeneric"; } public Task GetPaymentMethodDescriptionAsync() { throw new NotImplementedException(); } /// /// Gets a type of a view component for displaying plugin in public store ("payment info" checkout step) /// /// View component type public Type GetPublicViewComponent() { return typeof(PaymentNopCliGenericViewComponent); } /// /// Install the plugin /// public override async Task InstallAsync() { //settings await _settingService.SaveSettingAsync(new NopCliGenericPaymentSettings { UseDev = true }); //locales await _localizationService.AddOrUpdateLocaleResourceAsync(new Dictionary { ["Plugins.Payments.NopCliGeneric.Fields.Url"] = "NopCliGeneric Url", ["Plugins.Payments.NopCliGeneric.Fields.UseDev"] = "Use Dev", ["Plugins.Payments.NopCliGeneric.Fields.AuthKey"] = "Auth Key", ["Plugins.Payments.NopCliGeneric.Fields.IsStandard"] = "Is Standard", ["Plugins.Payments.NopCliGeneric.Fields.AdditionalFee"] = "Additional fee", ["Plugins.Payments.NopCliGeneric.Fields.AdditionalFee.Hint"] = "Enter additional fee to charge your customers.", ["Plugins.Payments.NopCliGeneric.Fields.AdditionalFeePercentage"] = "Additional fee. Use percentage", ["Plugins.Payments.NopCliGeneric.Fields.AdditionalFeePercentage.Hint"] = "Determines whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used.", ["Plugins.Payments.NopCliGeneric.Fields.PassProductNamesAndTotals"] = "Pass product names and order totals to NopCliGeneric", ["Plugins.Payments.NopCliGeneric.Fields.PassProductNamesAndTotals.Hint"] = "Check if product names and order totals should be passed to NopCliGeneric.", ["Plugins.Payments.NopCliGeneric.Fields.RedirectionTip"] = "You will be redirected to NopCliGeneric site to complete the order.", ["Plugins.Payments.NopCliGeneric.Instructions"] = @"

If you're using this gateway ensure that your primary store currency is supported by NopCliGeneric.

To use PDT, you must activate PDT and Auto Return in your NopCliGeneric account profile. You must also acquire a PDT identity token, which is used in all PDT communication you send to NopCliGeneric. Follow these steps to configure your account for PDT:

1. Log in to your NopCliGeneric account (click here to create your account).
2. Click the Profile button.
3. Click the Profile and Settings button.
4. Select the My selling tools item on left panel.
5. Click Website Preferences Update in the Selling online section.
6. Under Auto Return for Website Payments, click the On radio button.
7. For the Return URL, enter the URL on your site that will receive the transaction ID posted by NopCliGeneric after a customer payment ({0}).
8. Under Payment Data Transfer, click the On radio button and get your PDT identity token.
9. Click Save.

", ["Plugins.Payments.NopCliGeneric.PaymentMethodDescription"] = "You will be redirected to NopCliGeneric site to complete the payment", ["Plugins.Payments.NopCliGeneric.RoundingWarning"] = "It looks like you have \"ShoppingCartSettings.RoundPricesDuringCalculation\" setting disabled. Keep in mind that this can lead to a discrepancy of the order total amount, as NopCliGeneric only rounds to two decimals." }); await base.InstallAsync(); } /// /// Uninstall the plugin /// public override async Task UninstallAsync() { //settings await _settingService.DeleteSettingAsync(); //locales await _localizationService.DeleteLocaleResourcesAsync("Plugins.Payments.NopCliGeneric"); await base.UninstallAsync(); } #endregion #region Properties /// /// Gets a value indicating whether capture is supported /// public bool SupportCapture => false; /// /// Gets a value indicating whether partial refund is supported /// public bool SupportPartiallyRefund => false; /// /// Gets a value indicating whether refund is supported /// public bool SupportRefund => false; /// /// Gets a value indicating whether void is supported /// public bool SupportVoid => false; /// /// Gets a recurring payment type of payment method /// public RecurringPaymentType RecurringPaymentType => RecurringPaymentType.NotSupported; /// /// Gets a payment method type /// public PaymentMethodType PaymentMethodType => PaymentMethodType.Redirection; /// /// Gets a value indicating whether we should display a payment information page for this plugin /// public bool SkipPaymentInfo => false; /// /// Gets a payment method description that will be displayed on checkout pages in the public store /// public Task PaymentMethodDescription => //return description of this payment method to be display on "payment method" checkout step. good practice is to make it localizable //for example, for a redirection payment method, description may be like this: "You will be redirected to NopCliGeneric site to complete the payment" _localizationService.GetResourceAsync("Plugins.Payments.NopCliGeneric.PaymentMethodDescription"); #endregion } }