Skip to content

Commit

Permalink
joinrpg#1604 Redirect after payment refactored:
Browse files Browse the repository at this point in the history
- anonymous access is allowed to handling endpoints;
- in case of missed payment information trying to update last proposed payment;
- payment handling refactored.
  • Loading branch information
Shiko1st committed Nov 27, 2021
1 parent e932e4a commit 10a4d6c
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 81 deletions.
63 changes: 31 additions & 32 deletions src/JoinRpg.Portal/Controllers/Money/PaymentsController.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 });

/// <summary>
Expand Down Expand Up @@ -89,65 +86,67 @@ public async Task<ActionResult> ClaimPayment(PaymentViewModel data)
}
}

private async Task<ActionResult> HandleClaimPaymentRedirect(int projectId, int claimId, string orderId, string description, string errorMessage)
private async Task<ActionResult> 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<ActionResult> ClaimPaymentSuccessGet(int projectId, int claimId, string orderId)
=> await HandleClaimPaymentRedirect(projectId, claimId, orderId, "",
"Ошибка обработки успешного платежа");


[HttpPost]
[Authorize]
[AllowAnonymous] // it is ok to be anonymous here as we are querying actual payment state from bank
public async Task<ActionResult> 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<ActionResult> 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<ActionResult> ClaimPaymentFail(int projectId, int claimId, string orderId,
[CanBeNull] string description)
string? description)
=> await HandleClaimPaymentRedirect(projectId, claimId, orderId, description,
"Ошибка обработки неудавшегося платежа");

Expand Down
125 changes: 78 additions & 47 deletions src/JoinRpg.Services.Impl/PaymentsService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -155,7 +155,7 @@ private async Task<Comment> AddPaymentCommentAsync(
claim,
CurrentUserId,
Now,
request.CommentText ?? "",
request.CommentText,
true,
null);
comment.Finance = new FinanceOperation
Expand Down Expand Up @@ -204,6 +204,22 @@ private async Task<FinanceOperation> LoadFinanceOperationAsync(int projectId, in
return fo;
}

private async Task<FinanceOperation?> LoadLastUnapprovedFinanceOperationAsync(int projectId, int claimId)
{
return await (from fo in UnitOfWork.GetDbSet<FinanceOperation>()
join pt in UnitOfWork.GetDbSet<PaymentType>()
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)
Expand Down Expand Up @@ -241,64 +257,79 @@ private void UpdateFinanceOperationStatus(FinanceOperation fo, PaymentData payme
}
}

/// <inheritdoc />
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?
}

/// <inheritdoc />
public async Task UpdateClaimPaymentAsync(int projectId, int claimId, int orderId)
=> await UpdateClaimPaymentAsync(await LoadFinanceOperationAsync(projectId, claimId, orderId));

// TODO: Probably need to send some notifications?
/// <inheritdoc />
public async Task UpdateLastClaimPaymentAsync(int projectId, int claimId)
{
var fo = await LoadLastUnapprovedFinanceOperationAsync(projectId, claimId);
if (fo is not null)
{
await UpdateClaimPaymentAsync(fo);
}
}

Expand Down
9 changes: 8 additions & 1 deletion src/JoinRpg.Services.Interfaces/IPaymentsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,18 @@ public interface IPaymentsService
Task<ClaimPaymentContext> InitiateClaimPaymentAsync(ClaimPaymentRequest request);

/// <summary>
/// Updates status of the payment (only if it is not already approved)
/// Updates status of a proposed payment
/// </summary>
/// <param name="projectId">Database Id of a project</param>
/// <param name="claimId">Database Id of a claim</param>
/// <param name="orderId">Finance operation Id</param>
Task UpdateClaimPaymentAsync(int projectId, int claimId, int orderId);

/// <summary>
/// Updates status of the last proposed payment
/// </summary>
/// <param name="projectId">Database Id of a project</param>
/// <param name="claimId">Database Id of a claim</param>
Task UpdateLastClaimPaymentAsync(int projectId, int claimId);
}
}
2 changes: 1 addition & 1 deletion src/JoinRpg.WebPortal.Models/ErrorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down

0 comments on commit 10a4d6c

Please sign in to comment.