using GDZZ.Application.Entity; using GDZZ.Core.Entity; using GDZZ.Core; 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; using GDZZ.Application.Service.WXPay.Dto; 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; using Furion.FriendlyException; using Senparc.CO2NET.Cache.Redis; using GDZZ.Core.Service; using System.Linq.Dynamic.Core.Tokenizer; using System.Diagnostics; namespace GDZZ.Application.Service.WXPay { [ApiDescriptionSettings("Application", Name = "WXPay", Order = 1)] public class WXPayService : IWXPayService, IDynamicApiController, ITransient { private readonly SqlSugarRepository Baseuser; // wx用户仓储 private readonly SqlSugarRepository _sysUserRep; // 用户表仓储 private readonly SqlSugarRepository _sysTenantRep; //租户仓储 private readonly SqlSugarRepository self; //职业仓储 private readonly SqlSugarRepository ComsumeRep; //消费记录仓储 private readonly SqlSugarRepository balance; //余额仓储 private readonly SqlSugarRepository rechargeRep; //充值仓储 private readonly SqlSugarRepository payTakeRep; //支付仓储 private readonly SqlSugarRepository refundRep; //退款仓储 private readonly WechatOAuth _wechatOAuth; //微信权限服务 private readonly IHttpContextAccessor _httpContextAccessor; //http服务 /// /// 获取配置文件 /// private readonly SenparcWeixinSetting _oauthConfig; public WXPayService( IOptions options, SqlSugarRepository Baseuser, SqlSugarRepository sysTenantRep, SqlSugarRepository sysUserRep, SqlSugarRepository balance, SqlSugarRepository ComsumeRep, SqlSugarRepository Self, SqlSugarRepository rechargeRep, SqlSugarRepository payTakeRep, SqlSugarRepository refundRep, WechatOAuth wechatOAuth, IHttpContextAccessor _httpContextAccessor, IEventPublisher eventPublisher) { this.self = Self; this.refundRep= refundRep; this.balance = balance; this.Baseuser = Baseuser; this.ComsumeRep = ComsumeRep; this._sysUserRep = sysUserRep; this._sysTenantRep = sysTenantRep; this.rechargeRep = rechargeRep; this.payTakeRep = payTakeRep; this._wechatOAuth = wechatOAuth; this._oauthConfig = options.Value.SenparcWeixin; this._httpContextAccessor = _httpContextAccessor; } /// /// 查询余额 /// /// [HttpGet] [Route("Mini/v1/GetUserBalance")] public async Task GetUserBalance() { var balan = await this.balance.AsQueryable().Filter("TenantId", true).SingleAsync(x => x.UserID == UserManager.UserId); if (balan == null) return null; return new BalanceOut() { Amount = balan.Amount, UserID = UserManager.UserId, }; } /// /// 充值记录查询 /// /// [HttpGet] [Route("Mini/v1/GetRechargeList")] public async Task> GetRechargeList() { var rechargeLiist =await this.rechargeRep.Where(x => x.CreatedUserId == UserManager.UserId).ToListAsync(); return rechargeLiist.Adapt>(); } /// /// 修改余额 /// /// [HttpGet] [Route("Mini/v1/UpBalance")] public async Task UpBalance(decimal Consume,long? ResumeID) { var comrep = await this.ComsumeRep.Where(x=>x.ResumeID== ResumeID).FirstAsync(); if(!comrep.IsNullOrZero()) //已经消费过了 return; 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, ResumeID = ResumeID }); return await this.balance.AsUpdateable(ban).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync()>0; } /// /// 查询消费记录 /// /// [HttpGet] [Route("Mini/v1/GetBalance")] public async Task> GetBalance() { var comsume = await this.ComsumeRep.Where(x => x.CreatedUserId == UserManager.UserId).OrderByDescending(x=>x.CreatedTime).ToListAsync(); return comsume.Adapt>(); } #region 小程序支付 /// ///微信小程序支付 /// /// 商品Id /// [HttpPost] [Route("Mini/v1/wxpay")] public async Task WxPay(AuthUserInput authUserInput) { var recharge = await this.rechargeRep.InsertReturnEntityAsync(new MiniRecharge() { PaymentMoney = authUserInput.Money, Status = RechargeEnum.NoFinis, Type = RechargeTypeEnum.StoredValue, TotalPrice = authUserInput.Money }); var payTake = await this.payTakeRep.InsertReturnEntityAsync(new MiniPayTake() { PaymentMoney = authUserInput.Money, OrderId = recharge.Id, PaySn = "", PayStatus = PayStatusEnum.NotPaying }); int pMoney = (int)(authUserInput.Money * 100); 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) }; } /// /// 微信小程序支付回调 /// /// [HttpPost] [Route("/Mini/NotifyUrl")] [UnifyResult(typeof(string))] [AllowAnonymous] public async Task 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";//正确的订单处理 //直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息! Console.WriteLine("回调成功"); //attach var paymentId = long.Parse(resHandler.GetParameter("attach")); //业务处理 var paytake = await this.payTakeRep.Where(x => x.OrderId == paymentId).SingleAsync(); if (!paytake.IsEmpty()) { paytake.PayStatus = PayStatusEnum.Paying; this.payTakeRep.Update(paytake); } var recharge = await this.rechargeRep.Where(x => x.Id == paymentId).SingleAsync(); if (!recharge.IsEmpty()) { 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 }); } } content.Content = string.Format(@" ", return_code, return_msg); this.SendTemplate(0, return_code, resHandler); } else { content.Content = "回调失败"; } this.LogRecord(resHandler); } catch (Exception ex) { content.Content = ex.Message; } return content; } #endregion #region 小程序退款 /// ///微信小程序退款 /// /// [HttpPost] [Route("Mini/v1/refund")] public async Task 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 = "退款请求完成", }; } /// /// 退款回调 /// [HttpPost("RefundNotifyUrl")] [UnifyResult(typeof(string))] public async Task 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; if (await this.refundRep.UpdateAsync(refun) > 0) { content.Content = string.Format(@" ", return_code, return_msg); return content; } } else { content.Content = "回调处理失败!"; } } catch (Exception e) { content.Content = e.Message; } return content; } #endregion #region 商户 /// /// 商户转账 /// /// [HttpPost] [Route("Mini/v1/GetBalance")] public async Task MerchantTransfer(TransferInput wxRefundInput) { //读取商户信息 var appid = Config.SenparcWeixinSetting.TenPayV3_AppId; var mchid = Config.SenparcWeixinSetting.TenPayV3_MchId; var serialno = Config.SenparcWeixinSetting.TenPayV3_SerialNumber; string partnerTradeNo = "xcx" + DateTime.Now.ToString("yyyyMMddHHmmfff"); string result = JsApiPay.WithDrawsToWx(appid, mchid, serialno, wxRefundInput.MerchantID, partnerTradeNo, Convert.ToInt32(wxRefundInput.TransferAmount * 100)); return result; } #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 } }