656 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using gehGassi.Dto;
using gehGassi.Dto.Common;
using gehGassi.Dto.Payment;
using gehGassi.Dto.Walks;
using gehGassiApp.Core.Data;
using gehGassiApp.Core.Interfaces;
using gehGassiApp.Core.Interfaces.Synchronization;
using gehGassiApp.Core.Mapper;
using gehGassiApp.Core.Resources;
using gehGassiApp.Domain.Common;
using gehGassiApp.Domain.Payment;
using gehGassiApp.Domain.Users;
using gehGassiApp.Domain.Walks;
namespace gehGassiApp.Core.Services
{
/// <summary>
/// Service der die Verwaltung von Zahlungen übernimmt
/// </summary>
public class PaymentService : IPaymentService
{
private readonly IUnitOfWork _unitOfWork;
private readonly ICommunicationService _communicationService;
private readonly IUserService _userService;
private readonly IWalkService _walkService;
private readonly ISyncInfoPullService _syncInfoPullService;
private readonly IRepository<AppUser> _appUserRepository;
private readonly IRepository<Payout> _payoutRepository;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="unitOfWork">Instanz eines IUnitOfWork</param>
/// <param name="communicationService">Instanz eines ICommunicationService</param>
/// <param name="userService">Instanz eines IUserService</param>
/// <param name="walkService">Instanz eines IWalkService</param>
/// <param name="syncInfoPullService">Instanz eines ISyncInfoPullService</param>
public PaymentService(IUnitOfWork unitOfWork, ICommunicationService communicationService, IUserService userService, IWalkService walkService, ISyncInfoPullService syncInfoPullService)
{
_unitOfWork = unitOfWork;
_communicationService = communicationService;
_userService = userService;
_walkService = walkService;
_syncInfoPullService = syncInfoPullService;
_appUserRepository = _unitOfWork.GetRepository<AppUser>();
_payoutRepository = _unitOfWork.GetRepository<Payout>();
}
/// <summary>
/// Gibt eine Liste von Wallets eines App-Users zurück
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<CommunicationResult<List<Wallet>>> GetWalletsAsync(string appUserId, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<List<Wallet>>() { Value = new List<Wallet>() };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.GetWalletsAsync(appUserId, accessToken, token);
if (queryResult.Success)
{
result.Success = true;
result.Value = queryResult.Value;
}
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Gibt den Saldo eines Wallets zurück
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="walletType">Typ des Wallets</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<CommunicationResult<long>> GetWalletBalanceAsync(string appUserId, WalletType walletType, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<long>() { Value = 0 };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.GetWalletBalanceAsync(appUserId, walletType, accessToken, token);
if (queryResult.Success)
{
result.Success = true;
result.Value = queryResult.Value;
}
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Gibt die Bankverbindung eines App-Users zurück
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<CommunicationResult<BankAccount>> GetBankAccountAsync(string appUserId, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<BankAccount>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.GetBankAccountsAsync(appUserId, accessToken, token);
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Anlegen oder Bearbeiten eines Bankkontos
/// </summary>
/// <param name="bankAccount">Bankkonto</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult mit einem AppUser der die entsprechende BankId gesetzt hat</returns>
public async Task<CommunicationResult<AppUser>> CreateOrUpdateBankAccountAsync(BankAccount bankAccount, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<AppUser>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var dto = bankAccount.ToDto();
var queryResult = await _communicationService.CreateOrUpdateBankAccountAsync(dto, accessToken, token);
if (queryResult.Success)
{
//jetzt auch lokal die Änderung speichern
var appUser = await _userService.SetBankAccountAsync(queryResult.Value.Id, queryResult.Value.BankId, queryResult.Value.UpdatedAt);
queryResult.Value = appUser;
}
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Authorisierung der Bezahlung eines Walks mit dem Guthaben eines Wallets
/// Das Geld wird vom Guthabenkonto auf das Transaktions-Konto gelegt
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="walkId">Id des Walks</param>
/// <param name="ammount">Zu zahlender Bertrag</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<CommunicationResult<Walk>> AuthorizeWalkWithCreditAsync(string appUserId, string walkId, decimal ammount, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<Walk>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.AuthorizeWalkWithCreditAsync(appUserId, walkId, ammount, accessToken, token);
if (queryResult.Success)
{
//jetzt auch lokal die Änderung speichern
var updatedWalk = await _walkService.SetPaymentStatusLocalAsync(queryResult.Value.Id, queryResult.Value.PaymentStatus, queryResult.Value.UpdatedAt);
queryResult.Value = updatedWalk;
}
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Authorisierung der Bezahlung eines Walks komplett mit einem Gutschein
/// Das Geld wird vom Guthabenkonto auf das Transaktions-Konto gelegt
/// </summary>
/// <param name="voucherId">Id des Gutscheins</param>
/// <param name="voucherCode">Code des Gutscheins</param>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="walkId">Id des Walks</param>
/// <param name="ammount">Zu zahlender Bertrag</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<CommunicationResult<Walk>> AuthorizeWalkWithVoucherAsync(string voucherId, string voucherCode, string appUserId, string walkId, decimal ammount, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<Walk>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.AuthorizeWalkWithVoucherAsync(voucherId, voucherCode, appUserId, walkId, ammount, accessToken, token);
if (queryResult.Success)
{
//jetzt auch lokal die Änderung speichern
var updatedWalk = await _walkService.SetPaymentStatusLocalAsync(queryResult.Value.Id, queryResult.Value.PaymentStatus, queryResult.Value.UpdatedAt);
queryResult.Value = updatedWalk;
}
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Einzahlen einer Summe auf das Transaktions-Konto eines App-Users mit Bezug auf einen Walk.
/// Es können gebühren sofort auf das Transaktions-Konto des Mandanten abgeführt werden
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="walkId">Id des Walks</param>
/// <param name="ammount">Zu zahlender Bertrag</param>
/// <param name="fees">Anfallende Gebühren</param>
/// <param name="fromCredit">Betrag der zusätzlich vom Guthabenkonto bezahlt werden muss</param>
/// <param name="payInType">Typ der Einzahlung</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult mit dem ReturnUrl, wenn erfolgreich</returns>
public async Task<CommunicationResult<string>> PayInAsync(string appUserId, string walkId, decimal ammount, decimal fees, decimal fromCredit, PayInType payInType, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<string>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.PayInAsync(appUserId, walkId, ammount, fees, fromCredit, payInType, accessToken, token);
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Holen der letzten KYC-Dokumente eines App-Users
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult mit dem aktuell gültigen KYC-Dokument oder null, wenn keines vorhanden</returns>
public async Task<CommunicationResult<IdentityDocument>> GetLatestKycDocumentAsync(string appUserId, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<IdentityDocument>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.GetLatestKycDocumentAsync(appUserId, accessToken, token);
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// KYC-Dokument für einen App-User erstellen mit einer Seite
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="source">Dokument-Quelle</param>
/// <param name="fileOneName">Dateiname</param>
/// <param name="fileOne">Datei als byte-Array</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult mit dem aktuell erstellten KYC-Dokument</returns>
public async Task<CommunicationResult<IdentityDocument>> CreateKycDocumentAsync(string appUserId, IdentityDocumentSource source, string fileOneName, byte[] fileOne, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<IdentityDocument>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.CreateKycDocumentAsync(appUserId, source, fileOneName, fileOne, accessToken, token);
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// KYC-Dokument für einen App-User erstellen mit zwei Seiten
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="source">Dokument-Quelle</param>
/// <param name="fileOneName">Dateiname Datei 1</param>
/// <param name="fileOne">Datei 1 als byte-Array</param>
/// <param name="fileTwoName">Dateiname Datei 2</param>
/// <param name="fileTwo">Datei 2 als byte-Array</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult mit dem aktuell erstellten KYC-Dokument</returns>
public async Task<CommunicationResult<IdentityDocument>> CreateKycDocumentAsync(string appUserId, IdentityDocumentSource source, string fileOneName, byte[] fileOne, string fileTwoName, byte[] fileTwo, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<IdentityDocument>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.CreateKycDocumentAsync(appUserId, source, fileOneName, fileOne, fileTwoName, fileTwo, accessToken, token);
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Gibt Walks vom Server zurück
/// </summary>
/// <param name="appUserId">Id App-Users</param>
/// <param name="sortOrders">Liste von Sortierangaben</param>
/// <param name="take">Wie viele Walks sollen abgerufen werden? -1 Wenn nicht anwenden.</param>
/// <param name="skip">Wie viele Walks sollen ausgelassen werden? -1 Wenn nicht anwenden.</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>Nächster Walk oder null, wenn keiner vorhanden</returns>
public async Task<ListCommunicationResult<List<Payout>>> GetPayoutsAsync(string appUserId, List<DynamicSortOrder> sortOrders, int take, int skip, string accessToken, CancellationToken token)
{
var result = new ListCommunicationResult<List<Payout>>() { Value = new List<Payout>() };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var query = new PayoutQueryDto()
{
Take = take,
Skip = skip,
LastUpdate = null,
AppUserId = appUserId,
};
query.DynamicSortOrder = sortOrders.ToDto();
var queryResult = await _communicationService.GetPayoutsAsync(query, accessToken, token);
if (queryResult.Success && queryResult.Value.Count >= 1)
{
return queryResult;
}
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Erstellen einer Auszahlung
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="ammount">Betrag der ausgezahlt werden soll</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<CommunicationResult<Payout>> CreatePayoutAsync(string appUserId, decimal ammount, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<Payout>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.CreatePayoutAsync(appUserId, ammount, accessToken, token);
//Auszahlung lokal anlegen wenn erfolgreich
if (queryResult.Success)
{
_payoutRepository.Add(queryResult.Value);
await _unitOfWork.CommitAsync();
}
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
/// <summary>
/// Gibt eine Auszahlung eines App-Users zurück
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <param name="payoutId">Id der Auszahlung</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>CommunicationResult</returns>
public async Task<CommunicationResult<Payout>> GetPayoutAsync(string appUserId, string payoutId, string accessToken, CancellationToken token)
{
var result = new CommunicationResult<Payout>() { Value = null };
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var queryResult = await _communicationService.GetPayoutAsync(appUserId, payoutId, accessToken, token);
return queryResult;
}
else
{
result.Success = false;
result.ErrorCode = CommunicationErrors.ServerNoConnection;
result.ErrorMessage = Errors.Server_NoConnection;
}
return result;
}
#region Sync Payout
/// <summary>
/// Holen der Daten vom lokalen Speicher die noch nicht synchronisiert wurden und senden an den Server
/// </summary>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>Task</returns>
public async Task<SyncResult<Payout>> PushAsync(string accessToken, CancellationToken token)
{
var result = new SyncResult<Payout>();
//Es gibt hier kein Push!
await Task.Delay(1);
return result;
}
/// <summary>
/// Holen der letzten Daten vom Server und Synchronisieren mit den lokalen Daten
/// </summary>
/// <param name="language">Sprache</param>
/// <param name="accessToken">Aktuelles Accesstoken</param>
/// <param name="token">CancellationToken</param>
/// <returns>Task</returns>
public async Task<SyncResult<Payout>> PullAsync(string language, string accessToken, CancellationToken token)
{
var result = new SyncResult<Payout>();
var isConnected = await _communicationService.IsConnected();
if (isConnected)
{
var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false);
DateTimeOffset? lastUpdate = null;
var lastSyncInfo = await _syncInfoPullService.GetAsync(nameof(Payout));
if (lastSyncInfo != null)
lastUpdate = lastSyncInfo.LastUpdate;
var ctsQuery = new CancellationTokenSource(Common.Constants.PushPullTimeout);
var payoutResult = await _communicationService.GetPayoutsForSyncAsync(user.Id, lastUpdate, accessToken, token);
if (payoutResult.Success)
{
result = await HandleChangesAsync(payoutResult.Value);
if (result.LastUpdate != null)
await _syncInfoPullService.AddOrUpddateAsync(nameof(Payout), result.LastUpdate.Value).ConfigureAwait(false);
}
result.LastUpdate ??= lastUpdate;
}
return result;
}
#endregion
#region Private
/// <summary>
/// Behandeln der Liste von Auszahlungen wenn welche vom Online-Store geholt werden.
/// </summary>
/// <param name="payouts">Liste der Auszahlungen</param>
/// <returns>Task</returns>
private async Task<SyncResult<Payout>> HandleChangesAsync(List<Payout> payouts)
{
var result = new SyncResult<Payout>();
//Je Rasse durchgehen ob was gemacht werden soll
foreach (var payout in payouts)
{
var localPayout = await _payoutRepository.FirstOrDefaultAsync(c => c.Id == payout.Id, false);
if (localPayout != null && localPayout.UpdatedAt < payout.UpdatedAt)
{
if (localPayout.UpdatedAt >= payout.UpdatedAt)
{
if (result.LastUpdate == null || result.LastUpdate < localPayout.UpdatedAt)
result.LastUpdate = localPayout.UpdatedAt;
continue;
}
var deleted = localPayout.Deleted == false && payout.Deleted;
localPayout.Version = payout.Version;
localPayout.UpdatedAt = payout.UpdatedAt;
localPayout.Deleted = payout.Deleted;
localPayout.MangoPayUserId = payout.MangoPayUserId;
localPayout.MangoPayId = payout.MangoPayId;
localPayout.WalletId = payout.WalletId;
localPayout.MangoPayWalletId = payout.MangoPayWalletId;
localPayout.MangoPayBankId = payout.MangoPayBankId;
localPayout.Iban = payout.Iban;
localPayout.Bic = payout.Bic;
localPayout.Status = payout.Status;
localPayout.Ammount = payout.Ammount;
localPayout.Currency = payout.Currency;
localPayout.ResultCode = payout.ResultCode;
localPayout.ResultMessage = payout.ResultMessage;
localPayout.ExecutionDate = payout.ExecutionDate;
localPayout.Refunded = payout.Refunded;
localPayout.RefundReasonType = payout.RefundReasonType;
localPayout.RefundReasonMessage = payout.RefundReasonMessage;
localPayout.Created = payout.Created;
if (result.LastUpdate == null || result.LastUpdate < localPayout.UpdatedAt)
result.LastUpdate = localPayout.UpdatedAt;
if (!deleted)
result.Updated.Add(localPayout);
else
result.Deleted.Add(localPayout);
_payoutRepository.Update(localPayout);
}
if (localPayout == null)
{
localPayout = new Payout()
{
Id = payout.Id,
Version = payout.Version,
UpdatedAt = payout.UpdatedAt,
Deleted = payout.Deleted,
AppUserId = payout.AppUserId,
MangoPayUserId = payout.MangoPayUserId,
MangoPayId = payout.MangoPayId,
WalletId = payout.WalletId,
MangoPayWalletId = payout.MangoPayWalletId,
MangoPayBankId = payout.MangoPayBankId,
Iban = payout.Iban,
Bic = payout.Bic,
Status = payout.Status,
Ammount = payout.Ammount,
Currency = payout.Currency,
ResultCode = payout.ResultCode,
ResultMessage = payout.ResultMessage,
ExecutionDate = payout.ExecutionDate,
Refunded = payout.Refunded,
RefundReasonType = payout.RefundReasonType,
RefundReasonMessage = payout.RefundReasonMessage,
Created = payout.Created
};
_payoutRepository.Add(localPayout);
if (result.LastUpdate == null || result.LastUpdate < localPayout.UpdatedAt)
result.LastUpdate = localPayout.UpdatedAt;
result.Added.Add(localPayout);
}
}
if (result.HasChanges)
{
try
{
await _unitOfWork.CommitAsync();
}
catch (Exception ex)
{
var err = ex.Message;
}
}
return result;
}
#endregion
}
}