513 lines
21 KiB
C#
Executable File
513 lines
21 KiB
C#
Executable File
using Microsoft.AspNetCore.Http;
|
|
using Nop.Core;
|
|
using Nop.Core.Domain.Orders;
|
|
using Nop.Core.Domain.Payments;
|
|
using Nop.Plugin.Payments.TapPay.Components;
|
|
using Nop.Plugin.Payments.TapPay.Domain;
|
|
using Nop.Plugin.Payments.TapPay.Models;
|
|
using Nop.Plugin.Payments.TapPay.Services;
|
|
using Nop.Services.Configuration;
|
|
using Nop.Services.Customers;
|
|
using Nop.Services.Localization;
|
|
using Nop.Services.Logging;
|
|
using Nop.Services.Orders;
|
|
using Nop.Services.Payments;
|
|
using Nop.Services.Plugins;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Nop.Plugin.Payments.TapPay;
|
|
|
|
/// <summary>
|
|
/// TapPay payment processor
|
|
/// </summary>
|
|
public class TapPayPaymentProcessor : BasePlugin, IPaymentMethod
|
|
{
|
|
#region Fields
|
|
|
|
protected readonly ILocalizationService _localizationService;
|
|
protected readonly IOrderTotalCalculationService _orderTotalCalculationService;
|
|
protected readonly ISettingService _settingService;
|
|
protected readonly ICustomerService _customerService;
|
|
protected readonly IOrderService _orderService;
|
|
protected readonly IStoreContext _storeContext;
|
|
protected readonly ITapPayResultService _tapPayResultService;
|
|
protected readonly IWebHelper _webHelper;
|
|
protected readonly IHttpContextAccessor _httpContextAccessor;
|
|
protected readonly ILogger _logger;
|
|
protected readonly TapPayPaymentSettings _tapPayPaymentSettings;
|
|
|
|
#endregion
|
|
|
|
#region Ctor
|
|
|
|
public TapPayPaymentProcessor(ILocalizationService localizationService,
|
|
IOrderTotalCalculationService orderTotalCalculationService,
|
|
ISettingService settingService,
|
|
ICustomerService customerService,
|
|
IOrderService orderService,
|
|
IStoreContext storeContext,
|
|
ITapPayResultService tapPayResultService,
|
|
IWebHelper webHelper,
|
|
IHttpContextAccessor httpContextAccessor,
|
|
ILogger logger,
|
|
TapPayPaymentSettings tapPayPaymentSettings)
|
|
{
|
|
_localizationService = localizationService;
|
|
_orderTotalCalculationService = orderTotalCalculationService;
|
|
_settingService = settingService;
|
|
_customerService = customerService;
|
|
_orderService = orderService;
|
|
_storeContext = storeContext;
|
|
_tapPayResultService = tapPayResultService;
|
|
_webHelper = webHelper;
|
|
_httpContextAccessor = httpContextAccessor;
|
|
_logger = logger;
|
|
_tapPayPaymentSettings = tapPayPaymentSettings;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Process a payment
|
|
/// </summary>
|
|
/// <param name="processPaymentRequest">Payment info required for an order processing</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the process payment result
|
|
/// </returns>
|
|
public async Task<ProcessPaymentResult> ProcessPaymentAsync(ProcessPaymentRequest processPaymentRequest)
|
|
{
|
|
var result = new ProcessPaymentResult();
|
|
|
|
//load settings for a chosen store scope
|
|
var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync();
|
|
// var _tapPayPaymentSettings = await _settingService.LoadSettingAsync<TapPayPaymentSettings>(storeScope);
|
|
|
|
var customer = await _customerService.GetCustomerByIdAsync(processPaymentRequest.CustomerId);
|
|
var customerBillingAddress = await _customerService.GetCustomerBillingAddressAsync(customer);
|
|
|
|
string GetCustomValue(string key)
|
|
{
|
|
if (processPaymentRequest.CustomValues.TryGetValue(key, out var value) && value != null)
|
|
return value.ToString().Trim();
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
var prime = GetCustomValue("Prime");
|
|
if (string.IsNullOrEmpty(prime))
|
|
{
|
|
result.AddError("TapPay prime is missing.");
|
|
return result;
|
|
}
|
|
|
|
var cardholderEmail = GetCustomValue("CardholderEmail");
|
|
var cardholderPhone = GetCustomValue("CardholderPhoneNumber");
|
|
var cardholderPhoneCode = GetCustomValue("CardholderPhoneNumberCountryCode");
|
|
var cardholderNameEn = GetCustomValue("CardholderNameEn");
|
|
|
|
var requestBody = JsonSerializer.Serialize(
|
|
new {
|
|
partner_key = _tapPayPaymentSettings.PartnerKey,
|
|
prime = prime,
|
|
amount = (int)processPaymentRequest.OrderTotal,
|
|
merchant_id = _tapPayPaymentSettings.Merchant.ToString(),
|
|
order_number = processPaymentRequest.OrderGuid.ToString(),
|
|
//bank_transaction_id = processPaymentRequest.OrderGuid.ToString("N"),
|
|
three_domain_secure = true,
|
|
result_url = new {
|
|
frontend_redirect_url = _tapPayPaymentSettings.RedirectUrl,
|
|
backend_notify_url = _tapPayPaymentSettings.NotifyUrl,
|
|
go_back_url = _tapPayPaymentSettings.GoBackUrl
|
|
},
|
|
details = "AI Hardware / APP",
|
|
cardholder = new {
|
|
phone_number = cardholderPhone,
|
|
phone_number_country_code = string.IsNullOrWhiteSpace(cardholderPhoneCode) ? "886" : cardholderPhoneCode,
|
|
name_en = cardholderNameEn,
|
|
name = (customerBillingAddress?.LastName ?? string.Empty) + (customerBillingAddress?.FirstName ?? string.Empty),
|
|
email = cardholderEmail,
|
|
zip_code = customerBillingAddress?.ZipPostalCode,
|
|
address = (customerBillingAddress?.City ?? string.Empty) + (customerBillingAddress?.Address1 ?? string.Empty) + (customerBillingAddress?.Address2 ?? string.Empty),
|
|
member_id = processPaymentRequest.CustomerId.ToString()
|
|
}
|
|
}
|
|
);
|
|
|
|
using(var client = new HttpClient())
|
|
{
|
|
var request = new HttpRequestMessage {
|
|
RequestUri = new Uri(_tapPayPaymentSettings.ServiceEndpoint),
|
|
Method = HttpMethod.Post,
|
|
Headers = {
|
|
{ "X-Version", "1" },
|
|
{ "x-api-key", _tapPayPaymentSettings.PartnerKey },
|
|
},
|
|
Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
|
|
};
|
|
var response = await client.SendAsync(request);
|
|
try
|
|
{
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
catch
|
|
{
|
|
var failedResult = new TapPayResult()
|
|
{
|
|
order_number = processPaymentRequest.OrderGuid,
|
|
RequestHeader = request.ToString(),
|
|
RequestBody = requestBody,
|
|
ResponseHeader = response.ToString(),
|
|
CreatedOnUtc = DateTime.UtcNow,
|
|
};
|
|
await _tapPayResultService.InsertTapPayResultAsync(failedResult);
|
|
|
|
throw;
|
|
}
|
|
|
|
var responseBody = await response.Content.ReadAsStringAsync();
|
|
var responseBodyObject = JsonSerializer.Deserialize<TapPayResultModel>(responseBody);
|
|
|
|
try
|
|
{
|
|
var paymentResult = new TapPayResult()
|
|
{
|
|
order_number = new Guid(responseBodyObject.order_number),
|
|
status = responseBodyObject.status,
|
|
msg = responseBodyObject.msg,
|
|
rec_trade_id = responseBodyObject.rec_trade_id,
|
|
bank_transaction_id = responseBodyObject.bank_transaction_id,
|
|
bank_order_number = responseBodyObject.bank_order_number,
|
|
auth_code = responseBodyObject.auth_code,
|
|
card_secret = JsonSerializer.Serialize(responseBodyObject.card_secret),
|
|
amount = responseBodyObject.amount,
|
|
currency = responseBodyObject.currency,
|
|
card_info = JsonSerializer.Serialize(responseBodyObject.card_info),
|
|
acquirer = responseBodyObject.acquirer,
|
|
transaction_time_millis = responseBodyObject.transaction_time_millis,
|
|
bank_transaction_time = JsonSerializer.Serialize(responseBodyObject.bank_transaction_time),
|
|
bank_result_code = responseBodyObject.bank_result_code,
|
|
bank_result_msg = responseBodyObject.bank_result_msg,
|
|
payment_url = responseBodyObject.payment_url,
|
|
instalment_info = JsonSerializer.Serialize(responseBodyObject.instalment_info),
|
|
redeem_info = JsonSerializer.Serialize(responseBodyObject.redeem_info),
|
|
card_identifier = responseBodyObject.card_identifier,
|
|
merchant_reference_info = JsonSerializer.Serialize(responseBodyObject.merchant_reference_info),
|
|
event_code = responseBodyObject.event_code,
|
|
is_rba_verified = responseBodyObject.is_rba_verified,
|
|
transaction_method_details = JsonSerializer.Serialize(responseBodyObject.transaction_method_details),
|
|
RequestHeader = request.ToString(),
|
|
RequestBody = requestBody,
|
|
ResponseHeader = response.ToString(),
|
|
ResponseBody = responseBody,
|
|
CreatedOnUtc = DateTime.UtcNow,
|
|
};
|
|
await _tapPayResultService.InsertTapPayResultAsync(paymentResult);
|
|
}
|
|
catch
|
|
{
|
|
await _logger.InformationAsync(request.ToString() + "\nBody:\n" + requestBody);
|
|
await _logger.InformationAsync(response.ToString() + "\nBody:\n" + responseBody);
|
|
}
|
|
|
|
if(responseBodyObject.status != 0)
|
|
{
|
|
result.AddError(responseBodyObject.msg);
|
|
throw new NopException(responseBodyObject.msg);
|
|
}
|
|
else
|
|
{
|
|
if(string.IsNullOrEmpty(responseBodyObject.payment_url))
|
|
{
|
|
result.NewPaymentStatus = PaymentStatus.Paid;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Post process payment (used by payment gateways that require redirecting to a third-party URL)
|
|
/// </summary>
|
|
/// <param name="postProcessPaymentRequest">Payment info required for an order processing</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public Task PostProcessPaymentAsync(PostProcessPaymentRequest postProcessPaymentRequest)
|
|
{
|
|
if(postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Pending)
|
|
{
|
|
var lastPaymentResult = _tapPayResultService.GetByOrderGuidAsync(postProcessPaymentRequest.Order.OrderGuid).Result;
|
|
if(lastPaymentResult != null && !string.IsNullOrEmpty(lastPaymentResult.payment_url))
|
|
{
|
|
_httpContextAccessor.HttpContext.Response.Redirect(lastPaymentResult.payment_url);
|
|
}
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether payment method should be hidden during checkout
|
|
/// </summary>
|
|
/// <param name="cart">Shopping cart</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains true - hide; false - display.
|
|
/// </returns>
|
|
public Task<bool> HidePaymentMethodAsync(IList<ShoppingCartItem> 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 Task.FromResult(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets additional handling fee
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the additional handling fee
|
|
/// </returns>
|
|
public async Task<decimal> GetAdditionalHandlingFeeAsync(IList<ShoppingCartItem> cart)
|
|
{
|
|
return await _orderTotalCalculationService.CalculatePaymentAdditionalFeeAsync(cart, 0, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Captures payment
|
|
/// </summary>
|
|
/// <param name="capturePaymentRequest">Capture payment request</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the capture payment result
|
|
/// </returns>
|
|
public Task<CapturePaymentResult> CaptureAsync(CapturePaymentRequest capturePaymentRequest)
|
|
{
|
|
return Task.FromResult(new CapturePaymentResult { Errors = new[] { "Capture method not supported" } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refunds a payment
|
|
/// </summary>
|
|
/// <param name="refundPaymentRequest">Request</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the result
|
|
/// </returns>
|
|
public Task<RefundPaymentResult> RefundAsync(RefundPaymentRequest refundPaymentRequest)
|
|
{
|
|
return Task.FromResult(new RefundPaymentResult { Errors = new[] { "Refund method not supported" } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Voids a payment
|
|
/// </summary>
|
|
/// <param name="voidPaymentRequest">Request</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the result
|
|
/// </returns>
|
|
public Task<VoidPaymentResult> VoidAsync(VoidPaymentRequest voidPaymentRequest)
|
|
{
|
|
return Task.FromResult(new VoidPaymentResult { Errors = new[] { "Void method not supported" } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process recurring payment
|
|
/// </summary>
|
|
/// <param name="processPaymentRequest">Payment info required for an order processing</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the process payment result
|
|
/// </returns>
|
|
public Task<ProcessPaymentResult> ProcessRecurringPaymentAsync(ProcessPaymentRequest processPaymentRequest)
|
|
{
|
|
var result = new ProcessPaymentResult
|
|
{
|
|
AllowStoringCreditCardNumber = true
|
|
};
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels a recurring payment
|
|
/// </summary>
|
|
/// <param name="cancelPaymentRequest">Request</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the result
|
|
/// </returns>
|
|
public Task<CancelRecurringPaymentResult> CancelRecurringPaymentAsync(CancelRecurringPaymentRequest cancelPaymentRequest)
|
|
{
|
|
//always success
|
|
return Task.FromResult(new CancelRecurringPaymentResult());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods)
|
|
/// </summary>
|
|
/// <param name="order">Order</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the result
|
|
/// </returns>
|
|
public Task<bool> CanRePostProcessPaymentAsync(Order order)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(order);
|
|
|
|
//it's not a redirection payment method. So we always return false
|
|
return Task.FromResult(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate payment form
|
|
/// </summary>
|
|
/// <param name="form">The parsed form values</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the list of validating errors
|
|
/// </returns>
|
|
public Task<IList<string>> ValidatePaymentFormAsync(IFormCollection form)
|
|
{
|
|
var warnings = new List<string>();
|
|
|
|
return Task.FromResult<IList<string>>(warnings);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get payment information
|
|
/// </summary>
|
|
/// <param name="form">The parsed form values</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the payment info holder
|
|
/// </returns>
|
|
public Task<ProcessPaymentRequest> GetPaymentInfoAsync(IFormCollection form)
|
|
{
|
|
var paymentRequest = new ProcessPaymentRequest();
|
|
paymentRequest.CustomValues.Add("Prime", form["Prime"].ToString());
|
|
paymentRequest.CustomValues.Add("CardholderEmail", form["CardholderEmail"].ToString());
|
|
paymentRequest.CustomValues.Add("CardholderPhoneNumber", form["CardholderPhoneNumber"].ToString());
|
|
paymentRequest.CustomValues.Add("CardholderPhoneNumberCountryCode", form["CardholderPhoneNumberCountryCode"].ToString());
|
|
paymentRequest.CustomValues.Add("CardholderNameEn", form["CardholderNameEn"].ToString());
|
|
return Task.FromResult(paymentRequest);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a configuration page URL
|
|
/// </summary>
|
|
public override string GetConfigurationPageUrl()
|
|
{
|
|
return $"{_webHelper.GetStoreLocation()}Admin/PaymentTapPay/Configure";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a type of a view component for displaying plugin in public store ("payment info" checkout step)
|
|
/// </summary>
|
|
/// <returns>View component type</returns>
|
|
public Type GetPublicViewComponent()
|
|
{
|
|
return typeof(PaymentTapPayViewComponent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Install the plugin
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public override async Task InstallAsync()
|
|
{
|
|
//locales
|
|
await _localizationService.AddOrUpdateLocaleResourceAsync(new Dictionary<string, string>
|
|
{
|
|
["Plugins.Payments.TapPay.Instructions"] = "TapPay App",
|
|
["Plugins.Payments.TapPay.PartnerKey"] = "Partner Key",
|
|
["Plugins.Payments.TapPay.AppId"] = "App ID",
|
|
["Plugins.Payments.TapPay.AppKey"] = "App Key",
|
|
["Plugins.Payments.TapPay.ServerType"] = "Sandbox / Production",
|
|
["Plugins.Payments.TapPay.ServiceEndpoint"] = "TapPay payment service endpoint",
|
|
["Plugins.Payments.TapPay.RecordEndpoint"] = "TapPay record api endpoint",
|
|
["Plugins.Payments.TapPay.Merchant"] = "Merchant Store",
|
|
["Plugins.Payments.TapPay.PaymentMethodDescription"] = "TapPay payment",
|
|
["Plugins.Payments.TapPay.RedirectUrl"] = "Frontend Redirect Url",
|
|
["Plugins.Payments.TapPay.NotifyUrl"] = "Backend Notify Url",
|
|
["Plugins.Payments.TapPay.GoBackUrl"] = "Go Back Url",
|
|
});
|
|
|
|
await base.InstallAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uninstall the plugin
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public override async Task UninstallAsync()
|
|
{
|
|
//settings
|
|
await _settingService.DeleteSettingAsync<TapPayPaymentSettings>();
|
|
|
|
//locales
|
|
await _localizationService.DeleteLocaleResourcesAsync("Plugins.Payments.TapPay");
|
|
|
|
await base.UninstallAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a payment method description that will be displayed on checkout pages in the public store
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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 PayPal site to complete the payment"
|
|
/// </remarks>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public async Task<string> GetPaymentMethodDescriptionAsync()
|
|
{
|
|
return await _localizationService.GetResourceAsync("Plugins.Payments.TapPay.PaymentMethodDescription");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether capture is supported
|
|
/// </summary>
|
|
public bool SupportCapture => false;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether partial refund is supported
|
|
/// </summary>
|
|
public bool SupportPartiallyRefund => false;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether refund is supported
|
|
/// </summary>
|
|
public bool SupportRefund => false;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether void is supported
|
|
/// </summary>
|
|
public bool SupportVoid => false;
|
|
|
|
/// <summary>
|
|
/// Gets a recurring payment type of payment method
|
|
/// </summary>
|
|
public RecurringPaymentType RecurringPaymentType => RecurringPaymentType.Manual;
|
|
|
|
/// <summary>
|
|
/// Gets a payment method type
|
|
/// </summary>
|
|
public PaymentMethodType PaymentMethodType => PaymentMethodType.Standard;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether we should display a payment information page for this plugin
|
|
/// </summary>
|
|
public bool SkipPaymentInfo => false;
|
|
|
|
#endregion
|
|
}
|