You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

688 lines
26 KiB

using GDZZ.Application.Entity;
using GDZZ.Core.Entity;
using GDZZ.Core;
2 years ago
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Furion.EventBus;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Http;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
2 years ago
using GDZZ.Application.Service.WXPay.Dto;
2 years ago
using TenPayOldV3 = Senparc.Weixin.TenPay.V3.TenPayV3;
using Senparc.Weixin;
using Senparc.Weixin.TenPay.V3;
using Senparc.Weixin.TenPay;
using Senparc.CO2NET.Utilities;
using Senparc.Weixin.Sample.CommonService.TemplateMessage;
using System.IO;
using System.Text;
using Senparc.Weixin.Exceptions;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using Mapster;
2 years ago
using Furion.FriendlyException;
using Senparc.CO2NET.Cache.Redis;
using GDZZ.Core.Service;
using System.Linq.Dynamic.Core.Tokenizer;
using System.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Furion.JsonSerialization;
using Yitter.IdGenerator;
2 years ago
namespace GDZZ.Application.Service.WXPay
{
[ApiDescriptionSettings("Application", Name = "WXPay", Order = 1)]
public class WXPayService : IWXPayService, IDynamicApiController, ITransient
2 years ago
{
2 years ago
private readonly IHostingEnvironment hostingEnvironment;
private readonly SqlSugarRepository<BaseUser> Baseuser; // wx用户仓储
private readonly SqlSugarRepository<SysUser> _sysUserRep; // 用户表仓储
private readonly SqlSugarRepository<SysTenant> _sysTenantRep; //租户仓储
private readonly SqlSugarRepository<SeIF> self; //职业仓储
2 years ago
private readonly SqlSugarRepository<Consume> ComsumeRep; //消费记录仓储
private readonly SqlSugarRepository<Balance> balance; //余额仓储
private readonly SqlSugarRepository<MiniRecharge> rechargeRep; //充值仓储
private readonly SqlSugarRepository<MiniPayTake> payTakeRep; //支付仓储
private readonly SqlSugarRepository<ReFund> refundRep; //退款仓储
private readonly SqlSugarRepository<Taking> takingRep; //提现仓储
private readonly WechatOAuth _wechatOAuth; //微信权限服务
private readonly IHttpContextAccessor _httpContextAccessor; //http服务
/// <summary>
/// 获取配置文件
/// </summary>
2 years ago
private readonly SenparcWeixinSetting _oauthConfig;
public WXPayService(
IOptions<OAuthOptions> options,
SqlSugarRepository<BaseUser> Baseuser,
SqlSugarRepository<SysTenant> sysTenantRep,
SqlSugarRepository<SysUser> sysUserRep,
SqlSugarRepository<Balance> balance,
2 years ago
SqlSugarRepository<Consume> ComsumeRep,
SqlSugarRepository<SeIF> Self,
SqlSugarRepository<MiniRecharge> rechargeRep,
SqlSugarRepository<MiniPayTake> payTakeRep,
SqlSugarRepository<ReFund> refundRep,
SqlSugarRepository<Taking> takingRep,
WechatOAuth wechatOAuth,
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor _httpContextAccessor,
IEventPublisher eventPublisher)
{
2 years ago
this.self = Self;
this.refundRep= refundRep;
this.balance = balance;
this.Baseuser = Baseuser;
2 years ago
this.ComsumeRep = ComsumeRep;
this._sysUserRep = sysUserRep;
this._sysTenantRep = sysTenantRep;
this.rechargeRep = rechargeRep;
this.payTakeRep = payTakeRep;
this._wechatOAuth = wechatOAuth;
this.takingRep= takingRep;
this._oauthConfig = options.Value.SenparcWeixin;
this.hostingEnvironment = hostingEnvironment;
this._httpContextAccessor = _httpContextAccessor;
}
/// <summary>
/// 查询余额
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/GetUserBalance")]
2 years ago
public async Task<BalanceOut> GetUserBalance()
{
var balan = await this.balance.AsQueryable().Filter("TenantId", true).SingleAsync(x => x.UserID == UserManager.UserId);
2 years ago
if (balan == null)
return null;
return new BalanceOut()
{
Amount = balan.Amount,
UserID = UserManager.UserId,
2 years ago
};
}
/// <summary>
/// 充值记录查询
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/GetRechargeList")]
public async Task<List<RechargeOut>> GetRechargeList()
{
2 years ago
var rechargeLiist =await this.rechargeRep.Where(x => x.CreatedUserId == UserManager.UserId).ToListAsync();
return rechargeLiist.Adapt<List<RechargeOut>>();
}
2 years ago
/// <summary>
/// 修改余额
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/UpBalance")]
public async Task<dynamic> UpBalance(decimal Consume,long? ResumeID)
{
this.balance.CurrentBeginTran();
2 years ago
var comrep = await this.ComsumeRep.Where(x=>x.ResumeID== ResumeID).FirstAsync();
if(!comrep.IsNullOrZero()) //已经消费过了
return "";
2 years ago
var ban = await this.balance.Where(x => x.UserID == UserManager.UserId).FirstAsync();
if (ban == null)
throw Oops.Oh(ErrorCode.xg1002);
ban.Amount -= Consume;
if (ban.Amount < 0)
throw Oops.Oh(ErrorCode.B1001);
await this.ComsumeRep.InsertAsync(new Consume()
{
CAmount= Consume,
Surplus = ban.Amount,
Sort =(int)ConsumeEnum.Contact,
2 years ago
ResumeID = ResumeID
});
this.balance.CurrentCommitTran();
2 years ago
return await this.balance.AsUpdateable(ban).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync()>0;
}
/// <summary>
/// 查询消费记录
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/GetBalance")]
public async Task<List<ConsumeOut>> GetBalance()
2 years ago
{
var comsume = await this.ComsumeRep.Where(x => x.CreatedUserId == UserManager.UserId).OrderByDescending(x=>x.CreatedTime).ToListAsync();
return comsume.Adapt<List<ConsumeOut>>();
2 years ago
}
#region 小程序支付
/// <summary>
///微信小程序支付
/// </summary>
/// <param name="authUserInput">商品Id</param>
/// <returns></returns>
[HttpPost]
2 years ago
[Route("Mini/v1/wxpay")]
public async Task<dynamic> WxPay(AuthUserInput authUserInput)
{
2 years ago
var recharge = await this.rechargeRep.InsertReturnEntityAsync(new MiniRecharge()
{
2 years ago
PaymentMoney = authUserInput.Money,
Status = RechargeEnum.NoFinis,
Type = RechargeTypeEnum.StoredValue,
TotalPrice = authUserInput.Money
});
var payTake = await this.payTakeRep.InsertReturnEntityAsync(new MiniPayTake()
{
2 years ago
PaymentMoney = authUserInput.Money,
Type = Enum.PayTypeEnum.BUY,
OrderId = recharge.Id,
PaySn = "",
PayStatus = PayStatusEnum.NotPaying
});
int pMoney = (int)(authUserInput.Money * 100);
2 years ago
string sp_billno =
$"{Config.SenparcWeixinSetting.TenPayV3_MchId}{SystemTime.Now:yyyyMMddHHmmss}{TenPayV3Util.BuildRandomStr(6)}";
string timeStamp = TenPayV3Util.GetTimestamp();
string nonceStr = TenPayV3Util.GetNoncestr();
2 years ago
TenPayV3UnifiedorderRequestData xmlDataInfo = new TenPayV3UnifiedorderRequestData(Config.SenparcWeixinSetting.WxOpenAppId,
Config.SenparcWeixinSetting.TenPayV3_MchId, "余额充值", sp_billno,
pMoney,
"127.0.0.1",
Config.SenparcWeixinSetting.TenPayV3_TenpayNotify, TenPayV3Type.JSAPI, authUserInput.OpenID,
Config.SenparcWeixinSetting.TenPayV3_Key, nonceStr, null, null, null, null, payTake.OrderId.ToString());
2 years ago
Console.WriteLine(xmlDataInfo.PackageRequestHandler.ParseXML());
var result = TenPayOldV3.Unifiedorder(xmlDataInfo);//调用统一订单接口
string packageStr = "prepay_id=" + result.prepay_id;
2 years ago
return new
{
success = true,
result.prepay_id,
appId = Config.SenparcWeixinSetting.WxOpenAppId,
timeStamp,
nonceStr,
package = packageStr,
signType = "MD5",
paySign = TenPayV3.GetJsPaySign(Config.SenparcWeixinSetting.WxOpenAppId, timeStamp, nonceStr,
packageStr, Config.SenparcWeixinSetting.TenPayV3_Key)
};
2 years ago
}
/// <summary>
/// 卡消费支付
/// </summary>
/// <param name="authUserInput">商品Id</param>
/// <returns></returns>
[HttpPost]
[Route("Mini/v1/wxCardPay")]
public async Task<dynamic> wxCardPay(AuthUserInput authUserInput)
{
var payres = await this.payTakeRep.AsQueryable().Where(x => x.Type == Enum.PayTypeEnum.BUY && (DateTime.Now - x.CreatedTime.Value).TotalDays < 30 && x.CreatedUserId == UserManager.UserId).FirstAsync();
if (!payres.IsEmpty())
return "已经购买";
var payTake = await this.payTakeRep.InsertReturnEntityAsync(new MiniPayTake()
{
PaymentMoney = authUserInput.Money,
OrderId = YitIdHelper.NextId(),
Type = Enum.PayTypeEnum.BUY,
PaySn = "",
PayStatus = PayStatusEnum.NotPaying
});
int pMoney = (int)(authUserInput.Money );
string sp_billno =
$"{Config.SenparcWeixinSetting.TenPayV3_MchId}{SystemTime.Now:yyyyMMddHHmmss}{TenPayV3Util.BuildRandomStr(6)}";
string timeStamp = TenPayV3Util.GetTimestamp();
string nonceStr = TenPayV3Util.GetNoncestr();
TenPayV3UnifiedorderRequestData xmlDataInfo = new TenPayV3UnifiedorderRequestData(Config.SenparcWeixinSetting.WxOpenAppId,
Config.SenparcWeixinSetting.TenPayV3_MchId, "开卡支付", sp_billno,
pMoney,
"127.0.0.1",
Config.SenparcWeixinSetting.TenPayV3_TenpayNotify, TenPayV3Type.JSAPI, authUserInput.OpenID,
Config.SenparcWeixinSetting.TenPayV3_Key, nonceStr, null, null, null, null, payTake.OrderId.ToString());
Console.WriteLine(xmlDataInfo.PackageRequestHandler.ParseXML());
var result = TenPayOldV3.Unifiedorder(xmlDataInfo);//调用统一订单接口
string packageStr = "prepay_id=" + result.prepay_id;
return new
{
success = true,
result.prepay_id,
appId = Config.SenparcWeixinSetting.WxOpenAppId,
timeStamp,
nonceStr,
package = packageStr,
signType = "MD5",
paySign = TenPayV3.GetJsPaySign(Config.SenparcWeixinSetting.WxOpenAppId, timeStamp, nonceStr,
packageStr, Config.SenparcWeixinSetting.TenPayV3_Key)
};
}
/// <summary>
/// 微信小程序支付回调
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("/Mini/NotifyUrl")]
[UnifyResult(typeof(string))]
[AllowAnonymous]
public async Task<ActionResult> NotifyUrl()
{
ResponseHandler resHandler = new ResponseHandler(this._httpContextAccessor.HttpContext);
ContentResult content = new ContentResult();
string return_code = resHandler.GetParameter("return_code");
string return_msg = resHandler.GetParameter("return_msg");
resHandler.SetKey(Config.SenparcWeixinSetting.TenPayV3_Key);
try
{
//验证请求是否从微信发过来(安全)
if (resHandler.IsTenpaySign() && return_code.ToUpper() == "SUCCESS")
{
//res = "success";//正确的订单处理
//直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息!
2 years ago
Console.WriteLine("回调成功");
//attach
var paymentId = long.Parse(resHandler.GetParameter("attach"));
//业务处理
var paytake = await this.payTakeRep.Where(x => x.OrderId == paymentId).SingleAsync();
if (paytake.IsEmpty())
throw Oops.Oh("无此订单,回调失败");
this.payTakeRep.BeginTran();
paytake.PayStatus = PayStatusEnum.Paying;
switch (paytake.Type)
{
case Enum.PayTypeEnum.RECHARGE:
var recharge = await this.rechargeRep.Where(x => x.Id == paymentId).SingleAsync();
if (!recharge.IsEmpty())
2 years ago
{
recharge.Status = RechargeEnum.Finish;
this.rechargeRep.Update(recharge);
var balan = await this.balance.AsQueryable().Filter("TenantId", true).Where(x => x.UserID == paytake.CreatedUserId).SingleAsync();
if (!balan.IsEmpty())
{
balan.Amount = recharge.PaymentMoney;
this.balance.Update(balan);
}
else
{
this.balance.Insert(new Balance()
{
Amount = recharge.PaymentMoney,
UserID = (long)paytake.CreatedUserId
});
}
}
break;
case Enum.PayTypeEnum.BUY:
break;
default:
throw Oops.Oh("订单类型错误");
}
this.payTakeRep.Update(paytake);
content.Content = string.Format(@"<xml>
<return_code><![CDATA[{0}]]></return_code>
<return_msg><![CDATA[{1}]]></return_msg>
</xml>", return_code, return_msg);
this.SendTemplate(0, return_code, resHandler);
this.payTakeRep.CommitTran();
}
else
{
content.Content = "回调失败";
}
this.LogRecord(resHandler);
}
catch (Exception ex)
{
this.payTakeRep.RollbackTran();
content.Content = ex.Message;
}
return content;
}
#endregion
#region 小程序退款
/// <summary>
///微信小程序退款
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("Mini/v1/refund")]
public async Task<dynamic> WxRefund(WxRefundInput wxRefundInput)
{
//查询订单
var order = this.payTakeRep.FirstOrDefault(x => x.OrderId == wxRefundInput.OrderID);
if (order == null)
return new Exception("查询无此订单,退款失败");
//生成退款单
var res = await this.refundRep.InsertReturnEntityAsync(new ReFund()
{
State = RefundStatusEnum.REDIND,
Amount = order.PaymentMoney,
Message = "退款审核中",
PayNo = order.Id.ToString(),
});
if (res == null)
return new Exception("生成退款单失败");
string notifyUrl = string.Format(Config.SenparcWeixinSetting.TenPayV3_TenpayNotify, "RefundNotifyUrl");
string opUserId = Config.SenparcWeixinSetting.TenPayV3_MchId;
//退款金额
int refundFee = (int)(order.PaymentMoney * 100);
//支付时的总金额
int totalFee = (int)(order.PaymentMoney * 100);
string outRefundNo = res.Id.ToString();//新退款单号
string outTradeNo = order.OrderId.ToString();
var nonceStr = TenPayV3Util.GetNoncestr();
var dataInfo = new TenPayV3RefundRequestData(Config.SenparcWeixinSetting.WxOpenAppId, Config.SenparcWeixinSetting.TenPayV3_MchId,
Config.SenparcWeixinSetting.TenPayV3_Key, null, nonceStr, null, outTradeNo, outRefundNo, totalFee, refundFee, opUserId, null,
notifyUrl: notifyUrl);
return new
{
message = "退款请求完成",
};
}
/// <summary>
/// 退款回调
/// </summary>
[HttpPost("RefundNotifyUrl")]
[UnifyResult(typeof(string))]
public async Task<ActionResult> RefundNotifyUrl()
{
ResponseHandler resHandler = new ResponseHandler(this._httpContextAccessor.HttpContext);
ContentResult content = new ContentResult();
string return_code = resHandler.GetParameter("return_code");
string return_msg = resHandler.GetParameter("return_msg");
var mch_key = Senparc.Weixin.Config.SenparcWeixinSetting.TenPayV3_Key;
try
{
if (return_code.ToUpper() == "SUCCESS")
{
string req_info = resHandler.GetParameter("req_info");
var decodeReqInfo = TenPayV3Util.DecodeRefundReqInfo(req_info, mch_key);
var decodeDoc = System.Xml.Linq.XDocument.Parse(decodeReqInfo);
var refundNotifyXml = decodeDoc.Serialize();
//获取接口中需要用到的信息
string out_trade_no = decodeDoc.Root.Element("out_trade_no").Value;
string transaction_id = decodeDoc.Root.Element("transaction_id").Value;
string refund_id = decodeDoc.Root.Element("refund_id").Value;
int total_fee = int.Parse(decodeDoc.Root.Element("total_fee").Value);
int refund_fee = int.Parse(decodeDoc.Root.Element("refund_fee").Value);
string out_refund_no = decodeDoc.Root.Element("out_refund_no").Value;
var refun = this.refundRep.FirstOrDefault(x => x.PayNo == out_refund_no);
refun.PayNo = out_refund_no;
refun.Amount = ((float)refund_fee.ParseToInt() / 100).ParseToDecimal();
refun.Message = "退款完成";
refun.RefundID = refund_id;
refun.refundNo = out_refund_no;
refun.TransactionNo = transaction_id;
refun.State = RefundStatusEnum.RESUCCESS;
1 year ago
if (await this.refundRep.UpdateAsync(refun) > 0)
{
content.Content = string.Format(@"<xml>
<return_code><![CDATA[{0}]]></return_code>
<return_msg><![CDATA[{1}]]></return_msg>
</xml>", return_code, return_msg);
return content;
}
}
else
{
content.Content = "回调处理失败!";
}
}
catch (Exception e)
{
content.Content = e.Message;
}
return content;
}
#endregion
#region 商户
/// <summary>
/// 提现审批
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("/Mini/v1/Approval")]
public async Task<bool> Approval(ApprovalInput approvalInput)
{
this.takingRep.BeginTran();
var taking = await this.takingRep.AsQueryable().FirstAsync(x => x.Id == approvalInput.ID);
if (taking == null)
throw Oops.Oh(ErrorCode.xg1002);
var balance = await this.balance.AsQueryable().FirstAsync(x => x.UserID == taking.CreatedUserId);
if (taking.Status == Enum.TakingEnum.FINISHED)
throw Oops.Oh("重复审核");
try
{
if (!approvalInput.Examine && taking.Status != Enum.TakingEnum.FAIL)
{
//回退金额 修改状态
taking.Status = Enum.TakingEnum.FAIL;
balance.Amount += taking.Money;
taking.Message = "申请提现失败!请联系客服";
}
else if(approvalInput.Examine)
{
var hostting = this.hostingEnvironment.ContentRootPath;
//读取商户信息
var appid = Config.SenparcWeixinSetting.TenPayV3_AppId;
var mchid = Config.SenparcWeixinSetting.TenPayV3_MchId;
var serialno = Config.SenparcWeixinSetting.TenPayV3_SerialNumber;
string url = string.Format("https://api.mch.weixin.qq.com/v3/transfer/batches");
string partnerTradeNo = "tk" + DateTime.Now.ToString("yyyyMMddHHmmfff");
JsApiPay jsApiPay = new JsApiPay();
int amount = Convert.ToInt32(taking.Money * 100);
JsPay result = await jsApiPay.WithDrawsToWx(url, appid, mchid, serialno, taking.OpenID, partnerTradeNo, amount, hostting);
if (result.code.IsEmpty())
{
taking.Status = Enum.TakingEnum.FINISHED;
taking.WXOrderNo = result.out_batch_no;
}
else
{
taking.Status = Enum.TakingEnum.FAIL;
balance.Amount += taking.Money;
taking.Message = result.message;
}
}
await this.takingRep.UpdateAsync(taking);
this.balance.Update(balance);
this.takingRep.CommitTran();
}
catch (Exception)
{
this.takingRep.RollbackTran();
return false;
}
return true;
}
/// <summary>
/// 提现申请
/// </summary>
/// <param name="takingApply"></param>
/// <returns></returns>
[HttpPost]
[Route("Mini/v1/TakingApply")]
public async Task<bool> TakingApply(TakingApplyInput takingApply)
{
//纪录申请
var bRes = await this.balance.AsQueryable().FirstAsync(x => x.UserID == UserManager.UserId);
if (bRes == null)
throw Oops.Oh(ErrorCode.xg1002);
if (bRes.Amount < takingApply.TransferAmount)
throw Oops.Oh(ErrorCode.xg1002);
try
{
this.balance.BeginTran();
bRes.Amount -= takingApply.TransferAmount;
await this.takingRep.InsertAsync(new Taking()
{
OrderNo = YitIdHelper.NextId(),
Money = takingApply.TransferAmount,
Status = Enum.TakingEnum.ACCEPTED,
OpenID = takingApply.OpenID,
Remarks = takingApply.Remarks
});
await this.balance.UpdateAsync(bRes);
this.balance.CommitTran();
}
catch (Exception)
{
this.balance.RollbackTran();
return false;
}
return true ;
}
#endregion
#region 附加服务
public void SendTemplate(int type, string return_code, ResponseHandler resHandler)
{
switch (type)
{
case 0:
//发送支付成功的模板消息
try
{
string appId = Config.SenparcWeixinSetting.TenPayV3_AppId;//与微信公众账号后台的AppId设置保持一致区分大小写。
string openId = resHandler.GetParameter("openid");
var templateData = new WeixinTemplate_PaySuccess("https://weixin.senparc.com", "微信支付 V2 购买商品", "状态:" + return_code);
Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息参数", appId + " , " + openId);
var result = Senparc.Weixin.MP.AdvancedAPIs.TemplateApi.SendTemplateMessage(appId, openId, templateData);
}
catch (Exception ex)
{
Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息", ex.ToString());
}
break;
default:
break;
}
}
private void LogRecord(ResponseHandler resHandler)
{
#region 记录日志
var logDir = ServerUtility.ContentRootMapPath(string.Format("~/App_Data/TenPayNotify/{0}", SystemTime.Now.ToString("yyyyMMdd")));
if (!Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
var logPath = Path.Combine(logDir, string.Format("{0}-{1}-{2}.txt", SystemTime.Now.ToString("yyyyMMdd"), SystemTime.Now.ToString("HHmmss"), Guid.NewGuid().ToString("n").Substring(0, 8)));
using (var fileStream = System.IO.File.OpenWrite(logPath))
{
var notifyXml = resHandler.ParseXML();
fileStream.Write(Encoding.Default.GetBytes(notifyXml), 0, Encoding.Default.GetByteCount(notifyXml));
fileStream.Close();
}
#endregion
}
#endregion
2 years ago
}
}