Skip to content

Commit

Permalink
Merge pull request #1606 from Shiko1st/bug/payments/qrcode
Browse files Browse the repository at this point in the history
Recent payment issues
  • Loading branch information
leotsarev authored Nov 29, 2021
2 parents 78a3eed + 2dcff38 commit 7227ab9
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 92 deletions.
2 changes: 2 additions & 0 deletions src/JoinRpg.DataModel/Finances/FinanceOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
// Checking money value
switch (OperationType)
{
#pragma warning disable CS0612 // Type or member is obsolete
case FinanceOperationType.FeeChange:
#pragma warning restore CS0612 // Type or member is obsolete
if (MoneyAmount != 0)
{
yield return new ValidationResult($"Operation type {OperationType} must not have money amount", new[] { nameof(MoneyAmount) });
Expand Down
68 changes: 36 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,72 @@ 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 = "Вернуться к заявке",
});
}
}

//TODO: why we are losing cookies here? It's ok, because we don't do anything insecure here, but still...
// What we are doing here?
// 1. ask bank about status of orderId
// 2. Update status if required
// 3. Redirect to claim
[HttpGet]
[Authorize]
[AllowAnonymous]
[ActionName(nameof(ClaimPaymentSuccess))]
public async Task<ActionResult> ClaimPaymentSuccessGet(int projectId, int claimId, string orderId)
=> await HandleClaimPaymentRedirect(projectId, claimId, orderId, "",
"Ошибка обработки успешного платежа");


[HttpPost]
[Authorize]
[AllowAnonymous] //TODO see above
public async Task<ActionResult> ClaimPaymentSuccess(int projectId, int claimId, string orderId)
=> await HandleClaimPaymentRedirect(projectId, claimId, orderId, "",
"Ошибка обработки успешного платежа");


[HttpGet]
[Authorize]
[AllowAnonymous] //TODO see above
[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] //TODO see above
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
5 changes: 0 additions & 5 deletions src/PscbApi/ApiConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ public struct ApiConfiguration
/// </summary>
public string MerchantId;

/// <summary>
/// Marketplace Id (issued by PSCB) for the Fast Payments System
/// </summary>
public string MerchantIdFastPayments;

/// <summary>
/// Default url for successful payments
/// </summary>
Expand Down
8 changes: 2 additions & 6 deletions src/PscbApi/BankApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,7 @@ public async Task<PaymentRequestDescriptor> BuildPaymentRequestAsync(
Url = $"{ActualApiEndpoint}/pay",
Request = new PaymentRequest
{
marketPlace = message.PaymentMethod == PscbPaymentMethod.FastPaymentsSystem
? _configuration.MerchantIdFastPayments ?? _configuration.MerchantId
: _configuration.MerchantId,
marketPlace = _configuration.MerchantId,
message = Convert.ToBase64String(messageAsJsonUtf8),
signature = messageWithKey.Sha256Encode().ToHexString()
}
Expand Down Expand Up @@ -182,9 +180,7 @@ public async Task<PaymentInfo> GetPaymentInfoAsync(PscbPaymentMethod paymentMeth
var queryParams = new PaymentInfoQueryParams
{
OrderId = orderId,
MerchantId = paymentMethod == PscbPaymentMethod.FastPaymentsSystem
? _configuration.MerchantIdFastPayments ?? _configuration.MerchantId
: _configuration.MerchantId,
MerchantId = _configuration.MerchantId,
GetCardData = getCardData,
GetFiscalData = getFiscalData,
};
Expand Down

0 comments on commit 7227ab9

Please sign in to comment.