/// <summary> /// 发起退款 /// </summary> /// <param name="sourceSn"></param> /// <param name="amount"></param> /// <param name="refundReason"></param> /// <returns></returns> public async Task Refund(string sourceSn, int amount, string refundReason) { if (amount <= 0) { throw new Exception($"金额错误"); } var order = await DbContext.Set <PrepayOrders>().Include(r => r.RefundOrders) .FirstOrDefaultAsync(r => r.SourceSn == sourceSn); if (order == null || order.Status != PrepayOrderStatus.Succeed) { throw new Exception("支付状态错误"); } if (order.PayAmount <= order.RefundOrders.Sum(r => r.RefundAmount)) { throw new Exception("订单已全部退款完成"); } if (amount > order.PayAmount) { throw new Exception("退款金额错误"); } var mch = MchOptions.Get(order.MchId); if (mch == null) { throw new Exception($"商户数据异常,无法退款,mchId:{order.MchId}"); } var payment = Payments.FirstOrDefault(r => r.Channel == order.PayChannel.Value); if (payment == null) { throw new Exception($"支付通道错误,order channel:{order.PayChannel}"); } string localRefundNo = Guid.NewGuid().ToString("n"); using var locker = Locker.Lock(Lockers.RefundOrderCreate.Format(order.Id), 3); using var tran = DbContext.Database.BeginTransaction(); try { RefundOrders refundOrder = new RefundOrders { PrepayOrderId = order.Id, CreateTime = DateTime.Now.GetLongDate(), LocalRefundNo = localRefundNo, RefundStatus = RefundStatus.RefundProcessing, RefundAmount = amount }; DbContext.Add(refundOrder); await DbContext.SaveChangesAsync(); // 发起退款请求 await payment.Refund(order.AppId, order.MchId, order.CurrentPayLocalTradeNo, localRefundNo, order.Amount, amount, refundReason); tran.Commit(); } catch (Exception ex) { tran.Rollback(); throw ex; } }
/// <summary> /// 准备支付,请求支付数据 /// </summary> /// <param name="userId"></param> /// <param name="input"></param> /// <returns></returns> public async Task <AppPrepayDto> Prepay(string userId, OrderPrepayInput input) { using var locker = Locker.Lock(Lockers.PrepayOrderGetPayData.Format(input.PrepayOrderSn), 3); var order = await DbContext.Set <PrepayOrders>() .FirstOrDefaultAsync(o => o.Sn == input.PrepayOrderSn && o.UserId == userId); if (order == null) { throw new Exception("订单不存在"); } if (order.Status != PrepayOrderStatus.Waiting) { throw new Exception("订单状态错误"); } var now = DateTime.Now.GetLongDate(); var mch = MchOptions.GetByName(order.MchName, input.Channel.Convert2Type()); if (mch == null) { throw new Exception("channel 参数错误"); } var dataProvider = ServiceProvider.GetServices <IPrepayOrderDataProvider>() .SingleOrDefault(p => p.SourceType == order.SourceType); if (dataProvider == null) { throw new Exception("无效来源或者来源没有数据提供"); } var canpay = await dataProvider.CanPay(userId.ParseTo <int>(), order.SourceSn); if (!canpay.canPay) { throw new Exception(canpay.errorReason ?? "订单异常"); } var cacheKey = prepayCacheKey.Format(order.Id, input.Channel, input.AppId, mch.MchId); // 缓存中查看有没有支付对应的支付数据,有就直接返回,没有就调用对应的支付通道并获取 var prepayData = await DistributedCache.GetObjectWithJsonAsync <PrepayData>(cacheKey); if (prepayData == null) { var oldLocalTradeNo = order.CurrentPayLocalTradeNo; var newLocalTradeNo = Guid.NewGuid().ToString("n"); var payParams = new PayParams() { OrderId = order.Id, OrderSn = order.Sn, LocalTradeNo = newLocalTradeNo, Amount = order.PayAmount, Title = order.Title, Attach = order.Attach, ClientIp = input.ClientIp, TraderId = input.TraderId, RedirectUrl = input.RedirectUrl, Channel = input.Channel, MchId = mch.MchId, AppId = input.AppId }; var payment = Payments.FirstOrDefault(r => r.Channel == input.Channel); if (payment == null) { throw new Exception("无效的支付方式"); } if (!oldLocalTradeNo.IsNullOrWhiteSpace()) { try { // 每次请求新的支付数据时,都将旧的预付单关闭掉,避免重复支付 await payment.CloseOrder(input.AppId, mch.MchId, oldLocalTradeNo); } catch { // ignored } } order.CurrentPayLocalTradeNo = newLocalTradeNo; prepayData = await payment.GetPrepayData(payParams); await DistributedCache.SetObjectWithJsonAsync(cacheKey, prepayData, DateTimeOffset.Now.AddHours(1)); } try { var result = new AppPrepayDto { PayData = prepayData.PayData }; order.PayChannel = input.Channel; order.PrepayOriginData = prepayData.PrepayOriginData; order.MchId = mch.MchId; order.LastPrepayTime = now; order.AppId = input.AppId; await DbContext.SaveChangesAsync(); return(result); } catch (Exception e) { throw new Exception("支付数据生成失败", e); } }