feat : add payment , zarinpal gateway

add payment entity , add zarinpal gateway and ready for test
release
Amir Hossein Khademi 2024-02-12 10:46:18 +03:30
parent 8d8ab5dc8f
commit 879b59f0bd
52 changed files with 5115 additions and 377 deletions

35
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md.
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/NetinaShop.Api/bin/Debug/net8.0/NetinaShop.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/NetinaShop.Api",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored 100644
View File

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/NetinaShop.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/NetinaShop.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/NetinaShop.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -51,8 +51,7 @@ public class OrderBagController : ICarterModule
=> TypedResults.Ok(await mediator.Send(new SubmitDiscountCommand(orderId, discountCode), cancellationToken));
public async Task<IResult> AddShippingToOrderBagAsync(Guid orderId, [FromBody] SubmitOrderDeliveryCommand request, IMediator mediator, CancellationToken cancellationToken)
=> TypedResults.Ok(
await mediator.Send(new SubmitOrderDeliveryCommand(request.Address, request.PostalCode,
=> TypedResults.Ok( await mediator.Send(new SubmitOrderDeliveryCommand(request.Address, request.PostalCode,
request.ReceiverPhoneNumber, request.ReceiverFullName, orderId, request.ShippingId), cancellationToken));
public async Task<IResult> SubmitOrderPaymentAsync(Guid orderId, [FromQuery] OrderPaymentMethod paymentMethod, IMediator mediator, CancellationToken cancellationToken)

View File

@ -1,4 +1,6 @@
namespace NetinaShop.Api.Controller;
using NetinaShop.Domain.Enums;
namespace NetinaShop.Api.Controller;
public class OrderController : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
@ -21,11 +23,14 @@ public class OrderController : ICarterModule
}
public async Task<IResult> GetAllAsync(IMediator mediator, [FromQuery] int page = 0, CancellationToken cancellationToken = default)
=> TypedResults.Ok(await mediator.Send(new GetOrdersQuery(page), cancellationToken));
public async Task<IResult> GetAllAsync(IMediator mediator, [FromQuery]long? selectedDate, [FromQuery] OrderStatus? orderStatus, [FromQuery] OrderQueryDateFilter? dateFilter, [FromQuery] int page = 0, CancellationToken cancellationToken = default)
=> TypedResults.Ok(await mediator.Send(new GetOrdersQuery(Page:page, SelectedDate: selectedDate, OrderStatus:orderStatus, DateFilter:dateFilter), cancellationToken));
public async Task<IResult> GetAsync(IMediator mediator,Guid id, CancellationToken cancellationToken = default)
=> TypedResults.Ok(await mediator.Send(new GetOrderLDtoQuery(id), cancellationToken));
public async Task<IResult> GetAsync(IMediator mediator, Guid id, CancellationToken cancellationToken = default)
{
var order = await mediator.Send(new GetOrderLDtoQuery(id), cancellationToken);
return TypedResults.Ok(order);
}
public async Task<IResult> DeleteAsync(IMediator mediator, Guid id, CancellationToken cancellationToken = default)
=> TypedResults.Ok(await mediator.Send(new DeleteOrderCommand(id), cancellationToken));

View File

@ -0,0 +1,57 @@
using TypedResults = Microsoft.AspNetCore.Http.TypedResults;
namespace NetinaShop.Api.Controller;
public class PaymentController : ICarterModule
{
public virtual void AddRoutes(IEndpointRouteBuilder app)
{
var group = app.NewVersionedApi("AccountingPayment")
.MapGroup($"api/accounting/pay");
group.MapGet("", GetAllAsync)
.WithDisplayName("GetPayments")
.RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser())
.HasApiVersion(1.0);
//group.MapGet("{id}", GetAsync)
// .WithDisplayName("GetShipping")
// .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser())
// .HasApiVersion(1.0);
group.MapGet("verify", VerifyPaymentAsync)
.HasApiVersion(1.0);
}
// GET:Get All Entity
public async Task<IResult> GetAllAsync([FromQuery] int page, IMediator mediator, CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(new GetPaymentsQuery(page), cancellationToken));
// GET:Get An Entity By Id
public async Task<IResult> GetAsync(Guid id, IMediator mediator, CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(new GetShippingQuery(id), cancellationToken));
// POST:Create Entity
public async Task<IResult> VerifyPaymentAsync([FromQuery] string Authority, [FromQuery] string Status, IPaymentService paymentService,ILogger<PaymentController> logger, CancellationToken cancellationToken)
{
try
{
if (Status == "OK")
{
var result = await paymentService.VerifyPaymentAsync(authority: Authority, cancellationToken);
return TypedResults.Redirect("");
}
else
{
return TypedResults.Redirect("");
}
}
catch (Exception e)
{
logger.LogError(e.Message);
return TypedResults.Redirect("");
}
}
}

View File

@ -13,7 +13,7 @@ public static class LoggerConfig
o.MinimumEventLevel = LogEventLevel.Error;
o.Dsn = "https://592b7fbb29464442a8e996247abe857f@watcher.igarson.app/7";
})
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Error)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Information)
.CreateLogger();
}
}

View File

@ -1,5 +1,7 @@
using System.Security.Cryptography;
using System;
using System.Security.Cryptography;
using NetinaShop.Repository.Migrations;
using Refit;
namespace NetinaShop.Api.WebFramework.MiddleWares;
@ -73,7 +75,8 @@ public class ExceptionHandlerMiddleware
{
message = exception.Message;
}
if(exception.AdditionalData==null)
if (exception.AdditionalData == null)
await WriteToResponseAsync();
else
await WriteToResponseWithObjectAsync(exception.AdditionalData);
@ -90,7 +93,17 @@ public class ExceptionHandlerMiddleware
SetUnAuthorizeResponse(exception);
await WriteToResponseAsync();
}
catch (ApiException apiException)
{
_logger.LogError(apiException, apiException.Message);
httpStatusCode = HttpStatusCode.InternalServerError;
apiStatusCode = ApiResultStatusCode.RefitError;
message = apiException.Message;
await WriteToResponseAsync();
}
catch (Exception exception)
{
_logger.LogError(exception, exception.Message);

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Core.Abstracts;
public interface IPaymentService : IScopedDependency
{
Task<string> GetPaymentLinkAsync(double amount, string factorNumber, Guid orderId, Guid userId, string phoneNumber, string fullName, CancellationToken cancellationToken = default);
Task<string> VerifyPaymentAsync(string authority, CancellationToken cancellationToken = default);
}

View File

@ -14,26 +14,45 @@ public class SubmitOrderPaymentCommandHandler : IRequestHandler<SubmitOrderPayme
}
public async Task<SubmitOrderPaymentResponseDto> Handle(SubmitOrderPaymentCommand request, CancellationToken cancellationToken)
{
var order = await _repositoryWrapper.SetRepository<Order>()
.TableNoTracking
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
await _mediator.Send(new CalculateOrderCommand(request.OrderId, true), cancellationToken);
if (order == null)
var orderSDto = await _repositoryWrapper.SetRepository<Order>()
.TableNoTracking
.Where(o => o.Id == request.OrderId)
.Select(OrderMapper.ProjectToSDto)
.FirstOrDefaultAsync(cancellationToken);
if (orderSDto == null)
throw new AppException("Order not found", ApiResultStatusCode.NotFound);
var order = orderSDto.AdaptToOrder();
var response = new SubmitOrderPaymentResponseDto();
if (request.PaymentMethod == OrderPaymentMethod.OnlinePayment)
{
response.NeedToPayOnline = true;
response.PaymentUrl = await _paymentService.GetPaymentLinkAsync(order.Id, order.TotalPrice, $"پرداخت سفارش {order.Id}");
if (request.HasPaid)
{
response.NeedToPayOnline = false;
order.SetSubmitOrder(request.PaymentMethod);
}
else
{
response.NeedToPayOnline = true;
response.PaymentUrl = await _paymentService.GetPaymentLinkAsync(orderSDto.TotalPrice, orderSDto.FactorCode, orderSDto.Id, orderSDto.UserId, orderSDto.UserPhoneNumber, orderSDto.UserFullName, cancellationToken);
}
}
else if (request.PaymentMethod == OrderPaymentMethod.Cash)
else if (request.PaymentMethod == OrderPaymentMethod.PayOnDoor)
{
response.NeedToPayOnline = false;
order.SetSubmitOrder(request.PaymentMethod);
_repositoryWrapper.SetRepository<Order>().Update(order);
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
await _mediator.Send(new CreatePaymentCommand(order.FactorCode, order.TotalPrice,
$"پرداخت نقدی سفارش {order.FactorCode}", string.Empty, string.Empty, string.Empty,
PaymentType.PayOnDoor, PaymentStatus.Paid, order.Id
, order.UserId), cancellationToken);
}
else if (request.PaymentMethod == OrderPaymentMethod.CardTransfer)
{
@ -41,6 +60,11 @@ public class SubmitOrderPaymentCommandHandler : IRequestHandler<SubmitOrderPayme
order.SetSubmitOrder(request.PaymentMethod);
_repositoryWrapper.SetRepository<Order>().Update(order);
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
await _mediator.Send(new CreatePaymentCommand(order.FactorCode, order.TotalPrice,
$"پرداخت نقدی سفارش {order.FactorCode}", string.Empty, string.Empty, string.Empty,
PaymentType.CardTransfer, PaymentStatus.Paid, order.Id
, order.UserId), cancellationToken);
}
return response;

View File

@ -10,5 +10,5 @@ public sealed record RemoveFromOrderBagCommand(List<OrderBagRequestDto> RequestD
public sealed record SubmitDiscountCommand(Guid OrderId,string DiscountCode) : IRequest<bool>;
public sealed record SubmitOrderDeliveryCommand(string Address, string PostalCode, string ReceiverPhoneNumber, string ReceiverFullName, Guid OrderId, Guid ShippingId) : IRequest<bool>;
public sealed record SubmitOrderPaymentCommand(Guid OrderId, OrderPaymentMethod PaymentMethod) : IRequest<SubmitOrderPaymentResponseDto>;
public sealed record SubmitOrderPaymentCommand(Guid OrderId, OrderPaymentMethod PaymentMethod , bool HasPaid = false) : IRequest<SubmitOrderPaymentResponseDto>;

View File

@ -0,0 +1,4 @@
namespace NetinaShop.Domain.CommandQueries.Commands;
public sealed record CreatePaymentCommand(string FactorNumber, double Amount, string Description, string TransactionCode, string CardPan, string Authority, PaymentType Type, PaymentStatus Status, Guid OrderId, Guid UserId) : IRequest<bool>;
public sealed record UpdatePaymentCommand(Guid Id,string FactorNumber, double Amount, string Description, string TransactionCode, string CardPan, string Authority, PaymentType Type, PaymentStatus Status, Guid OrderId, Guid UserId) : IRequest<bool>;

View File

@ -3,4 +3,4 @@
public sealed record GetOrderLDtoQuery(Guid Id) : IRequest<OrderLDto>;
public sealed record GetOrderQuery(Guid Id) : IRequest<Order>;
public sealed record GetOrdersQuery(int Page = 0) : IRequest<List<OrderSDto>>;
public sealed record GetOrdersQuery(OrderQueryDateFilter? DateFilter, OrderStatus? OrderStatus,long? SelectedDate, int Page = 0) : IRequest<List<OrderSDto>>;

View File

@ -0,0 +1,4 @@
namespace NetinaShop.Domain.CommandQueries.Queries;
public sealed record GetPaymentQuery(Guid Id = default , string? Authority = null) : IRequest<PaymentSDto>;
public sealed record GetPaymentsQuery(int Page = 0) : IRequest<List<PaymentSDto>>;

View File

@ -1,6 +1,7 @@
namespace NetinaShop.Domain.Dtos.LargDtos;
public class OrderLDto : BaseDto<OrderLDto,Order>
{
public string FactorCode { get; set; } = string.Empty;
public long TotalPrice { get; set; }
public long DeliveryPrice { get; set; }
public long TaxesPrice { get; set; }
@ -14,9 +15,25 @@ public class OrderLDto : BaseDto<OrderLDto,Order>
public DateTime OrderAt { get; set; }
public int PreparingMinute { get; set; }
public string DiscountCode { get; set; } = string.Empty;
public long TotalPriceWithoutDiscount => TotalPrice + DiscountPrice;
public List<OrderProductSDto> OrderProducts { get; internal set; } = new();
public string UserFullName { get; set; } = string.Empty;
public string UserPhoneNumber { get; set; } = string.Empty;
public List<OrderDeliverySDto> OrderDeliveries { get; internal set; } = new();
public List<OrderProductSDto> OrderProducts { get; set; } = new();
public List<OrderDeliverySDto> OrderDeliveries { get; set; } = new();
public List<PaymentSDto> Payments { get; set; } = new();
public OrderDeliverySDto OrderDelivery
{
get
{
if (OrderDeliveries.Count > 0)
return OrderDeliveries.FirstOrDefault() ?? new OrderDeliverySDto();
return new OrderDeliverySDto();
}
}
}

View File

@ -6,6 +6,7 @@ public class OrderDeliverySDto : BaseDto<OrderDeliverySDto, OrderDelivery>
public string PostalCode { get; set; } = string.Empty;
public string ReceiverPhoneNumber { get; set; } = string.Empty;
public string ReceiverFullName { get; set; } = string.Empty;
public string ShippingMethod { get; set; } = string.Empty;
public Guid OrderId { get; set; }
public Guid ShippingId { get; internal set; }
}

View File

@ -2,13 +2,17 @@
public class OrderProductSDto : BaseDto<OrderProductSDto, OrderProduct>
{
public int Count { get; internal set; }
public float ProductFee { get; internal set; }
public float ProductCost { get; internal set; }
public OrderStatus OrderProductStatus { get; internal set; }
public int Count { get; set; }
public double ProductFee { get; set; }
public double ProductFeeWithDiscount { get; set; }
public bool HasDiscount { get; set; }
public double ProductCost { get; set; }
public double PackingFee { get; set; }
public double PackingCost { get; set; }
public OrderStatus OrderProductStatus { get; set; }
public Guid ProductId { get; internal set; }
public Guid OrderId { get; internal set; }
public Guid ProductId { get; set; }
public string ProductName { get; set; } = string.Empty;
public Guid OrderId { get; set; }
}

View File

@ -1,17 +1,22 @@
namespace NetinaShop.Domain.Dtos.SmallDtos;
public class OrderSDto : BaseDto<OrderSDto, Order>
{
public long TotalPrice { get; set; }
public long DeliveryPrice { get; set; }
public long TaxesPrice { get; set; }
public long ServicePrice { get; set; }
public long PackingPrice { get; set; }
public long TotalProductsPrice { get; set; }
public long DiscountPrice { get; set; }
public double TotalPrice { get; set; }
public string FactorCode { get; set; } = string.Empty;
public double DeliveryPrice { get; set; }
public double TaxesPrice { get; set; }
public double ServicePrice { get; set; }
public double PackingPrice { get; set; }
public double TotalProductsPrice { get; set; }
public double DiscountPrice { get; set; }
public bool IsPayed { get; set; }
public OrderStatus OrderStatus { get; set; }
public DateTime PayedAt { get; set; }
public DateTime DoneAt { get; set; }
public DateTime OrderAt { get; set; }
public int PreparingMinute { get; set; }
public string DiscountCode { get; set; } = string.Empty;
public string UserFullName { get; set; } = string.Empty;
public string UserPhoneNumber { get; set; } = string.Empty;
public Guid UserId { get; internal set; }
}

View File

@ -0,0 +1,20 @@
using NetinaShop.Domain.Entities.Accounting;
namespace NetinaShop.Domain.Dtos.SmallDtos;
public class PaymentSDto : BaseDto<PaymentSDto,Payment>
{
public string FactorNumber { get; set; } = string.Empty;
public double Amount { get; set; }
public string Description { get; set; } = string.Empty;
public string TransactionCode { get; set; } = string.Empty;
public string CardPan { get; set; } = string.Empty;
public string Authority { get; set; } = string.Empty;
public PaymentType Type { get; set; }
public PaymentStatus Status { get; set; }
public Guid OrderId { get; set; }
public Guid UserId { get; set; }
public string UserFullName { get; set; } = string.Empty;
public string UserPhoneNumber { get; set; } = string.Empty;
}

View File

@ -0,0 +1,16 @@
namespace NetinaShop.Domain.Entities.Accounting;
public partial class Payment
{
public static Payment Create(string factorNumber, double amount, string description, string transactionCode, string cardPan, string authority, PaymentType type, PaymentStatus status, Guid orderId, Guid userId)
{
return new Payment(factorNumber, amount, description, transactionCode, cardPan, authority, type, status, orderId, userId);
}
public void ChangeStatus(PaymentStatus status, string? cardPan = null)
{
this.Status = status;
if (status != PaymentStatus.Paid && cardPan != null)
CardPan = cardPan;
}
}

View File

@ -0,0 +1,41 @@
namespace NetinaShop.Domain.Entities.Accounting;
[AdaptTwoWays("[name]LDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget | MapType.Projection)]
[AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)]
[AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)]
[GenerateMapper]
public partial class Payment : ApiEntity
{
public Payment()
{
}
public Payment(string factorNumber, double amount, string description, string transactionCode, string cardPan, string authority, PaymentType type, PaymentStatus status, Guid orderId, Guid userId)
{
FactorNumber = factorNumber;
Amount = amount;
Description = description;
TransactionCode = transactionCode;
CardPan = cardPan;
Authority = authority;
Type = type;
Status = status;
OrderId = orderId;
UserId = userId;
}
public string FactorNumber { get; internal set; } = string.Empty;
public double Amount { get; internal set; }
public string Description { get; internal set; } = string.Empty;
public string TransactionCode { get; internal set; } = string.Empty;
public string CardPan { get; internal set; } = string.Empty;
public string Authority { get; internal set; } = string.Empty;
public PaymentType Type { get; internal set; }
public PaymentStatus Status { get; internal set; }
public Guid OrderId { get; internal set; }
public Order? Order { get; internal set; }
public Guid UserId { get; internal set; }
public ApplicationUser? User { get; internal set; }
}

View File

@ -4,7 +4,8 @@ public partial class Order
{
public static Order Create(Guid userId)
{
return new Order(0, 0, 0, 0, 0, 0, 0, false, OrderStatus.OrderBag, DateTime.MinValue, DateTime.MinValue, 0, string.Empty, userId, OrderPaymentMethod.OnlinePayment);
var factorNumber = StringExtensions.GetId(10).ToUpper();
return new Order(factorNumber, 0, 0, 0, 0, 0, 0, 0, false, OrderStatus.OrderBag, DateTime.MinValue, DateTime.Now, 0, string.Empty, userId, OrderPaymentMethod.OnlinePayment);
}
@ -62,7 +63,7 @@ public partial class Order
OrderAt = DateTime.Now;
PaymentMethod = paymentMethod;
break;
case OrderPaymentMethod.Cash:
case OrderPaymentMethod.PayOnDoor:
OrderStatus = OrderStatus.Submitted;
IsPayed = false;
OrderAt = DateTime.Now;

View File

@ -1,4 +1,6 @@
namespace NetinaShop.Domain.Entities.Orders;
using NetinaShop.Domain.Entities.Accounting;
namespace NetinaShop.Domain.Entities.Orders;
[AdaptTwoWays("[name]LDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget | MapType.Projection)]
[AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)]
@ -12,6 +14,7 @@ public partial class Order : ApiEntity
}
public Order(
string factorCode,
double totalPrice,
double deliveryPrice,
double taxesPrice,
@ -19,8 +22,16 @@ public partial class Order : ApiEntity
double packingPrice,
double totalProductsPrice,
double discountPrice,
bool isPayed, OrderStatus orderStatus, DateTime doneAt, DateTime orderAt, int preparingMinute, string discountCode, Guid userId, OrderPaymentMethod paymentMethod)
bool isPayed,
OrderStatus orderStatus,
DateTime doneAt,
DateTime orderAt,
int preparingMinute,
string discountCode,
Guid userId,
OrderPaymentMethod paymentMethod)
{
FactorCode = factorCode;
TotalPrice = totalPrice;
DeliveryPrice = deliveryPrice;
TaxesPrice = taxesPrice;
@ -38,6 +49,7 @@ public partial class Order : ApiEntity
PaymentMethod = paymentMethod;
}
public string FactorCode { get; internal set; } = string.Empty;
public double TotalProductsPrice { get; internal set; }
public double PackingPrice { get; internal set; }
public double ServicePrice { get; internal set; }
@ -60,4 +72,6 @@ public partial class Order : ApiEntity
public List<OrderProduct> OrderProducts { get; internal set; } = new();
public List<OrderDelivery> OrderDeliveries { get; internal set; } = new();
public List<Payment> Payments { get; internal set; } = new();
}

View File

@ -1,5 +1,8 @@
namespace NetinaShop.Domain.Entities.Orders;
[AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)]
[AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)]
[GenerateMapper]
public partial class OrderProduct : ApiEntity
{
public OrderProduct()
@ -32,8 +35,8 @@ public partial class OrderProduct : ApiEntity
}
public int Count { get; internal set; }
public double ProductFee { get; internal set; }
public double ProductFeeWithDiscount { get; set; }
public bool HasDiscount { get; set; }
public double ProductFeeWithDiscount { get; internal set; }
public bool HasDiscount { get; internal set; }
public double ProductCost { get; internal set; }
public double PackingFee { get; internal set; }
public double PackingCost { get; internal set; }

View File

@ -3,7 +3,7 @@
public enum OrderPaymentMethod
{
[Display(Name = "پرداخت درب محل")]
Cash,
PayOnDoor,
[Display(Name = "پرداخت انلاین")]
OnlinePayment,
[Display(Name = "پرداخت کارت به کارت")]

View File

@ -0,0 +1,13 @@
namespace NetinaShop.Domain.Enums;
public enum OrderQueryDateFilter
{
[Display(Name = "امروز")]
Today = 0,
[Display(Name = "هفته اخیر")]
ThisWeek = 1,
[Display(Name = "یک ماه اخیر")]
ThisMonth = 2,
[Display(Name = "تاریخ خاص")]
CustomDate = 3,
}

View File

@ -0,0 +1,13 @@
namespace NetinaShop.Domain.Enums;
public enum PaymentStatus
{
[Display(Name = "در انتظار پرداخت درگاه")]
InPaymentGateway = 0,
[Display(Name = "در انتظار پرداخت")]
NotPaidYet = 1,
[Display(Name = "پرداخت شده")]
Paid = 200,
[Display(Name = "کنسل شده")]
Cancel = 500
}

View File

@ -0,0 +1,13 @@
namespace NetinaShop.Domain.Enums;
public enum PaymentType
{
[Display(Name = "نقدی")]
Cash,
[Display(Name = "کارت به کارت")]
CardTransfer,
[Display(Name = "آنلاین")]
Online,
[Display(Name = "در محل")]
PayOnDoor
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
using System;
using System.Linq.Expressions;
using Mapster.Models;
using NetinaShop.Domain.Dtos.SmallDtos;
using NetinaShop.Domain.Entities.Orders;
using NetinaShop.Domain.Entities.Products;
namespace NetinaShop.Domain.Mappers
{
public static partial class OrderProductMapper
{
public static OrderProduct AdaptToOrderProduct(this OrderProductSDto p1)
{
return p1 == null ? null : new OrderProduct()
{
Count = p1.Count,
ProductFee = p1.ProductFee,
ProductFeeWithDiscount = p1.ProductFeeWithDiscount,
HasDiscount = p1.HasDiscount,
ProductCost = p1.ProductCost,
PackingFee = p1.PackingFee,
PackingCost = p1.PackingCost,
OrderProductStatus = p1.OrderProductStatus,
ProductId = p1.ProductId,
Product = new Product()
{
Cost = p1.ProductCost,
Id = p1.ProductId
},
OrderId = p1.OrderId,
Order = new Order() {Id = p1.OrderId},
Id = p1.Id,
CreatedAt = p1.CreatedAt
};
}
public static OrderProduct AdaptTo(this OrderProductSDto p2, OrderProduct p3)
{
if (p2 == null)
{
return null;
}
OrderProduct result = p3 ?? new OrderProduct();
result.Count = p2.Count;
result.ProductFee = p2.ProductFee;
result.ProductFeeWithDiscount = p2.ProductFeeWithDiscount;
result.HasDiscount = p2.HasDiscount;
result.ProductCost = p2.ProductCost;
result.PackingFee = p2.PackingFee;
result.PackingCost = p2.PackingCost;
result.OrderProductStatus = p2.OrderProductStatus;
result.ProductId = p2.ProductId;
result.Product = funcMain1(new Never(), result.Product, p2);
result.OrderId = p2.OrderId;
result.Order = funcMain2(new Never(), result.Order, p2);
result.Id = p2.Id;
result.CreatedAt = p2.CreatedAt;
return result;
}
public static OrderProductSDto AdaptToSDto(this OrderProduct p8)
{
return p8 == null ? null : new OrderProductSDto()
{
Count = p8.Count,
ProductFee = p8.ProductFee,
ProductFeeWithDiscount = p8.ProductFeeWithDiscount,
HasDiscount = p8.HasDiscount,
ProductCost = p8.ProductCost,
PackingFee = p8.PackingFee,
PackingCost = p8.PackingCost,
OrderProductStatus = p8.OrderProductStatus,
ProductId = p8.ProductId,
ProductName = p8.Product != null ? p8.Product.PersianName : string.Empty,
OrderId = p8.OrderId,
Id = p8.Id,
CreatedAt = p8.CreatedAt
};
}
public static OrderProductSDto AdaptTo(this OrderProduct p9, OrderProductSDto p10)
{
if (p9 == null)
{
return null;
}
OrderProductSDto result = p10 ?? new OrderProductSDto();
result.Count = p9.Count;
result.ProductFee = p9.ProductFee;
result.ProductFeeWithDiscount = p9.ProductFeeWithDiscount;
result.HasDiscount = p9.HasDiscount;
result.ProductCost = p9.ProductCost;
result.PackingFee = p9.PackingFee;
result.PackingCost = p9.PackingCost;
result.OrderProductStatus = p9.OrderProductStatus;
result.ProductId = p9.ProductId;
result.ProductName = p9.Product != null ? p9.Product.PersianName : string.Empty;
result.OrderId = p9.OrderId;
result.Id = p9.Id;
result.CreatedAt = p9.CreatedAt;
return result;
}
public static Expression<Func<OrderProduct, OrderProductSDto>> ProjectToSDto => p11 => new OrderProductSDto()
{
Count = p11.Count,
ProductFee = p11.ProductFee,
ProductFeeWithDiscount = p11.ProductFeeWithDiscount,
HasDiscount = p11.HasDiscount,
ProductCost = p11.ProductCost,
PackingFee = p11.PackingFee,
PackingCost = p11.PackingCost,
OrderProductStatus = p11.OrderProductStatus,
ProductId = p11.ProductId,
ProductName = p11.Product != null ? p11.Product.PersianName : string.Empty,
OrderId = p11.OrderId,
Id = p11.Id,
CreatedAt = p11.CreatedAt
};
private static Product funcMain1(Never p4, Product p5, OrderProductSDto p2)
{
Product result = p5 ?? new Product();
result.Cost = p2.ProductCost;
result.Id = p2.ProductId;
return result;
}
private static Order funcMain2(Never p6, Order p7, OrderProductSDto p2)
{
Order result = p7 ?? new Order();
result.Id = p2.OrderId;
return result;
}
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Linq.Expressions;
using Mapster.Models;
using NetinaShop.Domain.Dtos.SmallDtos;
using NetinaShop.Domain.Entities.Accounting;
using NetinaShop.Domain.Entities.Orders;
using NetinaShop.Domain.Entities.Users;
namespace NetinaShop.Domain.Mappers
{
public static partial class PaymentMapper
{
public static Payment AdaptToPayment(this PaymentSDto p1)
{
return p1 == null ? null : new Payment()
{
FactorNumber = p1.FactorNumber,
Amount = p1.Amount,
Description = p1.Description,
TransactionCode = p1.TransactionCode,
CardPan = p1.CardPan,
Authority = p1.Authority,
Type = p1.Type,
Status = p1.Status,
OrderId = p1.OrderId,
Order = new Order() {Id = p1.OrderId},
UserId = p1.UserId,
User = new ApplicationUser()
{
Id = p1.UserId,
PhoneNumber = p1.UserPhoneNumber
},
Id = p1.Id,
CreatedAt = p1.CreatedAt
};
}
public static Payment AdaptTo(this PaymentSDto p2, Payment p3)
{
if (p2 == null)
{
return null;
}
Payment result = p3 ?? new Payment();
result.FactorNumber = p2.FactorNumber;
result.Amount = p2.Amount;
result.Description = p2.Description;
result.TransactionCode = p2.TransactionCode;
result.CardPan = p2.CardPan;
result.Authority = p2.Authority;
result.Type = p2.Type;
result.Status = p2.Status;
result.OrderId = p2.OrderId;
result.Order = funcMain1(new Never(), result.Order, p2);
result.UserId = p2.UserId;
result.User = funcMain2(new Never(), result.User, p2);
result.Id = p2.Id;
result.CreatedAt = p2.CreatedAt;
return result;
}
public static PaymentSDto AdaptToSDto(this Payment p8)
{
return p8 == null ? null : new PaymentSDto()
{
FactorNumber = p8.FactorNumber,
Amount = p8.Amount,
Description = p8.Description,
TransactionCode = p8.TransactionCode,
CardPan = p8.CardPan,
Authority = p8.Authority,
Type = p8.Type,
Status = p8.Status,
OrderId = p8.OrderId,
UserId = p8.UserId,
UserFullName = p8.User != null ? p8.User.FirstName + " " + p8.User.LastName : string.Empty,
UserPhoneNumber = p8.User != null ? p8.User.PhoneNumber : string.Empty,
Id = p8.Id,
CreatedAt = p8.CreatedAt
};
}
public static PaymentSDto AdaptTo(this Payment p9, PaymentSDto p10)
{
if (p9 == null)
{
return null;
}
PaymentSDto result = p10 ?? new PaymentSDto();
result.FactorNumber = p9.FactorNumber;
result.Amount = p9.Amount;
result.Description = p9.Description;
result.TransactionCode = p9.TransactionCode;
result.CardPan = p9.CardPan;
result.Authority = p9.Authority;
result.Type = p9.Type;
result.Status = p9.Status;
result.OrderId = p9.OrderId;
result.UserId = p9.UserId;
result.UserFullName = p9.User != null ? p9.User.FirstName + " " + p9.User.LastName : string.Empty;
result.UserPhoneNumber = p9.User != null ? p9.User.PhoneNumber : string.Empty;
result.Id = p9.Id;
result.CreatedAt = p9.CreatedAt;
return result;
}
public static Expression<Func<Payment, PaymentSDto>> ProjectToSDto => p11 => new PaymentSDto()
{
FactorNumber = p11.FactorNumber,
Amount = p11.Amount,
Description = p11.Description,
TransactionCode = p11.TransactionCode,
CardPan = p11.CardPan,
Authority = p11.Authority,
Type = p11.Type,
Status = p11.Status,
OrderId = p11.OrderId,
UserId = p11.UserId,
UserFullName = p11.User != null ? p11.User.FirstName + " " + p11.User.LastName : string.Empty,
UserPhoneNumber = p11.User != null ? p11.User.PhoneNumber : string.Empty,
Id = p11.Id,
CreatedAt = p11.CreatedAt
};
private static Order funcMain1(Never p4, Order p5, PaymentSDto p2)
{
Order result = p5 ?? new Order();
result.Id = p2.OrderId;
return result;
}
private static ApplicationUser funcMain2(Never p6, ApplicationUser p7, PaymentSDto p2)
{
ApplicationUser result = p7 ?? new ApplicationUser();
result.Id = p2.UserId;
result.PhoneNumber = p2.UserPhoneNumber;
return result;
}
}
}

View File

@ -1,4 +1,6 @@
namespace NetinaShop.Domain;
using NetinaShop.Domain.Entities.Accounting;
namespace NetinaShop.Domain;
public class MapsterRegister : IRegister
{
@ -12,6 +14,10 @@ public class MapsterRegister : IRegister
.Map("HeaderFileName", o => o.Files.Count > 0 && o.Files.Any(f => f.IsHeader) ? o.Files.FirstOrDefault(f => f.IsHeader)!.FileName : string.Empty)
.TwoWays();
config.NewConfig<OrderDelivery, OrderDeliverySDto>()
.Map("ShippingMethod", o => o.Shipping != null ? o.Shipping.Name : string.Empty)
.TwoWays();
config.NewConfig<ProductCategory, ProductCategorySDto>()
.Map("ParentName", o => o.Parent != null ? o.Parent.Name : string.Empty)
.TwoWays();
@ -23,6 +29,31 @@ public class MapsterRegister : IRegister
.IgnoreNullValues(false)
.TwoWays();
config.NewConfig<Order, OrderSDto>()
.Map("UserFullName", o => o.User != null ? o.User.FirstName + " " + o.User.LastName : string.Empty)
.Map("UserPhoneNumber", o => o.User != null ? o.User.PhoneNumber : string.Empty)
.IgnoreNullValues(false)
.TwoWays();
config.NewConfig<Order, OrderLDto>()
.Map("UserFullName", o => o.User != null ? o.User.FirstName + " " + o.User.LastName : string.Empty)
.Map("UserPhoneNumber", o => o.User != null ? o.User.PhoneNumber : string.Empty)
.IgnoreNullValues(false)
.TwoWays();
config.NewConfig<OrderProduct, OrderProductSDto>()
.Map("ProductName", o => o.Product != null ? o.Product.PersianName : string.Empty)
.IgnoreNullValues(false)
.TwoWays();
config.NewConfig<Payment, PaymentSDto>()
.Map("UserFullName", o => o.User != null ? o.User.FirstName + " " + o.User.LastName : string.Empty)
.Map("UserPhoneNumber", o => o.User != null ? o.User.PhoneNumber : string.Empty)
.IgnoreNullValues(false)
.TwoWays();
config.NewConfig<Product, ProductLDto>()
.Map("CategoryName", o => o.Category == null ? null : o.Category.Name)
.Map("BrandName", o => o.Brand == null ? null : o.Brand.Name)

View File

@ -2,5 +2,6 @@
public static class RestAddress
{
public static string BaseKaveNegar { get => "https://api.kavenegar.com/v1/"; }
public static string BaseKaveNegar => "https://api.kavenegar.com/v1/";
public static string BaseZarinpal => "https://api.zarinpal.com/pg";
}

View File

@ -0,0 +1,17 @@
namespace NetinaShop.Infrastructure.Models.RestApi.Zarinpal;
public class ZarinaplPaymentLinkRequest
{
public string merchant_id { get; set; } = string.Empty;
public int amount { get; set; }
public string callback_url { get; set; } = string.Empty;
public string description { get; set; } = string.Empty;
public ZarinaplPaymentLinkRequestMetadata metadata { get; set; } = new();
}
public class ZarinaplPaymentLinkRequestMetadata
{
public string mobile { get; set; } = string.Empty;
public string email { get; set; } = string.Empty;
}

View File

@ -0,0 +1,16 @@
namespace NetinaShop.Infrastructure.Models.RestApi.Zarinpal;
public class ZarinaplPaymentLinkResponse
{
public ZarinaplPaymentLinkResponseData data { get; set; }
public List<object> errors { get; set; }
}
public class ZarinaplPaymentLinkResponseData
{
public int code { get; set; }
public string message { get; set; } = string.Empty;
public string authority { get; set; } = string.Empty;
public string fee_type { get; set; } = string.Empty;
public int fee { get; set; }
}

View File

@ -0,0 +1,20 @@
namespace NetinaShop.Infrastructure.Models.RestApi.Zarinpal;
public class ZarinaplPaymentVerifyResponse
{
public ZarinaplPaymentVerifyResponseData data { get; set; }
public List<object> errors { get; set; }
}
public class ZarinaplPaymentVerifyResponseData
{
public int code { get; set; }
public string message { get; set; } = string.Empty;
public string card_hash { get; set; } = string.Empty;
public string card_pan { get; set; } = string.Empty;
public int ref_id { get; set; }
public string fee_type { get; set; } = string.Empty;
public int fee { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace NetinaShop.Infrastructure.Models.RestApi.Zarinpal;
public class ZarinaplVerifyPaymentRequest
{
public string merchant_id { get; set; } = string.Empty;
public int amount { get; set; }
public string authority { get; set; } = string.Empty;
}

View File

@ -18,11 +18,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>
<ItemGroup>
<Using Include="Amazon.S3" />
<Using Include="Amazon.S3.Model" />

View File

@ -5,9 +5,11 @@ namespace NetinaShop.Infrastructure.RestServices;
public interface IRestApiWrapper : IScopedDependency
{
IKaveNegarRestApi KaveNegarRestApi { get; }
IZarinpalRestApi ZarinpalRestApi { get; }
}
public class RestApiWrapper : IRestApiWrapper
{
public IKaveNegarRestApi KaveNegarRestApi => RestService.For<IKaveNegarRestApi>(RestAddress.BaseKaveNegar);
public IZarinpalRestApi ZarinpalRestApi => RestService.For<IZarinpalRestApi>(RestAddress.BaseZarinpal);
}

View File

@ -0,0 +1,12 @@
using NetinaShop.Infrastructure.Models.RestApi.Zarinpal;
namespace NetinaShop.Infrastructure.RestServices;
public interface IZarinpalRestApi
{
[Post("/v4/payment/request.json")]
Task<ZarinaplPaymentLinkResponse> GetPaymentLinkAsync([Body] ZarinaplPaymentLinkRequest request);
[Post("/v4/payment/verify.json")]
Task<ZarinaplPaymentVerifyResponse> VerifyPaymentAsync([Body] ZarinaplVerifyPaymentRequest request);
}

View File

@ -0,0 +1,74 @@
using Amazon.Runtime.Internal;
using MediatR;
using Microsoft.EntityFrameworkCore;
using NetinaShop.Domain.CommandQueries.Commands;
using NetinaShop.Domain.CommandQueries.Queries;
using NetinaShop.Domain.Entities.Accounting;
using NetinaShop.Domain.Entities.Orders;
using NetinaShop.Domain.Enums;
using NetinaShop.Infrastructure.Models.RestApi.Zarinpal;
using NetinaShop.Repository.Repositories.Base.Contracts;
namespace NetinaShop.Infrastructure.Services;
public class ZarinpalService : IPaymentService
{
private readonly IRestApiWrapper _restApiWrapper;
private readonly IMediator _mediator;
private readonly SiteSettings _siteSettings;
public ZarinpalService(IRestApiWrapper restApiWrapper,IOptionsSnapshot<SiteSettings> snapshot,IMediator mediator)
{
_restApiWrapper = restApiWrapper;
_mediator = mediator;
_siteSettings = snapshot.Value;
}
public async Task<string> GetPaymentLinkAsync(double amount,string factorNumber, Guid orderId, Guid userId, string phoneNumber , string fullName , CancellationToken cancellationToken = default)
{
var request = new ZarinaplPaymentLinkRequest
{
description = $"پرداخت {amount.ToString("N0")} ریال توسط {fullName} با شماره تماس {phoneNumber} برای سفارش با شماره {factorNumber}",
amount = (int)amount,
callback_url = Path.Combine("https://api.vesmook.com", "api", "accounting", "pay", "verify"),
merchant_id = "4292b845-b510-4d1d-8ee2-097499b198e5",
metadata = new ZarinaplPaymentLinkRequestMetadata { mobile = phoneNumber }
};
var response = await _restApiWrapper.ZarinpalRestApi.GetPaymentLinkAsync(request);
if (response.data.code != 100)
throw new AppException($"Exception in get link from zarinpal | {response.data.message}");
var createPaymentResult = await _mediator.Send(new CreatePaymentCommand(factorNumber, amount,
request.description, string.Empty, string.Empty,
response.data.authority, PaymentType.Online,PaymentStatus.InPaymentGateway, orderId, userId), cancellationToken);
string link = $"https://www.zarinpal.com/pg/StartPay/{response.data.authority}";
return link;
}
public async Task<string> VerifyPaymentAsync(string authority, CancellationToken cancellationToken = default)
{
var payment = await _mediator.Send(new GetPaymentQuery(Authority: authority), cancellationToken);
var request = new ZarinaplVerifyPaymentRequest
{
amount = (int)payment.Amount,
authority = payment.Authority,
merchant_id = "4292b845-b510-4d1d-8ee2-097499b198e5"
};
var response = await _restApiWrapper.ZarinpalRestApi.VerifyPaymentAsync(request);
if (response.data.code != 100)
throw new AppException($"Exception in get link from zarinpal | {response.data.message}");
payment.Status = PaymentStatus.Paid;
payment.CardPan = response.data.card_pan;
payment.TransactionCode = response.data.ref_id.ToString();
await _mediator.Send(
new UpdatePaymentCommand(payment.Id, payment.FactorNumber, payment.Amount, payment.Description,
payment.TransactionCode, payment.CardPan, payment.Authority, payment.Type, payment.Status,
payment.OrderId, payment.UserId), cancellationToken);
await _mediator.Send(new SubmitOrderPaymentCommand(payment.OrderId, OrderPaymentMethod.OnlinePayment, true),cancellationToken);
return payment.TransactionCode;
}
}

View File

@ -1,7 +0,0 @@
namespace NetinaShop.Repository.Abstracts;
public interface IPaymentService : IScopedDependency
{
Task<string> GetPaymentLinkAsync(Guid orderId, double amount, string description);
Task<bool> VerifiedPaymentAsync(string fishNumber, Guid orderId);
}

View File

@ -0,0 +1,26 @@
using NetinaShop.Domain.Entities.Accounting;
namespace NetinaShop.Repository.Handlers.Accounting;
public class CreatePaymentCommandHandler : IRequestHandler<CreatePaymentCommand,bool>
{
private readonly IRepositoryWrapper _repositoryWrapper;
public CreatePaymentCommandHandler(IRepositoryWrapper repositoryWrapper)
{
_repositoryWrapper = repositoryWrapper;
}
public async Task<bool> Handle(CreatePaymentCommand request, CancellationToken cancellationToken)
{
var payment = Payment.Create(request.FactorNumber, request.Amount, request.Description, request.TransactionCode,
request.CardPan, request.Authority, request.Type,request.Status, request.OrderId, request.UserId);
_repositoryWrapper.SetRepository<Payment>()
.Add(payment);
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
return true;
}
}

View File

@ -0,0 +1,41 @@
using NetinaShop.Domain.Entities.Accounting;
namespace NetinaShop.Repository.Handlers.Accounting;
public class GetPaymentQueryHandler : IRequestHandler<GetPaymentQuery,PaymentSDto>
{
private readonly IRepositoryWrapper _repositoryWrapper;
public GetPaymentQueryHandler(IRepositoryWrapper repositoryWrapper)
{
_repositoryWrapper = repositoryWrapper;
}
public async Task<PaymentSDto> Handle(GetPaymentQuery request, CancellationToken cancellationToken)
{
PaymentSDto? payment = null;
if (request.Authority != null)
{
payment = await _repositoryWrapper.SetRepository<Payment>()
.TableNoTracking
.Where(p => p.Authority == request.Authority)
.Select(PaymentMapper.ProjectToSDto)
.FirstOrDefaultAsync(cancellationToken);
}
else
{
if (request.Id == default)
throw new Exception("Id is null");
payment = await _repositoryWrapper.SetRepository<Payment>()
.TableNoTracking
.Where(p => p.Id == request.Id)
.Select(PaymentMapper.ProjectToSDto)
.FirstOrDefaultAsync(cancellationToken);
}
if (payment == null)
throw new AppException("Payment not found !", ApiResultStatusCode.NotFound);
return payment;
}
}

View File

@ -0,0 +1,22 @@
using NetinaShop.Domain.Entities.Accounting;
namespace NetinaShop.Repository.Handlers.Accounting;
public class GetPaymentsQueryHandler : IRequestHandler<GetPaymentsQuery,List<PaymentSDto>>
{
private readonly IRepositoryWrapper _repositoryWrapper;
public GetPaymentsQueryHandler(IRepositoryWrapper repositoryWrapper)
{
_repositoryWrapper = repositoryWrapper;
}
public async Task<List<PaymentSDto>> Handle(GetPaymentsQuery request, CancellationToken cancellationToken)
{
return await _repositoryWrapper.SetRepository<Payment>()
.TableNoTracking
.Skip(20 * request.Page)
.Take(20)
.Select(PaymentMapper.ProjectToSDto)
.ToListAsync(cancellationToken);
}
}

View File

@ -0,0 +1,31 @@
using NetinaShop.Domain.Entities.Accounting;
namespace NetinaShop.Repository.Handlers.Accounting;
public class UpdatePaymentCommandHandler : IRequestHandler<UpdatePaymentCommand,bool>
{
private readonly IRepositoryWrapper _repositoryWrapper;
public UpdatePaymentCommandHandler(IRepositoryWrapper repositoryWrapper)
{
_repositoryWrapper = repositoryWrapper;
}
public async Task<bool> Handle(UpdatePaymentCommand request, CancellationToken cancellationToken)
{
var ent = await _repositoryWrapper.SetRepository<Payment>()
.TableNoTracking
.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken);
if (ent == null)
throw new AppException("Payment not found", ApiResultStatusCode.NotFound);
var newEnt = Payment.Create(request.FactorNumber, request.Amount, request.Description, request.TransactionCode,
request.CardPan, request.Authority, request.Type, request.Status, request.OrderId, request.UserId);
newEnt.Id = ent.Id;
_repositoryWrapper.SetRepository<Payment>()
.Update(newEnt);
await _repositoryWrapper.SaveChangesAsync(cancellationToken);
return true;
}
}

View File

@ -0,0 +1,26 @@
using NetinaShop.Domain.Entities.Orders;
namespace NetinaShop.Repository.Handlers.Orders;
public class GetOrderLDtoQueryHandler: IRequestHandler<GetOrderLDtoQuery,OrderLDto>
{
private readonly IRepositoryWrapper _repositoryWrapper;
public GetOrderLDtoQueryHandler(IRepositoryWrapper repositoryWrapper)
{
_repositoryWrapper = repositoryWrapper;
}
public async Task<OrderLDto> Handle(GetOrderLDtoQuery request, CancellationToken cancellationToken)
{
if (request.Id == default)
throw new AppException("Order id is null");
var order = await _repositoryWrapper.SetRepository<Order>()
.TableNoTracking
.Where(o => o.Id == request.Id)
.Select(OrderMapper.ProjectToLDto)
.FirstOrDefaultAsync(cancellationToken);
if (order == null)
throw new AppException("Order not found", ApiResultStatusCode.NotFound);
return order;
}
}

View File

@ -10,10 +10,42 @@ public class GetOrdersQueryHandler : IRequestHandler<GetOrdersQuery,List<OrderSD
{
_repositoryWrapper = repositoryWrapper;
}
public Task<List<OrderSDto>> Handle(GetOrdersQuery request, CancellationToken cancellationToken)
public async Task<List<OrderSDto>> Handle(GetOrdersQuery request, CancellationToken cancellationToken)
{
return _repositoryWrapper.SetRepository<Order>()
.TableNoTracking
IQueryable<Order> orders = _repositoryWrapper.SetRepository<Order>()
.TableNoTracking;
if (request.DateFilter != null)
{
switch (request.DateFilter)
{
case OrderQueryDateFilter.CustomDate:
if (request.SelectedDate != null)
orders = orders.Where(o => o.CreatedAt.Date == DateTimeExtensions.UnixTimeStampToDateTime(request.SelectedDate.Value));
else
throw new AppException("For custom date you have to send SelectedDate",ApiResultStatusCode.BadRequest);
break;
case OrderQueryDateFilter.ThisMonth:
orders = orders.Where(o => o.CreatedAt.Date >= new DateTime(DateTime.Today.Year,DateTime.Today.Month,1));
break;
case OrderQueryDateFilter.ThisWeek:
orders = orders.Where(o => o.CreatedAt.Date >= DateTime.Today.AddDays(-7));
break;
case OrderQueryDateFilter.Today:
orders = orders.Where(o => o.CreatedAt.Date == DateTime.Today.Date);
break;
default:
break;
}
}
if (request.OrderStatus != null)
orders = orders.Where(o => o.OrderStatus == request.OrderStatus.Value);
return await orders
.OrderByDescending(o=>o.CreatedAt)
.Skip(request.Page * 15)
.Take(15)
.Select(OrderMapper.ProjectToSDto)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NetinaShop.Repository.Migrations
{
/// <inheritdoc />
public partial class EditOrderAddFactorCode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "FactorCode",
schema: "public",
table: "Orders",
type: "text",
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "FactorCode",
schema: "public",
table: "Orders");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NetinaShop.Repository.Migrations
{
/// <inheritdoc />
public partial class AddPayment : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Payments",
schema: "public",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
FactorNumber = table.Column<string>(type: "text", nullable: false),
Amount = table.Column<double>(type: "double precision", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
TransactionCode = table.Column<string>(type: "text", nullable: false),
CardPan = table.Column<string>(type: "text", nullable: false),
Authority = table.Column<string>(type: "text", nullable: false),
Type = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
RemovedAt = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
CreatedBy = table.Column<string>(type: "text", nullable: true),
IsRemoved = table.Column<bool>(type: "boolean", nullable: false),
RemovedBy = table.Column<string>(type: "text", nullable: true),
ModifiedAt = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
ModifiedBy = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Payments", x => x.Id);
table.ForeignKey(
name: "FK_Payments_Orders_OrderId",
column: x => x.OrderId,
principalSchema: "public",
principalTable: "Orders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Payments_Users_UserId",
column: x => x.UserId,
principalSchema: "public",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Payments_OrderId",
schema: "public",
table: "Payments",
column: "OrderId");
migrationBuilder.CreateIndex(
name: "IX_Payments_UserId",
schema: "public",
table: "Payments",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Payments",
schema: "public");
}
}
}

View File

@ -126,6 +126,77 @@ namespace NetinaShop.Repository.Migrations
b.ToTable("Tokens", "public");
});
modelBuilder.Entity("NetinaShop.Domain.Entities.Accounting.Payment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<double>("Amount")
.HasColumnType("double precision");
b.Property<string>("Authority")
.IsRequired()
.HasColumnType("text");
b.Property<string>("CardPan")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("CreatedBy")
.HasColumnType("text");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FactorNumber")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsRemoved")
.HasColumnType("boolean");
b.Property<DateTime>("ModifiedAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("ModifiedBy")
.HasColumnType("text");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<DateTime>("RemovedAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("RemovedBy")
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("TransactionCode")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Type")
.HasColumnType("integer");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("OrderId");
b.HasIndex("UserId");
b.ToTable("Payments", "public");
});
modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b =>
{
b.Property<Guid>("Id")
@ -393,6 +464,10 @@ namespace NetinaShop.Repository.Migrations
b.Property<DateTime>("DoneAt")
.HasColumnType("timestamp without time zone");
b.Property<string>("FactorCode")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsPayed")
.HasColumnType("boolean");
@ -1300,6 +1375,23 @@ namespace NetinaShop.Repository.Migrations
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("NetinaShop.Domain.Entities.Accounting.Payment", b =>
{
b.HasOne("NetinaShop.Domain.Entities.Orders.Order", "Order")
.WithMany("Payments")
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("NetinaShop.Domain.Entities.Users.ApplicationUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Order");
b.Navigation("User");
});
modelBuilder.Entity("NetinaShop.Domain.Entities.Blogs.Blog", b =>
{
b.HasOne("NetinaShop.Domain.Entities.Blogs.BlogCategory", "Category")
@ -1531,6 +1623,8 @@ namespace NetinaShop.Repository.Migrations
b.Navigation("OrderDeliveries");
b.Navigation("OrderProducts");
b.Navigation("Payments");
});
modelBuilder.Entity("NetinaShop.Domain.Entities.ProductCategories.ProductCategory", b =>