From 38142f2435f7a827d23fb27d4c1374266a956a3c Mon Sep 17 00:00:00 2001 From: Florian Mihalits Date: Mon, 30 Jun 2025 15:08:57 +0200 Subject: [PATCH] Add payment type handling for walks - Updated `Walk` class to include `PaymentType` property. - Introduced `WalkPaymentTypeDto` enum for payment types. - Modified DTOs (`PublicWalkRequestAcceptDto`, `WalkCreateDto`, `WalkDto`, `WalkWithNamesDto`) to include `PaymentType`. - Enhanced `ApiWalksController` with new endpoints for accepting and canceling walks, and confirming walks with ratings. - Updated `MappingProfile` for seamless conversion between `WalkPaymentType` and `WalkPaymentTypeDto`. --- gehGassi.Core/Services/WalkService.cs | 1 + gehGassi.Dto/Common/Enums.cs | 15 + .../Walks/PublicWalkRequestAcceptDto.cs | 5 + gehGassi.Dto/Walks/WalkCreateDto.cs | 5 + gehGassi.Dto/Walks/WalkDto.cs | 5 + gehGassi.Dto/Walks/WalkWithNamesDto.cs | 5 + .../Controllers/Api/ApiWalksController.cs | 843 ++++++++++++++++++ gehGassi.Web/Mapper/MappingProfile.cs | 3 + 8 files changed, 882 insertions(+) diff --git a/gehGassi.Core/Services/WalkService.cs b/gehGassi.Core/Services/WalkService.cs index de3a1d4..f9874ac 100644 --- a/gehGassi.Core/Services/WalkService.cs +++ b/gehGassi.Core/Services/WalkService.cs @@ -50,6 +50,7 @@ namespace gehGassi.Core.Services var walk = new Walk() { Id = Guid.NewGuid().ToString("N"), + PaymentType = WalkPaymentType.MangoPay, Created = DateTimeOffset.UtcNow }; return walk; diff --git a/gehGassi.Dto/Common/Enums.cs b/gehGassi.Dto/Common/Enums.cs index 15e9066..d44fd4d 100644 --- a/gehGassi.Dto/Common/Enums.cs +++ b/gehGassi.Dto/Common/Enums.cs @@ -584,6 +584,21 @@ namespace gehGassi.Dto.Common RefundendAndPayed = 60 } + /// + /// Zahlungsart für einen Walk + /// + public enum WalkPaymentTypeDto + { + /// + /// Mit MangoPay bezahlen (z.B. Kreditkarte) + /// + MangoPay = 1, + /// + /// Barzahlung + /// + Cash = 10 + } + /// /// Sortierreihenfolge /// diff --git a/gehGassi.Dto/Walks/PublicWalkRequestAcceptDto.cs b/gehGassi.Dto/Walks/PublicWalkRequestAcceptDto.cs index 3b91f1d..54bb222 100644 --- a/gehGassi.Dto/Walks/PublicWalkRequestAcceptDto.cs +++ b/gehGassi.Dto/Walks/PublicWalkRequestAcceptDto.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text; +using gehGassi.Dto.Common; namespace gehGassi.Dto.Walks { @@ -36,5 +37,9 @@ namespace gehGassi.Dto.Walks /// public DateTimeOffset UpdatedAt { get; set; } + /// + /// Zahlungsart für den Walk + /// + public WalkPaymentTypeDto PaymentType { get; set; } } } diff --git a/gehGassi.Dto/Walks/WalkCreateDto.cs b/gehGassi.Dto/Walks/WalkCreateDto.cs index 36bd004..9cc8ebb 100644 --- a/gehGassi.Dto/Walks/WalkCreateDto.cs +++ b/gehGassi.Dto/Walks/WalkCreateDto.cs @@ -101,5 +101,10 @@ namespace gehGassi.Dto.Walks /// [MaxLength(500)] public string Info { get; set; } + + /// + /// Zahlungsart für den Walk + /// + public WalkPaymentTypeDto PaymentType { get; set; } } } diff --git a/gehGassi.Dto/Walks/WalkDto.cs b/gehGassi.Dto/Walks/WalkDto.cs index 7bac30c..cb6cc16 100644 --- a/gehGassi.Dto/Walks/WalkDto.cs +++ b/gehGassi.Dto/Walks/WalkDto.cs @@ -179,6 +179,11 @@ namespace gehGassi.Dto.Walks [Required] public PaymentStatusDto PaymentStatus { get; set; } + /// + /// Zahlungsart für den Walk + /// + public WalkPaymentTypeDto PaymentType { get; set; } + /// /// Datum der Erstellung des Walks /// diff --git a/gehGassi.Dto/Walks/WalkWithNamesDto.cs b/gehGassi.Dto/Walks/WalkWithNamesDto.cs index 250637b..b55db7f 100644 --- a/gehGassi.Dto/Walks/WalkWithNamesDto.cs +++ b/gehGassi.Dto/Walks/WalkWithNamesDto.cs @@ -204,6 +204,11 @@ namespace gehGassi.Dto.Walks /// public PaymentStatusDto PaymentStatus { get; set; } + /// + /// Zahlungsart für den Walk + /// + public WalkPaymentTypeDto PaymentType { get; set; } + /// /// Id des Gutscheins der verwendet wurde, wenn einer verwendet wurde /// diff --git a/gehGassi.Web/Controllers/Api/ApiWalksController.cs b/gehGassi.Web/Controllers/Api/ApiWalksController.cs index 2d011a4..6977608 100644 --- a/gehGassi.Web/Controllers/Api/ApiWalksController.cs +++ b/gehGassi.Web/Controllers/Api/ApiWalksController.cs @@ -43,6 +43,7 @@ namespace gehGassi.Web.Controllers.Api [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ApiController] [ApiVersion(1)] + [ApiVersion(2)] [Route("api/walks")] [Route("api/v{v:apiVersion}/walks")] public class ApiWalksController : ApiBaseController @@ -954,6 +955,120 @@ namespace gehGassi.Web.Controllers.Api return NotFound(CommunicationErrors.Common_NotFound); } + /// + /// Akzeptieren einer Antwort zu einer öffentlichen Anfrage + /// + /// PublicWalkRequestDto + /// HTTP 200 OK, Felher sonst + [HttpPost] + [Route("AcceptPublicWalkRequest")] + [MapToApiVersion(2)] + public async Task AcceptPublicWalkRequestV2(PublicWalkRequestAcceptDto model) + { + var clientOffset = GetClientDateOffset(); + + if (User?.Identity != null && User.Identity.IsAuthenticated) + { + if (ModelState.IsValid) + { + var request = await _publicWalkRequestService.GetAsync(model.PublicWalkRequestId); + if (request != null && request.DogOwnerId == model.DogOwnerId) + { + var response = await _publicWalkResponseService.GetAsync(model.PublicWalkResponseId); + if (response != null && response.PublicWalkRequestId == request.Id) + { + response.Status = PublicWalkResponseStatus.Accepted; + response.UpdatedAt = model.UpdatedAt; + + await _publicWalkResponseService.CommitAsync(User.Identity.Name); + + //Nun Completed setzen für alle anderen die Warten... + await _publicWalkResponseService.SetCompletedForWaitingAsync(request.Id); + await _publicWalkResponseService.CommitAsync(User.Identity.Name); + + request.Status = PublicWalkRequestStatus.Placed; + request.PlacedDate = model.UpdatedAt; + request.PublicWalkResponseId = response.Id; + request.DogWalkerId = response.DogWalkerId; + request.UpdatedAt = model.UpdatedAt; + + await _publicWalkResponseService.CommitAsync(User.Identity.Name); + + //Nachricht an alle Angebote senden... + var responses = await _publicWalkResponseService.FindAsync(c => c.PublicWalkRequestId == request.Id); + foreach (var publicWalkResponse in responses.Where(c => c.DogWalkerId != request.DogWalkerId)) + { + _systemMessageService.Add(publicWalkResponse.DogWalkerId, AppUserType.DogWalker, request.Id, SystemMessageTables.PublicWalkRequest, SystemMessageType.PublicWalkRequestChanged, DateTimeOffset.UtcNow.AddDays(14)); + await _pushNotificationService.SendPublicWalkRequestChangedAsync(publicWalkResponse.DogWalkerId, request.Id); + } + await _systemMessageService.CommitAsync(User.Identity.Name); + foreach (var publicWalkResponse in responses.Where(c => c.DogWalkerId != request.DogWalkerId)) + { + await _appHubSender.SystemMessageAddedAsync(publicWalkResponse.DogWalkerId); + } + + //Nun einen Walk anlgegen! + var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + var geoLocation = geometryFactory.CreatePoint(new Coordinate(request.Location.X, request.Location.Y)); + + var walk = _walkService.Create(); + walk.Type = WalkType.OpenRequest; + walk.ServiceType = request.RequestType == PublicWalkRequestType.Sitting ? WalkServiceType.Sitting : WalkServiceType.Walking; + walk.DogWalkerId = request.DogWalkerId; + walk.DogOwnerId = request.DogOwnerId; + walk.PublicWalkRequestId = request.Id; + walk.DogsJson = request.DogsJson; + walk.DogCount = request.DogCount; + walk.Start = request.StartDate; + walk.End = request.EndDate; + walk.PickupAddress = request.PickupAddress.Clone(); + walk.PickupLocation = geoLocation; + walk.ReturnAddress = request.PickupAddress.Clone(); + walk.ReturnLocation = geoLocation; + walk.Price = response.Price; + walk.Info = request.Info; + walk.Status = WalkStatus.Accepted; + walk.Started = null; + walk.Completed = null; + walk.Confirmed = null; + walk.Complained = null; + walk.ComplainReason = string.Empty; + walk.DeclinedReason = string.Empty; + walk.CancelledReason = string.Empty; + walk.CancelledBy = CancellationSource.None; + walk.CompletedInfo = string.Empty; + walk.Defactation = false; + walk.PaymentStatus = PaymentStatus.Pending; + walk.PaymentType = (WalkPaymentType)model.PaymentType; + walk.UpdatedAt = DateTimeOffset.UtcNow; + walk.Created = DateTimeOffset.UtcNow; + + if (walk.Price == 0 || walk.PaymentType == WalkPaymentType.Cash) + walk.PaymentStatus = PaymentStatus.Authorized; + + _walkService.Add(walk); + await _walkService.CommitAsync(User.Identity.Name); + + if (walk.PaymentStatus is PaymentStatus.Authorized or PaymentStatus.Paid) + { + //Nachricht an den dogWalker senden + _systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkAdded, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogWalkerId); + await _pushNotificationService.SendNewWalkAsync(walk.DogWalkerId, walk.Id); + } + + var walkDto = Mapper.Map(walk); + return Ok(walkDto); + } + } + return NotFound(CommunicationErrors.Common_NotFound); + } + return BadRequest(CommunicationErrors.Common_Model_Invalid); + } + return NotFound(CommunicationErrors.Common_NotFound); + } + /// /// Stornieren einer öffentlichen Anfrage /// @@ -2224,6 +2339,128 @@ namespace gehGassi.Web.Controllers.Api return NotFound(CommunicationErrors.Common_NotFound); } + /// + /// Stornieren eines Walks + /// + /// PublicWalkRequestCancelDto + /// HTTP 200 OK, Fehler sonst + [HttpPost] + [Route("CancelWalk")] + [MapToApiVersion(2)] + public async Task CancelWalkV2(WalkCancelDto model) + { + var clientOffset = GetClientDateOffset(); + + if (User?.Identity != null && User.Identity.IsAuthenticated) + { + if (ModelState.IsValid) + { + var walk = await _walkService.GetAsync(model.WalkId); + if (walk != null) + { + if (!string.IsNullOrWhiteSpace(walk.VoucherId) && walk.VoucherAmmount > 0 && walk.PaymentType == WalkPaymentType.MangoPay) + { + var usage = await _voucherCampaignService.GetUsageAsync(walk.VoucherId, walk.DogOwnerId, walk.Id); + if (usage != null) + { + var voucher = await _voucherCampaignService.GetVoucherAsync(usage.VoucherId); + if (voucher != null && voucher.Id == usage.VoucherId) + { + var campaign = await _voucherCampaignService.GetAsync(voucher.VoucherCampaignId); + if (campaign != null) + { + var customer = await _customerService.GetAsync(campaign.CustomerId); + if (customer != null) + { + //Geld vom Transferkonto der Kamapgne auf das Guthabenkonto überweisen mit Verweis auf den Walk und Transaktion erfassen + var ammount = (int)(usage.VoucherAmmountUsed * 100); + var transferResult = await _mangoPayService.TransferMoneyAsync(customer.MangopayPaymentId, campaign.WalletFeeId, + customer.MangopayPaymentId, campaign.WalletCreditId, walk.Id, ammount, 0, $"VoucherId_{voucher.Id}_WalkId_{walk.Id}", "", ""); + + if (transferResult.Success) + { + //Usage beim voucher zurücksetzen (UsedCount, IsValid wenn Einzelgutschein) + voucher.UsedCount -= 1; + if (voucher.UsedCount < 0) + voucher.UsedCount = 0; + voucher.IsValid = true; + + //Usage in der Kampagne zurücksetzen (VouchersUsed, BudgetUsed) + campaign.VouchersUsed -= 1; + if (campaign.VouchersUsed < 0) + campaign.VouchersUsed = 0; + campaign.BudgetUsed -= usage.VoucherAmmountUsed; + if (campaign.BudgetUsed < 0) + campaign.BudgetUsed = 0; + + walk.VoucherId = null; + walk.VoucherAmmount = 0; + + //Reservieren durch die Anlage einer VoucherUsage + _voucherCampaignService.RemoveUsage(usage); + } + } + } + } + } + } + + walk.Status = WalkStatus.Cancelled; + walk.UpdatedAt = model.UpdatedAt; + walk.CancelledBy = (CancellationSource)model.CancellationSource; + walk.CancelledReason = model.CancelledReason; + + //Jetzt noch wegen Zahlungsstatus prüfen + if (walk.PaymentStatus == PaymentStatus.Paid || walk.PaymentStatus == PaymentStatus.Authorized) + { + //Zurücküberweisen der Summe vom Transaktionskonto auf das Guthabenkonto + if (walk.Price > 0 && walk.PaymentType == WalkPaymentType.MangoPay) + { + var ammount = (long)(walk.Price * 100); + var tag = $"Walk_Cancel_{walk.Id}"; + var refundResult = await _mangoPayService.TransferMoneyFromFeeToCreditAccountAsync(walk.DogOwnerId, ammount, tag); + if (refundResult.Success) + walk.PaymentStatus = PaymentStatus.Refunded; + else + walk.PaymentStatus = PaymentStatus.Voided; + } + else + walk.PaymentStatus = PaymentStatus.Voided; + } + + await _walkService.CommitAsync(User.Identity.Name); + + //Wallets aktualisieren + if (walk.PaymentType == WalkPaymentType.MangoPay) + { + await _mangoPayService.GetWalletBalanceAsync(walk.DogOwnerId, WalletType.Credits); + await _mangoPayService.GetWalletBalanceAsync(walk.DogOwnerId, WalletType.Fees); + } + + if (walk.CancelledBy == CancellationSource.DogWalker) + { + _systemMessageService.Add(walk.DogOwnerId, AppUserType.DogOwner, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkCancelled, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogOwnerId); + await _pushNotificationService.SendWalkCancelledAsync(walk.DogOwnerId, walk.Id, AppMode.DogOwner); + } + else + { + _systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkCancelled, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogWalkerId); + await _pushNotificationService.SendWalkCancelledAsync(walk.DogWalkerId, walk.Id, AppMode.DogWalker); + } + + return Ok(); + } + return NotFound(CommunicationErrors.Common_NotFound); + } + return BadRequest(CommunicationErrors.Common_Model_Invalid); + } + return NotFound(CommunicationErrors.Common_NotFound); + } + /// /// Starten eines Walks /// @@ -2604,6 +2841,190 @@ namespace gehGassi.Web.Controllers.Api return NotFound(CommunicationErrors.Common_NotFound); } + /// + /// Bestätigen eines Walks + /// + /// WalkConfirmDto + /// HTTP 200 OK, Felher sonst + [HttpPost] + [Route("ConfirmWalk")] + [MapToApiVersion(2)] + public async Task ConfirmWalkV2(WalkConfirmDto model) + { + var clientOffset = GetClientDateOffset(); + + if (User?.Identity != null && User.Identity.IsAuthenticated) + { + if (ModelState.IsValid) + { + var walk = await _walkService.GetAsync(model.WalkId); + if (walk != null && walk.DogOwnerId == model.AppUserId) + { + var appUser = await AppUserService.GetAsync(walk.DogOwnerId); + if (appUser is { Locked: true }) + { + return BadRequest(CommunicationErrors.Login_LockedOut); + } + var dogWalker = await AppUserService.GetAsync(walk.DogWalkerId); + if (dogWalker is { Locked: true }) + { + return BadRequest(CommunicationErrors.Login_LockedOut); + } + + if (walk.Status == WalkStatus.Completed && walk.Deleted == false) + { + walk.Status = WalkStatus.Confirmed; + walk.Confirmed = DateTimeOffset.UtcNow; + walk.UpdatedAt = DateTimeOffset.UtcNow; + walk.PaymentStatus = PaymentStatus.Paid; + await _walkService.CommitAsync(User.Identity.Name); + + //Wenn der Dogwalker in 30 Minuten mehr als 4 Walks bestätigt bekommt, dann Sperren wegen Betrugsverdacht + var numberOfConfirmedWalksTwentyMinutes = await _walkService.CountConfirmedByTimeRangeAsync(walk.DogWalkerId, DateTimeOffset.UtcNow.AddMinutes(-30), DateTimeOffset.UtcNow); + if (numberOfConfirmedWalksTwentyMinutes >= 4) + { + if (dogWalker != null) + { + dogWalker.Locked = true; + dogWalker.LockedReason = "Suspicious behavior - verächtiges Verhalten"; + dogWalker.LockedUntil = DateTimeOffset.UtcNow.AddYears(5); + await AppUserService.CommitAsync("System"); + + //Echten Benutzer sperren + var user = await _userService.GetByAppUserAsync(dogWalker.Id); + if (user != null) + { + user.LockoutEnd = DateTimeOffset.UtcNow.AddYears(5); + await _userService.CommitAsync("System"); + await _ticketStore.RemoveAsync(user.UserName); + await _refreshTokenService.RemoveAllAsync(user.Id, User.Identity.Name); + await _refreshTokenService.CommitAsync("System"); + } + } + } + + if (walk.PaymentType == WalkPaymentType.MangoPay) + { + //Zahlung veranlassen.... + if (walk.Price > 0) + { + decimal feeDecimal = 0; + long fee = 0; + var gehGassiFee = await _transactionFeeService.GetMinGehGassiFeeAsync(walk.Price); + if (gehGassiFee != null) + { + if (gehGassiFee.Percent > 0) + { + var percent = walk.Price * gehGassiFee.Percent / 100; + feeDecimal = percent; + } + + if (gehGassiFee.Fixed > 0) + { + feeDecimal += gehGassiFee.Fixed; + } + } + + if (feeDecimal > 0) + { + fee = (long)(feeDecimal * 100); + } + + //Wenn es einen gutschein gibt, dann Geld vom Transferkontos des Gutscheins auf das transferkonto des Hundebesitzers überweisen + if (!string.IsNullOrWhiteSpace(walk.VoucherId) && walk.VoucherAmmount > 0) + { + var voucherUsed = await _voucherCampaignService.GetUsageAsync(walk.VoucherId, walk.DogOwnerId, walk.Id); + if (voucherUsed != null) + { + var ammountFromVoucher = (long)(voucherUsed.VoucherAmmountUsed * 100); + voucherUsed.UsageStatus = VoucherUsageStatus.Used; + await _voucherCampaignService.CommitAsync(User.Identity.Name); + + var voucher = await _voucherCampaignService.GetVoucherAsync(voucherUsed.VoucherId); + if (voucher != null) + { + var campaign = await _voucherCampaignService.GetAsync(voucher.VoucherCampaignId); + if (campaign != null) + { + var customer = await _customerService.GetAsync(campaign.CustomerId); + if (customer != null) + { + var dogOwner = await AppUserService.GetAsync(walk.DogOwnerId); + if (dogOwner != null) + { + var wallet = await _walletService.GetAsync(walk.DogOwnerId, WalletType.Fees); + if (wallet != null) + { + var transferResult = await _mangoPayService.TransferMoneyAsync(customer.MangopayPaymentId, campaign.WalletFeeId, dogOwner.PaymentId, wallet.Id, walk.Id, ammountFromVoucher, 0, $"VoucherId_{voucher.Id}_WalkId_{walk.Id}", targetAppUserId: dogOwner.Id); + } + } + } + } + } + } + } + + var sourceWallet = await _walletService.GetAsync(walk.DogOwnerId, WalletType.Fees); + var targetWallet = await _walletService.GetAsync(walk.DogWalkerId, WalletType.Credits); + if (sourceWallet != null && targetWallet != null) + { + var sourceUser = await AppUserService.GetAsync(walk.DogOwnerId); + var targetUser = await AppUserService.GetAsync(walk.DogWalkerId); + + if (sourceUser != null && targetUser != null) + { + var ammount = (long)(walk.Price * 100); + var transcationSuccess = await _mangoPayService.TransferMoneyAsync(walk.DogOwnerId, walk.DogWalkerId, walk.Id, ammount, fee, $"WalkId_Payment_{walk.Id}"); + + //Wallets aktualisieren + await _mangoPayService.GetWalletBalanceAsync(sourceUser.Id, WalletType.Credits); + await _mangoPayService.GetWalletBalanceAsync(sourceUser.Id, WalletType.Fees); + + await _mangoPayService.GetWalletBalanceAsync(targetUser.Id, WalletType.Credits); + await _mangoPayService.GetWalletBalanceAsync(targetUser.Id, WalletType.Fees); + } + } + } + } + + //Coins für den Walker gutschreiben + var coins = walk.ServiceType switch + { + WalkServiceType.Walking => _coinsOptions.Value.WalkForDogwalker, + WalkServiceType.Sitting => _coinsOptions.Value.SittingForDowgWalker, + WalkServiceType.DayCare => _coinsOptions.Value.DayCareForDowgWalker, + _ => 0m + }; + await _coinsService.DepositAsync(walk.DogWalkerId, AppUserType.DogWalker, coins, walk.Id); + + //Coins für den Besitzer gutschreiben + var coinsOwner = walk.ServiceType switch + { + WalkServiceType.Walking => _coinsOptions.Value.WalkForOwner, + WalkServiceType.Sitting => _coinsOptions.Value.SittingForOwner, + WalkServiceType.DayCare => _coinsOptions.Value.DayCareForOwner, + _ => 0m + }; + await _coinsService.DepositAsync(walk.DogOwnerId, AppUserType.DogOwner, coinsOwner, walk.Id); + + _systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkConfirmed, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogWalkerId); + await _pushNotificationService.SendWalkConfirmedAsync(walk.DogWalkerId, walk.Id); + + var walkDto = Mapper.Map(walk); + return Ok(walkDto); + + } + } + return NotFound(CommunicationErrors.Common_NotFound); + } + return BadRequest(CommunicationErrors.Common_Model_Invalid); + } + + return NotFound(CommunicationErrors.Common_NotFound); + } + /// /// Bestätigen eines Walks mit Rating /// @@ -2798,6 +3219,205 @@ namespace gehGassi.Web.Controllers.Api return NotFound(CommunicationErrors.Common_NotFound); } + /// + /// Bestätigen eines Walks mit Rating + /// + /// WalkConfirmDto + /// HTTP 200 OK, Felher sonst + [HttpPost] + [Route("ConfirmWalkWithRating")] + [MapToApiVersion(2)] + public async Task ConfirmWalkWithRatingV2(WalkConfirmWithRatingDto model) + { + var clientOffset = GetClientDateOffset(); + + if (User?.Identity != null && User.Identity.IsAuthenticated) + { + if (ModelState.IsValid) + { + var walk = await _walkService.GetAsync(model.WalkId); + if (walk != null && walk.DogOwnerId == model.AppUserId) + { + var appUser = await AppUserService.GetAsync(walk.DogOwnerId); + if (appUser is { Locked: true }) + { + return BadRequest(CommunicationErrors.Login_LockedOut); + } + var dogWalker = await AppUserService.GetAsync(walk.DogWalkerId); + if (dogWalker is { Locked: true }) + { + return BadRequest(CommunicationErrors.Login_LockedOut); + } + + if (walk.Status == WalkStatus.Completed && walk.Deleted == false) + { + walk.Status = WalkStatus.Confirmed; + walk.Confirmed = DateTimeOffset.UtcNow; + walk.UpdatedAt = DateTimeOffset.UtcNow; + walk.PaymentStatus = PaymentStatus.Paid; + await _walkService.CommitAsync(User.Identity.Name); + + //Wenn der Dogwalker in 30 Minuten mehr als 4 Walks bestätigt bekommt, dann Sperren wegen Betrugsverdacht + var numberOfConfirmedWalksTwentyMinutes = await _walkService.CountConfirmedByTimeRangeAsync(walk.DogWalkerId, DateTimeOffset.UtcNow.AddMinutes(-30), DateTimeOffset.UtcNow); + if (numberOfConfirmedWalksTwentyMinutes >= 4) + { + if (dogWalker != null) + { + dogWalker.Locked = true; + dogWalker.LockedReason = "Suspicious behavior - verächtiges Verhalten"; + dogWalker.LockedUntil = DateTimeOffset.UtcNow.AddYears(5); + await AppUserService.CommitAsync("System"); + + //Echten Benutzer sperren + var user = await _userService.GetByAppUserAsync(dogWalker.Id); + if (user != null) + { + user.LockoutEnd = DateTimeOffset.UtcNow.AddYears(5); + await _userService.CommitAsync("System"); + await _ticketStore.RemoveAsync(user.UserName); + await _refreshTokenService.RemoveAllAsync(user.Id, User.Identity.Name); + await _refreshTokenService.CommitAsync("System"); + } + } + } + + if (walk.PaymentType == WalkPaymentType.MangoPay) + { + //Zahlung veranlassen.... + if (walk.Price > 0) + { + decimal feeDecimal = 0; + long fee = 0; + var gehGassiFee = await _transactionFeeService.GetMinGehGassiFeeAsync(walk.Price); + if (gehGassiFee != null) + { + if (gehGassiFee.Percent > 0) + { + var percent = walk.Price * gehGassiFee.Percent / 100; + feeDecimal = percent; + } + + if (gehGassiFee.Fixed > 0) + { + feeDecimal += gehGassiFee.Fixed; + } + } + + if (feeDecimal > 0) + { + fee = (long)(feeDecimal * 100); + } + + //Wenn es einen gutschein gibt, dann Geld vom Transferkontos des Gutscheins auf das transferkonto des Hundebesitzers überweisen + if (!string.IsNullOrWhiteSpace(walk.VoucherId) && walk.VoucherAmmount > 0) + { + var voucherUsed = await _voucherCampaignService.GetUsageAsync(walk.VoucherId, walk.DogOwnerId, walk.Id); + if (voucherUsed != null) + { + var ammountFromVoucher = (long)(voucherUsed.VoucherAmmountUsed * 100); + voucherUsed.UsageStatus = VoucherUsageStatus.Used; + await _voucherCampaignService.CommitAsync(User.Identity.Name); + + var voucher = await _voucherCampaignService.GetVoucherAsync(voucherUsed.VoucherId); + if (voucher != null) + { + var campaign = await _voucherCampaignService.GetAsync(voucher.VoucherCampaignId); + if (campaign != null) + { + var customer = await _customerService.GetAsync(campaign.CustomerId); + if (customer != null) + { + var dogOwner = await AppUserService.GetAsync(walk.DogOwnerId); + if (dogOwner != null) + { + var wallet = await _walletService.GetAsync(walk.DogOwnerId, WalletType.Fees); + if (wallet != null) + { + var transferResult = await _mangoPayService.TransferMoneyAsync(customer.MangopayPaymentId, campaign.WalletFeeId, dogOwner.PaymentId, wallet.Id, walk.Id, ammountFromVoucher, 0, $"VoucherId_{voucher.Id}_WalkId_{walk.Id}", targetAppUserId: dogOwner.Id); + } + } + } + } + } + } + } + + var sourceWallet = await _walletService.GetAsync(walk.DogOwnerId, WalletType.Fees); + var targetWallet = await _walletService.GetAsync(walk.DogWalkerId, WalletType.Credits); + if (sourceWallet != null && targetWallet != null) + { + var sourceUser = await AppUserService.GetAsync(walk.DogOwnerId); + var targetUser = await AppUserService.GetAsync(walk.DogWalkerId); + + if (sourceUser != null && targetUser != null) + { + var ammount = (long)(walk.Price * 100); + var transcationSuccess = await _mangoPayService.TransferMoneyAsync(walk.DogOwnerId, walk.DogWalkerId, walk.Id, ammount, fee, $"WalkId_Payment_{walk.Id}"); + + //Wallets aktualisieren + await _mangoPayService.GetWalletBalanceAsync(sourceUser.Id, WalletType.Credits); + await _mangoPayService.GetWalletBalanceAsync(sourceUser.Id, WalletType.Fees); + + await _mangoPayService.GetWalletBalanceAsync(targetUser.Id, WalletType.Credits); + await _mangoPayService.GetWalletBalanceAsync(targetUser.Id, WalletType.Fees); + } + } + } + } + + + //Coins für den Walker gutschreiben + var coins = walk.ServiceType switch + { + WalkServiceType.Walking => _coinsOptions.Value.WalkForDogwalker, + WalkServiceType.Sitting => _coinsOptions.Value.SittingForDowgWalker, + WalkServiceType.DayCare => _coinsOptions.Value.DayCareForDowgWalker, + _ => 0m + }; + await _coinsService.DepositAsync(walk.DogWalkerId, AppUserType.DogWalker, coins, walk.Id); + + //Coins für den Besitzer gutschreiben + var coinsOwner = walk.ServiceType switch + { + WalkServiceType.Walking => _coinsOptions.Value.WalkForOwner, + WalkServiceType.Sitting => _coinsOptions.Value.SittingForOwner, + WalkServiceType.DayCare => _coinsOptions.Value.DayCareForOwner, + _ => 0m + }; + await _coinsService.DepositAsync(walk.DogOwnerId, AppUserType.DogOwner, coinsOwner, walk.Id); + + //Rating erstellen + //var appUser = await AppUserService.GetAsync(walk.DogOwnerId); + if (appUser != null) + { + var rating = _ratingService.Create(walk.DogOwnerId, appUser.Type, walk.DogWalkerId, RatingTarget.Walker, model.RatingPoints, model.RatingInfo); + _ratingService.Add(rating); + + await _ratingService.CommitAsync(User.Identity.Name); + + //Statistik Hundebesitzer aktualisieren + await _ratingService.UpdateRatingStatisticsAsync(walk.DogWalkerId, RatingTarget.Walker); + await _ratingService.CommitAsync(User.Identity.Name); + } + + _systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkConfirmed, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogWalkerId); + await _pushNotificationService.SendWalkConfirmedAsync(walk.DogWalkerId, walk.Id); + + var walkDto = Mapper.Map(walk); + return Ok(walkDto); + + } + } + return NotFound(CommunicationErrors.Common_NotFound); + } + return BadRequest(CommunicationErrors.Common_Model_Invalid); + } + + return NotFound(CommunicationErrors.Common_NotFound); + } + /// /// Zahlungsstatus eines Walks setzen /// @@ -2947,6 +3567,102 @@ namespace gehGassi.Web.Controllers.Api return BadRequest(CommunicationErrors.Common_Model_Invalid); } + /// + /// Anlegen eines Walks wenn DIREKT! buchen möglich ist + /// + /// WalkCreateDto + /// HTTP 200 OK, Felher sonst + [HttpPost] + [Route("CreateWalkDirect")] + [MapToApiVersion(2)] + public async Task CreateWalkDirectV2(WalkCreateDto model) + { + var clientOffset = GetClientDateOffset(); + + var result = new CreateResponseDto + { + Status = CreateStatusDto.Error, + Value = null + }; + + if (User?.Identity != null && User.Identity.IsAuthenticated) + { + if (ModelState.IsValid) + { + var appUserId = User.AppUserId(); + var isLocked = await AppUserService.IsLockedAsync(appUserId); + var isBlocked = await AppUserService.IsBlockedAsync(model.DogWalkerId, appUserId); + + var walkPossible = await _walkService.IsWalkPossibleAsync(model.DogWalkerId, model.Start, model.End); + if (walkPossible && !isLocked && !isBlocked) + { + //Nun einen Walk anlgegen! + var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + var geoLocationPickup = geometryFactory.CreatePoint(new Coordinate(model.PickupAddressLng, model.PickupAddressLat)); + var geoLocationReturn = geometryFactory.CreatePoint(new Coordinate(model.ReturnAddressLng, model.ReturnAddressLat)); + + + var walk = _walkService.Create(); + walk.Type = (WalkType)model.Type; + walk.ServiceType = (WalkServiceType)model.ServiceType; + walk.DogWalkerId = model.DogWalkerId; + walk.DogOwnerId = model.DogOwnerId; + walk.PublicWalkRequestId = string.Empty; + walk.DogsJson = model.DogsJson; + walk.DogCount = model.DogCount; + walk.Start = model.Start; + walk.End = model.End; + walk.PickupAddress = Mapper.Map
(model.PickupAddress); + walk.PickupLocation = geoLocationPickup; + walk.ReturnAddress = Mapper.Map
(model.ReturnAddress); + walk.ReturnLocation = geoLocationReturn; + walk.Price = model.Price; + walk.Info = model.Info; + walk.Status = WalkStatus.Accepted; + walk.Started = null; + walk.Completed = null; + walk.Confirmed = null; + walk.Complained = null; + walk.ComplainReason = string.Empty; + walk.DeclinedReason = string.Empty; + walk.CancelledReason = string.Empty; + walk.CancelledBy = CancellationSource.None; + walk.CompletedInfo = string.Empty; + walk.Defactation = false; + walk.PaymentStatus = PaymentStatus.Pending; + walk.PaymentType = (WalkPaymentType)model.PaymentType; + walk.UpdatedAt = DateTimeOffset.UtcNow; + walk.Created = DateTimeOffset.UtcNow; + + if (walk.Price == 0 || walk.PaymentType == WalkPaymentType.Cash) + walk.PaymentStatus = PaymentStatus.Authorized; + + _walkService.Add(walk); + await _walkService.CommitAsync(User.Identity.Name); + + //Überlappende Walks die im Status "Request" sind, stornieren + if (walk.PaymentStatus is PaymentStatus.Authorized or PaymentStatus.Paid) + { + //Wenn der Preis eines Walks 0 ist, dann wird dieser direkt angelegt und gilt. + await _walkService.CancelWalkRequestsAsync(walk.DogWalkerId, walk.Start, walk.End); + await _walkService.CommitAsync(User.Identity.Name); + + _systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkAdded, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogWalkerId); + await _pushNotificationService.SendNewWalkAsync(walk.DogWalkerId, walk.Id); + } + + result.Status = CreateStatusDto.Success; + result.Value = Mapper.Map(walk); + return Ok(result); + } + return NotFound(CommunicationErrors.Walk_NotPossible); + } + } + return BadRequest(CommunicationErrors.Common_Model_Invalid); + } + /// /// Anlegen eines Walks als Anfrage /// @@ -3031,6 +3747,92 @@ namespace gehGassi.Web.Controllers.Api return BadRequest(CommunicationErrors.Common_Model_Invalid); } + /// + /// Anlegen eines Walks als Anfrage + /// + /// WalkCreateDto + /// HTTP 200 OK, Felher sonst + [HttpPost] + [Route("CreateWalkRequest")] + [MapToApiVersion(2)] + public async Task CreateWalkRequestV2(WalkCreateDto model) + { + var clientOffset = GetClientDateOffset(); + + var result = new CreateResponseDto + { + Status = CreateStatusDto.Error, + Value = null + }; + + if (User?.Identity != null && User.Identity.IsAuthenticated) + { + if (ModelState.IsValid) + { + var appUserId = User.AppUserId(); + var isLocked = await AppUserService.IsLockedAsync(appUserId); + var isBlocked = await AppUserService.IsBlockedAsync(model.DogWalkerId, appUserId); + + var walkPossible = await _walkService.IsWalkPossibleAsync(model.DogWalkerId, model.Start, model.End); + if (walkPossible && !isLocked && !isBlocked) + { + //Nun einen Walk anlgegen! + var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + var geoLocationPickup = geometryFactory.CreatePoint(new Coordinate(model.PickupAddressLng, model.PickupAddressLat)); + var geoLocationReturn = geometryFactory.CreatePoint(new Coordinate(model.ReturnAddressLng, model.ReturnAddressLat)); + + + var walk = _walkService.Create(); + walk.Type = (WalkType)model.Type; + walk.ServiceType = (WalkServiceType)model.ServiceType; + walk.DogWalkerId = model.DogWalkerId; + walk.DogOwnerId = model.DogOwnerId; + walk.PublicWalkRequestId = string.Empty; + walk.DogsJson = model.DogsJson; + walk.DogCount = model.DogCount; + walk.Start = model.Start; + walk.End = model.End; + walk.PickupAddress = Mapper.Map
(model.PickupAddress); + walk.PickupLocation = geoLocationPickup; + walk.ReturnAddress = Mapper.Map
(model.ReturnAddress); + walk.ReturnLocation = geoLocationReturn; + walk.Price = model.Price; + walk.Info = model.Info; + walk.Status = WalkStatus.Requested; + walk.Started = null; + walk.Completed = null; + walk.Confirmed = null; + walk.Complained = null; + walk.ComplainReason = string.Empty; + walk.DeclinedReason = string.Empty; + walk.CancelledReason = string.Empty; + walk.CancelledBy = CancellationSource.None; + walk.CompletedInfo = string.Empty; + walk.Defactation = false; + walk.PaymentStatus = PaymentStatus.None; + walk.PaymentType = (WalkPaymentType)model.PaymentType; + walk.UpdatedAt = DateTimeOffset.UtcNow; + walk.Created = DateTimeOffset.UtcNow; + + _walkService.Add(walk); + await _walkService.CommitAsync(User.Identity.Name); + + _systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkRequested, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogWalkerId); + await _pushNotificationService.SendWalkRequestedAsync(walk.DogWalkerId, walk.Id); + + result.Status = CreateStatusDto.Success; + result.Value = Mapper.Map(walk); + return Ok(result); + } + + return NotFound(CommunicationErrors.Walk_NotPossible); + } + } + return BadRequest(CommunicationErrors.Common_Model_Invalid); + } + /// /// Akzeptieren eines Walks /// @@ -3071,6 +3873,47 @@ namespace gehGassi.Web.Controllers.Api return NotFound(CommunicationErrors.Common_NotFound); } + /// + /// Akzeptieren eines Walks + /// + /// WalkAcceptDto + /// HTTP 200 OK, Felher sonst + [HttpPost] + [Route("AcceptWalk")] + [MapToApiVersion(2)] + public async Task AcceptWalkV2(WalkAcceptDto model) + { + var clientOffset = GetClientDateOffset(); + + if (User?.Identity != null && User.Identity.IsAuthenticated) + { + if (ModelState.IsValid) + { + var walk = await _walkService.GetAsync(model.WalkId); + if (walk != null) + { + walk.Status = WalkStatus.Accepted; + walk.PaymentStatus = PaymentStatus.Pending; + walk.UpdatedAt = model.UpdatedAt; + if (walk.Price == 0 || walk.PaymentType == WalkPaymentType.Cash) + walk.PaymentStatus = PaymentStatus.Authorized; + + await _walkService.CommitAsync(User.Identity.Name); + + _systemMessageService.Add(walk.DogOwnerId, AppUserType.DogOwner, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkAccepted, DateTimeOffset.UtcNow.AddDays(14)); + await _systemMessageService.CommitAsync(User.Identity.Name); + await _appHubSender.SystemMessageAddedAsync(walk.DogOwnerId); + await _pushNotificationService.SendWalkAcceptedAsync(walk.DogOwnerId, walk.Id); + + return Ok(); + } + return NotFound(CommunicationErrors.Common_NotFound); + } + return BadRequest(CommunicationErrors.Common_Model_Invalid); + } + return NotFound(CommunicationErrors.Common_NotFound); + } + /// /// Ablehnen eines Walks /// diff --git a/gehGassi.Web/Mapper/MappingProfile.cs b/gehGassi.Web/Mapper/MappingProfile.cs index 0f0b726..134ea92 100644 --- a/gehGassi.Web/Mapper/MappingProfile.cs +++ b/gehGassi.Web/Mapper/MappingProfile.cs @@ -641,6 +641,9 @@ namespace gehGassi.Web.Mapper CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); CreateMap();