From 10a4d6c7ae398361449263c4701b38181b48a2ab Mon Sep 17 00:00:00 2001 From: Vladislav Kiselev Date: Sun, 28 Nov 2021 02:57:07 +0300 Subject: [PATCH] #1604 Redirect after payment refactored: - anonymous access is allowed to handling endpoints; - in case of missed payment information trying to update last proposed payment; - payment handling refactored. --- .../Controllers/Money/PaymentsController.cs | 63 +++++---- src/JoinRpg.Services.Impl/PaymentsService.cs | 125 +++++++++++------- .../IPaymentsService.cs | 9 +- .../ErrorViewModel.cs | 2 +- 4 files changed, 118 insertions(+), 81 deletions(-) diff --git a/src/JoinRpg.Portal/Controllers/Money/PaymentsController.cs b/src/JoinRpg.Portal/Controllers/Money/PaymentsController.cs index 6240daef8..ea08cc716 100644 --- a/src/JoinRpg.Portal/Controllers/Money/PaymentsController.cs +++ b/src/JoinRpg.Portal/Controllers/Money/PaymentsController.cs @@ -1,8 +1,5 @@ using System; -using System.Security.Policy; using System.Threading.Tasks; -using JetBrains.Annotations; -using JoinRpg.Data.Interfaces; using JoinRpg.Interfaces; using JoinRpg.Services.Interfaces; using JoinRpg.Web.Models; @@ -24,7 +21,7 @@ public PaymentsController( _payments = payments; } - private string GetClaimUrl(int projectId, int claimId) + private string? GetClaimUrl(int projectId, int claimId) => Url.Action("Edit", "Claim", new { projectId, claimId }); /// @@ -89,40 +86,42 @@ public async Task ClaimPayment(PaymentViewModel data) } } - private async Task HandleClaimPaymentRedirect(int projectId, int claimId, string orderId, string description, string errorMessage) + private async Task HandleClaimPaymentRedirect(int projectId, int claimId, string orderId, string? description, string errorMessage) { - if (int.TryParse(orderId, out var financeOperationId)) + var financeOperationId = 0; + try { - try + if (int.TryParse(orderId, out financeOperationId)) { await _payments.UpdateClaimPaymentAsync(projectId, claimId, financeOperationId); - return RedirectToAction("Edit", "Claim", new { projectId, claimId }); } - catch (Exception e) + else { - return Error( - new ErrorViewModel - { - Message = $"{errorMessage} {financeOperationId}", - Description = e.Message, - Data = e, - ReturnLink = GetClaimUrl(projectId, claimId), - ReturnText = "Вернуться к заявке", - }); + await _payments.UpdateLastClaimPaymentAsync(projectId, claimId); } - } - return Error( - new ErrorViewModel - { - Message = $"Неверный идентификатор платежа: {orderId}", - ReturnLink = GetClaimUrl(projectId, claimId), - ReturnText = "Вернуться к заявке", - }); + // TODO: In case of invalid payment redirect to special page + return RedirectToAction("Edit", "Claim", new { projectId, claimId }); + } + catch (Exception e) + { + string foText = financeOperationId > 0 + ? financeOperationId.ToString() + : "unknown finance operation"; + return Error( + new ErrorViewModel + { + Message = $"{errorMessage} {foText}", + Description = e.Message, + Data = e, + ReturnLink = GetClaimUrl(projectId, claimId), + ReturnText = "Вернуться к заявке", + }); + } } [HttpGet] - [Authorize] + [AllowAnonymous] // it is ok to be anonymous here as we are querying actual payment state from bank [ActionName(nameof(ClaimPaymentSuccess))] public async Task ClaimPaymentSuccessGet(int projectId, int claimId, string orderId) => await HandleClaimPaymentRedirect(projectId, claimId, orderId, "", @@ -130,24 +129,24 @@ public async Task ClaimPaymentSuccessGet(int projectId, int claimI [HttpPost] - [Authorize] + [AllowAnonymous] // it is ok to be anonymous here as we are querying actual payment state from bank public async Task ClaimPaymentSuccess(int projectId, int claimId, string orderId) => await HandleClaimPaymentRedirect(projectId, claimId, orderId, "", "Ошибка обработки успешного платежа"); [HttpGet] - [Authorize] + [AllowAnonymous] // it is ok to be anonymous here as we are querying actual payment state from bank [ActionName(nameof(ClaimPaymentFail))] public async Task ClaimPaymentFailGet(int projectId, int claimId, string orderId, - [CanBeNull] string description) + string? description) => await HandleClaimPaymentRedirect(projectId, claimId, orderId, description, "Ошибка обработки неудавшегося платежа"); [HttpPost] - [Authorize] + [AllowAnonymous] // it is ok to be anonymous here as we are querying actual payment state from bank public async Task ClaimPaymentFail(int projectId, int claimId, string orderId, - [CanBeNull] string description) + string? description) => await HandleClaimPaymentRedirect(projectId, claimId, orderId, description, "Ошибка обработки неудавшегося платежа"); diff --git a/src/JoinRpg.Services.Impl/PaymentsService.cs b/src/JoinRpg.Services.Impl/PaymentsService.cs index 0513966db..1d75b3f1f 100644 --- a/src/JoinRpg.Services.Impl/PaymentsService.cs +++ b/src/JoinRpg.Services.Impl/PaymentsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.Entity; using System.Linq; using System.Threading.Tasks; using JoinRpg.Data.Write.Interfaces; @@ -40,7 +41,6 @@ private ApiConfiguration GetApiConfiguration(int projectId, int claimId) ApiEndpoint = _bankSecrets.ApiEndpoint, ApiDebugEndpoint = _bankSecrets.ApiDebugEndpoint, MerchantId = _bankSecrets.MerchantId, - MerchantIdFastPayments = _bankSecrets.MerchantIdFastPayments, ApiKey = _bankSecrets.ApiKey, ApiDebugKey = _bankSecrets.ApiDebugKey, DefaultSuccessUrl = _uriService.Get(new PaymentSuccessUrl(projectId, claimId)), @@ -155,7 +155,7 @@ private async Task AddPaymentCommentAsync( claim, CurrentUserId, Now, - request.CommentText ?? "", + request.CommentText, true, null); comment.Finance = new FinanceOperation @@ -204,6 +204,22 @@ private async Task LoadFinanceOperationAsync(int projectId, in return fo; } + private async Task LoadLastUnapprovedFinanceOperationAsync(int projectId, int claimId) + { + return await (from fo in UnitOfWork.GetDbSet() + join pt in UnitOfWork.GetDbSet() + on fo.PaymentTypeId equals pt.PaymentTypeId + where fo.ProjectId == projectId + && fo.ClaimId == claimId + && fo.OperationType == FinanceOperationType.Online + && pt.TypeKind == PaymentTypeKind.Online + && fo.State == FinanceOperationState.Proposed + select fo) + .Include(fo => fo.PaymentType) + .OrderByDescending(fo => fo.Created) + .FirstOrDefaultAsync(); + } + private void UpdateFinanceOperationStatus(FinanceOperation fo, PaymentData paymentData) { switch (paymentData.Status) @@ -241,64 +257,79 @@ private void UpdateFinanceOperationStatus(FinanceOperation fo, PaymentData payme } } - /// - public async Task UpdateClaimPaymentAsync(int projectId, int claimId, int orderId) + private async Task UpdateClaimPaymentAsync(FinanceOperation fo) { - var fo = await LoadFinanceOperationAsync(projectId, claimId, orderId); - - if (fo.State == FinanceOperationState.Proposed) + if (fo.State != FinanceOperationState.Proposed) { - var api = GetApi(projectId, claimId); - string orderIdStr = orderId.ToString().PadLeft(10, '0'); + return; + } + + var api = GetApi(fo.ProjectId, fo.ClaimId); + string orderIdStr = fo.CommentId.ToString().PadLeft(10, '0'); + + // Asking bank + PaymentInfo paymentInfo = await api.GetPaymentInfoAsync( + PscbPaymentMethod.BankCards, + orderIdStr); - // Asking bank - PaymentInfo paymentInfo = await api.GetPaymentInfoAsync( - PscbPaymentMethod.BankCards, + if (paymentInfo.Status == PaymentInfoQueryStatus.Failure + && paymentInfo.ErrorCode == ApiErrorCode.UnknownPayment) + { + paymentInfo = await api.GetPaymentInfoAsync( + PscbPaymentMethod.FastPaymentsSystem, orderIdStr); + } - if (paymentInfo.Status == PaymentInfoQueryStatus.Failure - && paymentInfo.ErrorCode == ApiErrorCode.UnknownPayment) + // Updating status + if (paymentInfo.Status == PaymentInfoQueryStatus.Success) + { + if (paymentInfo.ErrorCode == ApiErrorCode.UnknownPayment) { - paymentInfo = await api.GetPaymentInfoAsync( - PscbPaymentMethod.FastPaymentsSystem, - orderIdStr); + fo.State = FinanceOperationState.Declined; + fo.Changed = Now; } - - // Updating status - if (paymentInfo.Status == PaymentInfoQueryStatus.Success) + else if (paymentInfo.ErrorCode == null) { - if (paymentInfo.ErrorCode == ApiErrorCode.UnknownPayment) - { - fo.State = FinanceOperationState.Declined; - fo.Changed = Now; - } - else if (paymentInfo.ErrorCode == null) + UpdateFinanceOperationStatus(fo, paymentInfo.Payment); + if (fo.State == FinanceOperationState.Approved) { - UpdateFinanceOperationStatus(fo, paymentInfo.Payment); - if (fo.State == FinanceOperationState.Approved) - { - Claim claim = await GetClaimAsync(projectId, claimId); - claim.UpdateClaimFeeIfRequired(Now); - } + Claim claim = await GetClaimAsync(fo.ProjectId, fo.ClaimId); + claim.UpdateClaimFeeIfRequired(Now); } } - else if (paymentInfo.ErrorCode == ApiErrorCode.UnknownPayment) - { - fo.State = FinanceOperationState.Invalid; - fo.Changed = Now; - } - else if (IsCurrentUserAdmin) - { - throw new PaymentException(fo.Project, $"Payment status check failed: {paymentInfo.ErrorDescription}"); - } + } + else if (paymentInfo.ErrorCode == ApiErrorCode.UnknownPayment) + { + fo.State = FinanceOperationState.Invalid; + fo.Changed = Now; + } + else if (IsCurrentUserAdmin) + { + throw new PaymentException( + fo.Project, + $"Payment status check failed: {paymentInfo.ErrorDescription}"); + } - // Saving if status was updated - if (fo.State != FinanceOperationState.Proposed) - { - await UnitOfWork.SaveChangesAsync(); - } + // Saving if status was updated + if (fo.State != FinanceOperationState.Proposed) + { + await UnitOfWork.SaveChangesAsync(); + } + + // TODO: Probably need to send some notifications? + } + + /// + public async Task UpdateClaimPaymentAsync(int projectId, int claimId, int orderId) + => await UpdateClaimPaymentAsync(await LoadFinanceOperationAsync(projectId, claimId, orderId)); - // TODO: Probably need to send some notifications? + /// + public async Task UpdateLastClaimPaymentAsync(int projectId, int claimId) + { + var fo = await LoadLastUnapprovedFinanceOperationAsync(projectId, claimId); + if (fo is not null) + { + await UpdateClaimPaymentAsync(fo); } } diff --git a/src/JoinRpg.Services.Interfaces/IPaymentsService.cs b/src/JoinRpg.Services.Interfaces/IPaymentsService.cs index aff3f9473..2b559e848 100644 --- a/src/JoinRpg.Services.Interfaces/IPaymentsService.cs +++ b/src/JoinRpg.Services.Interfaces/IPaymentsService.cs @@ -123,11 +123,18 @@ public interface IPaymentsService Task InitiateClaimPaymentAsync(ClaimPaymentRequest request); /// - /// Updates status of the payment (only if it is not already approved) + /// Updates status of a proposed payment /// /// Database Id of a project /// Database Id of a claim /// Finance operation Id Task UpdateClaimPaymentAsync(int projectId, int claimId, int orderId); + + /// + /// Updates status of the last proposed payment + /// + /// Database Id of a project + /// Database Id of a claim + Task UpdateLastClaimPaymentAsync(int projectId, int claimId); } } diff --git a/src/JoinRpg.WebPortal.Models/ErrorViewModel.cs b/src/JoinRpg.WebPortal.Models/ErrorViewModel.cs index d996c96e3..da798e9e6 100644 --- a/src/JoinRpg.WebPortal.Models/ErrorViewModel.cs +++ b/src/JoinRpg.WebPortal.Models/ErrorViewModel.cs @@ -9,7 +9,7 @@ public class ErrorViewModel public string Description { get; set; } - public string ReturnLink { get; set; } + public string? ReturnLink { get; set; } public string ReturnText { get; set; }