using AgriculturalPlatform.Api.Data;
using AgriculturalPlatform.Api.Dtos;
using AgriculturalPlatform.Api.Models;
using AgriculturalPlatform.Api.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace AgriculturalPlatform.Api.Controllers;
[ApiController]
[Route("api/payments")]
[Authorize]
public class PaymentsController(
AppDbContext db,
DataScopeService scope,
CurrentUserService currentUser,
NumberGenerator numberGen) : ControllerBase
{
/// 付款记录列表
[HttpGet]
public async Task>> List(
string? status, int? farmerId, int? orgId, DateTime? from, DateTime? to,
[FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] string? keyword = null)
{
var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get());
var query = db.PaymentRecords
.Include(p => p.Farmer).Include(p => p.PurchaseOrder)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.AsQueryable();
if (visible is not null)
query = query.Where(p => p.PurchaserOrgId != null && visible.Contains(p.PurchaserOrgId.Value));
if (!string.IsNullOrWhiteSpace(status) && Enum.TryParse(status, out var ps))
query = query.Where(p => p.Status == ps);
if (farmerId.HasValue) query = query.Where(p => p.FarmerId == farmerId);
if (orgId.HasValue) query = query.Where(p => p.PurchaserOrgId == orgId);
if (from.HasValue) query = query.Where(p => p.CreatedAt >= from);
if (to.HasValue) query = query.Where(p => p.CreatedAt < to.Value.Date.AddDays(1));
if (!string.IsNullOrWhiteSpace(keyword))
query = query.Where(p => p.PayNo.Contains(keyword)
|| p.TradeNo.Contains(keyword)
|| (p.Farmer != null && p.Farmer.Name.Contains(keyword)));
var total = await query.CountAsync();
var items = await query.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize).Take(pageSize)
.ToListAsync();
return Ok(new PagedResult(items.ToDtos(), total));
}
/// 支付状态统计(待支付/成功/失败/异常)
[HttpGet("stats")]
public async Task Stats()
{
var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get());
var query = db.PaymentRecords.AsQueryable();
if (visible is not null)
query = query.Where(p => p.PurchaserOrgId != null && visible.Contains(p.PurchaserOrgId.Value));
return Ok(new
{
pending = await query.CountAsync(p => p.Status == PayStatus.Pending),
success = await query.CountAsync(p => p.Status == PayStatus.Success),
failed = await query.CountAsync(p => p.Status == PayStatus.Failed),
abnormal = await query.CountAsync(p => p.Status == PayStatus.Abnormal)
});
}
/// 农户应收/已付汇总(结算用)
[HttpGet("farmer-summary")]
public async Task FarmerSummary(int farmerId, int? orgId)
{
var visible = await scope.GetVisibleOrgIdsAsync(currentUser.Get());
var orderQuery = db.PurchaseOrders
.Where(p => p.FarmerId == farmerId && p.Status == PurchaseStatus.Completed)
.AsQueryable();
if (visible is not null)
orderQuery = orderQuery.Where(p => visible.Contains(p.PurchaserOrgId));
if (orgId.HasValue)
orderQuery = orderQuery.Where(p => p.PurchaserOrgId == orgId);
var totalAmount = await orderQuery.SumAsync(p => (decimal?)p.Amount) ?? 0;
var payQuery = db.PaymentRecords
.Where(p => p.FarmerId == farmerId && p.Status == PayStatus.Success)
.AsQueryable();
if (visible is not null)
payQuery = payQuery.Where(p => p.PurchaserOrgId != null && visible.Contains(p.PurchaserOrgId.Value));
if (orgId.HasValue)
payQuery = payQuery.Where(p => p.PurchaserOrgId == orgId);
var paidAmount = await payQuery.SumAsync(p => (decimal?)p.Amount) ?? 0;
// 未结算的收购单(可批量付款)
var unpaidOrders = await db.PurchaseOrders
.Where(p => p.FarmerId == farmerId && p.Status == PurchaseStatus.Completed)
.Where(p => !db.PaymentRecords.Any(pay =>
pay.PurchaseOrderId == p.Id && pay.Status == PayStatus.Success))
.Include(p => p.Product)
.Select(p => new { p.Id, p.OrderNo, p.Product!.Name, p.NetWeight, p.Amount, p.UnitPrice })
.ToListAsync();
return Ok(new
{
farmerId,
totalAmount,
paidAmount,
unpaidAmount = Math.Round(totalAmount - paidAmount, 2),
unpaidOrders
});
}
/// 发起支付(按收购单或对农户批量结算)
[HttpPost]
public async Task> Create(PaymentCreateRequest req)
{
if (!await db.Farmers.AnyAsync(f => f.Id == req.FarmerId))
return BadRequest(new { message = "农户不存在" });
if (!Enum.TryParse(req.Method, out var method))
return BadRequest(new { message = "支付方式不正确" });
// 按收购单付款:校验订单并防止重复支付
if (req.PurchaseOrderId.HasValue)
{
var order = await db.PurchaseOrders.FindAsync(req.PurchaseOrderId);
if (order is null || order.FarmerId != req.FarmerId)
return BadRequest(new { message = "收购单不存在或与农户不匹配" });
if (await db.PaymentRecords.AnyAsync(p =>
p.PurchaseOrderId == req.PurchaseOrderId && p.Status == PayStatus.Success))
return BadRequest(new { message = "该收购单已支付成功" });
}
var cu = currentUser.Get()!;
int? purchaserOrgId = req.PurchaseOrderId.HasValue
? (await db.PurchaseOrders.FindAsync(req.PurchaseOrderId))?.PurchaserOrgId
: cu.OrgId;
var payNo = await numberGen.NextAsync("ZF");
var payment = new PaymentRecord
{
PayNo = payNo,
PurchaseOrderId = req.PurchaseOrderId,
PurchaserOrgId = purchaserOrgId,
FarmerId = req.FarmerId,
Amount = req.Amount,
Method = method,
Status = PayStatus.Pending,
OperatorId = cu.Id,
Notes = req.Notes
};
db.PaymentRecords.Add(payment);
await db.SaveChangesAsync();
return Ok((await db.PaymentRecords
.Include(p => p.Farmer).Include(p => p.PurchaseOrder)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstAsync(p => p.Id == payment.Id)).ToDto());
}
/// 确认支付结果(模拟收单回调,默认支付成功,可选失败/异常)
[HttpPut("{id:int}/confirm")]
public async Task> Confirm(int id, PaymentConfirmRequest req)
{
var payment = await db.PaymentRecords.FindAsync(id);
if (payment is null) return NotFound();
if (payment.Status != PayStatus.Pending)
return BadRequest(new { message = "当前状态不可确认" });
// Status 缺省或非法时按成功处理;成功/失败/异常均可写入
var result = Enum.TryParse(req.Status, out var st)
&& st is PayStatus.Success or PayStatus.Failed or PayStatus.Abnormal
? st : PayStatus.Success;
payment.Status = result;
if (result == PayStatus.Success)
{
payment.PaidAt = DateTime.Now;
payment.TradeNo = string.IsNullOrWhiteSpace(req.TradeNo) ? payment.TradeNo : req.TradeNo;
if (string.IsNullOrWhiteSpace(payment.TradeNo))
payment.TradeNo = $"TRADE{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(100, 999)}";
}
if (!string.IsNullOrWhiteSpace(req.Message))
payment.Notes = string.IsNullOrWhiteSpace(payment.Notes) ? req.Message : $"{payment.Notes}\n{req.Message}";
await db.SaveChangesAsync();
return Ok((await db.PaymentRecords
.Include(p => p.Farmer).Include(p => p.PurchaseOrder)
.Include(p => p.PurchaserOrg).Include(p => p.Operator)
.FirstAsync(p => p.Id == payment.Id)).ToDto());
}
/// 退款
[HttpPut("{id:int}/refund")]
public async Task Refund(int id)
{
var payment = await db.PaymentRecords.FindAsync(id);
if (payment is null) return NotFound();
if (payment.Status != PayStatus.Success)
return BadRequest(new { message = "仅已支付成功的记录可退款" });
payment.Status = PayStatus.Refunded;
await db.SaveChangesAsync();
return Ok(new { message = "退款成功" });
}
}