using Infrastructure; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Senparc.CO2NET.Extensions; using Senparc.CO2NET.HttpUtility; using Senparc.CO2NET.Utilities; using Senparc.Weixin; using Senparc.Weixin.Entities; using Senparc.Weixin.Exceptions; using Senparc.Weixin.Helpers; using Senparc.Weixin.MP.AdvancedAPIs.MerChant; using Senparc.Weixin.TenPayV3; using Senparc.Weixin.TenPayV3.Apis; using Senparc.Weixin.TenPayV3.Apis.BasePay; using Senparc.Weixin.TenPayV3.Apis.Entities; using Senparc.Weixin.TenPayV3.Entities; using Senparc.Weixin.TenPayV3.Helpers; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Senparc.Weixin.TenPayV3.Apis.BasePay.Entities; namespace Infrastructure.WeChat.TenPay { /// /// 微信支付基础类 /// public class Pay { private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); /// /// 用于初始化BasePayApis /// private readonly ISenparcWeixinSettingForTenpayV3 _tenpayV3Setting; public static HttpContext HttpContext => HttpContextLocal.Current(); private readonly BasePayApis _basePayApis; private readonly SenparcHttpClient _httpClient; /// /// trade_no 和 transaction_id 对照表 /// TODO:可以放入缓存,设置有效时间 /// //public static ConcurrentDictionary TradeNumberToTransactionId = new ConcurrentDictionary(); public TenPayV3Info TenPayV3Info; public Pay(SenparcHttpClient httpClient) { var TenPayV3_AppId = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_AppId"); var TenPayV3_AppSecret = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_AppSecret"); var TenPayV3_MchId = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_MchId"); var TenPayV3_MchName = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_MchName"); var TenPayV3_Key = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_Key"); var TenPayV3_CertPath = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_CertPath"); var TenPayV3_CertSecret = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_CertPath"); var TenPayV3_TenpayNotify = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_TenpayNotify"); var TenPayV3_WxOpenNotify = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_WxOpenNotify"); var TenPayV3_PrivateKey = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_PrivateKey"); var TenPayV3_SerialNumber = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_SerialNumber"); var TenPayV3_ApiV3Key = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_ApiV3Key"); var tenPayV3Info = new TenPayV3Info(TenPayV3_AppId, TenPayV3_AppSecret, TenPayV3_MchId, TenPayV3_Key, TenPayV3_CertPath, TenPayV3_CertSecret,TenPayV3_TenpayNotify, TenPayV3_WxOpenNotify,TenPayV3_PrivateKey, TenPayV3_SerialNumber, TenPayV3_ApiV3Key); TenPayV3InfoCollection.Register(tenPayV3Info, TenPayV3_MchName); this.TenPayV3Info = tenPayV3Info; _tenpayV3Setting = Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting; _basePayApis = new BasePayApis(_tenpayV3Setting); this._httpClient = httpClient; } public async Task PrePay(string openId,string orderNo,int type,int price) { string sp_billno = orderNo;//out_trade_no //调用下单接口下单 var TenPayV3_MchName = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_MchName"); var name = TenPayV3_MchName; try { var appId = TenPayV3Info.AppId; var notifyUrl = TenPayV3Info.TenPayV3_WxOpenNotify; //请求信息 TransactionsRequestData jsApiRequestData = new( appId, TenPayV3Info.MchId, name, sp_billno, new TenpayDateTime(DateTime.Now.AddMinutes(1), false), null, notifyUrl, null, new() { currency = "CNY", total = price }, new(openId), null, null, null ); logger.Info("支付参数:{0}", jsApiRequestData.ToJson()); //请求接口 var basePayApis2 = new Senparc.Weixin.TenPayV3.TenPayHttpClient.BasePayApis2(_httpClient, _tenpayV3Setting); var result = await basePayApis2.JsApiAsync(jsApiRequestData); logger.Info("支付结果:{@UnifiedorderResult}", result); if (result.VerifySignSuccess != true) { throw new WeixinException("获取 prepay_id 结果校验出错!"); } //获取 UI 信息包 var jsApiUiPackage = TenPaySignHelper.GetJsApiUiPackage(appId, result.prepay_id); return jsApiUiPackage; } catch (Exception) { throw; } } /// /// JS-SDK支付回调地址(在下单接口中设置的 notify_url) /// /// public async Task PayNotifyUrl() { try { //获取微信服务器异步发送的支付通知信息 var resHandler = new TenPayNotifyHandler(HttpContext); var orderReturnJson = await resHandler.AesGcmDecryptGetObjectAsync(); //演示记录 transaction_id,实际开发中需要记录到数据库,以便退款和后续跟踪 //TradeNumberToTransactionId[orderReturnJson.out_trade_no] = orderReturnJson.transaction_id; //获取支付状态 string trade_state = orderReturnJson.trade_state; //验证请求是否从微信发过来(安全) NotifyReturnData returnData = new(); //验证可靠的支付状态 if (orderReturnJson.VerifySignSuccess == true && trade_state == "SUCCESS") { returnData.code = "SUCCESS";//正确的订单处理 logger.Info("回调成功啦!", returnData.code.ToString()); /* 提示: * 1、直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息! * 2、上述判断已经具有比较高的安全性以外,还可以对访问 IP 进行判断进一步加强安全性。 * 3、下面演示的是发送支付成功的模板消息提示,非必须。 */ } else { returnData.code = "FAILD";//错误的订单处理 returnData.message = "验证失败"; //此处可以给用户发送支付失败提示等 } #region 记录日志(也可以记录到数据库审计日志中) var notifyJson = orderReturnJson.ToJson().ToString(); await payLog("WechatPay", "支付通知信息:" + notifyJson + ""); #endregion //https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml return orderReturnJson; } catch (Exception ex) { WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex)); throw; } } /// /// 订单查询 /// /// public async Task OrderQuery(string out_trade_no = null, string transaction_id = null) { //out_trade_no transaction_id 两个参数不能都为空 if (out_trade_no is null && transaction_id is null) { throw new ArgumentNullException(nameof(out_trade_no) + " or " + nameof(transaction_id)); } OrderReturnJson result = null; //选择方式查询订单 if (out_trade_no is not null) { result = await _basePayApis.OrderQueryByOutTradeNoAsync(out_trade_no, TenPayV3Info.MchId); } if (transaction_id is not null) { result = await _basePayApis.OrderQueryByTransactionIdAsync(transaction_id, TenPayV3Info.MchId); } return result; } /// /// 关闭订单 /// /// public async Task CloseOrder(string out_trade_no) { //out_trade_no transaction_id 两个参数不能都为空 if (out_trade_no is null) { throw new ArgumentNullException(nameof(out_trade_no)); } ReturnJsonBase result = null; result = await _basePayApis.CloseOrderAsync(out_trade_no, TenPayV3Info.MchId); return result; } /// /// 退款申请接口 /// /// public async Task Refund(string transactionId, decimal totalFee,string? paymentRefundNumber,string reason) { try { string outRefundNo; await payLog("WechatRefund", "1asdasdas"); string nonceStr = TenPayV3Util.GetNoncestr(); await payLog("WechatRefund", "2 退款微信单号transactionId:" + transactionId + "/n 订单金额:" + totalFee); if (!string.IsNullOrEmpty(paymentRefundNumber)) { outRefundNo = paymentRefundNumber; } else { outRefundNo = CreateNo_Recharge(); } int refundFee = Convert.ToInt32(totalFee); string opUserId = TenPayV3Info.MchId; var notifyUrl = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_RefundNotify"); //var dataInfo = new TenPayV3RefundRequestData(TenPayV3Info.AppId, TenPayV3Info.MchId, TenPayV3Info.Key, // null, nonceStr, null, outTradeNo, outRefundNo, totalFee, refundFee, opUserId, null, notifyUrl: notifyUrl); //TODO:该接口参数二选一传入 var dataInfo = new RefundRequsetData(transactionId, null, outRefundNo, reason, notifyUrl, null, new RefundRequsetData.Amount(refundFee, null, refundFee, "CNY"), null); await payLog("WechatRefund", "2.5 DataInfo:" + dataInfo.ToJson()); //#region 新方法(Senparc.Weixin v6.4.4+) //var result = TenPayOldV3.Refund(_serviceProvider, dataInfo);//证书地址、密码,在配置文件中设置,并在注册微信支付信息时自动记录 //#endregion var result = await _basePayApis.RefundAsync(dataInfo); await payLog("WechatRefund", "3 退款结果Result:" + result.ToJson()); return result; //return Json(result, JsonRequestBehavior.AllowGet); } catch (Exception ex) { await payLog("WechatRefund", "报错:" + ex.Message); WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex)); throw; } } /// /// 退款通知地址 /// /// public async Task RefundNotifyUrl() { await payLog("WechatRefund", "允许被访问IP" + HttpContext.UserHostAddress()?.ToString()); NotifyReturnData returnData = new(); var resHandler = new TenPayNotifyHandler(HttpContext); var refundNotifyJson = await resHandler.AesGcmDecryptGetObjectAsync(); try { await payLog("WechatRefund", "退款支付结果:" + refundNotifyJson.ToJson()); string refund_status = refundNotifyJson.refund_status; if (/*refundNotifyJson.VerifySignSuccess == true &*/ refund_status == "SUCCESS") { returnData.code = "SUCCESS"; returnData.message = "OK"; //填写逻辑 await payLog("WechatRefund", "验证通过"); } else { returnData.code = "FAILD"; returnData.message = "验证失败"; await payLog("WechatRefund", "验证失败"); } return refundNotifyJson; //进行后续业务处理 } catch (Exception ex) { returnData.code = "FAILD"; returnData.message = ex.Message; WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex)); } return refundNotifyJson; //https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_3.shtml } #region 参数模型 /// /// 小程序支付返回的参数 /// public class PayParams { /// /// 产品Guid /// public JsApiUiPackage jsApiUiPackage { get; set; } /// /// 系统订单号 /// public string outTradeNo { get; set; } /// ///订单下单时间 /// public DateTime CreateTime { get; set; } /// ///订单结束时间 /// public DateTime OverTime { get; set; } } /// /// 小程序支付接口的参数 /// public class PayDto { /// /// 客户Guid /// public long UserId { get; set; } /// /// 支付类型 /// public int PayType { get; set; } /// /// 小程序用户OpenId /// public string OpenId { get; set; } /// /// 核销前价格 /// public decimal BeforeMoney { get; set; } /// /// 核销后价格/操作金额 /// public decimal Money { get; set; } /// /// 留言 /// public string Remark { get; set; } } /// /// 小程序支付订单查询的参数 /// public class OrderQueryDto { /// /// 系统订单号 /// public string? outTradeNo { get; set; } /// /// 微信支付订单号 /// public string? transactionId { get; set; } } /// /// 小程序支付需要的参数 /// public class PayRequesEntity { /// /// 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间 /// public string timeStamp { get; set; } /// /// 随机字符串,长度为32个字符以下。 /// public string nonceStr { get; set; } /// /// 统一下单接口返回的 prepay_id 参数值 /// public string package { get; set; } /// /// 签名算法 /// public string signType { get; set; } /// /// 签名 /// public string paySign { get; set; } } #endregion #region 微信支付日志操作 /// /// 微信支付日志 /// /// public async Task payLog(string dirName,string data) { var logDir = ServerUtility.ContentRootMapPath(string.Format("~/App_Data/{0}/{1}", dirName , 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)) { await fileStream.WriteAsync(Encoding.Default.GetBytes(data), 0, Encoding.Default.GetByteCount(data)); fileStream.Close(); } } #endregion /// /// 申请资金账单接口 /// /// 日期,格式如:2021-08-27 /// public async Task FundflowBill(string date) { var filePath = $"{date}-FundflowBill.csv"; Console.WriteLine("FilePath:" + filePath); using (var fs = new FileStream(filePath, FileMode.OpenOrCreate)) { BasePayApis basePayApis = new BasePayApis(); var result = await _basePayApis.FundflowBillQueryAsync(date, fs); fs.Flush(); } return "已经下载倒指定目录,文件名:" + filePath; } public static object _lock = new object(); public static int count = 1; /// /// 退款订单号生成 /// /// public static string CreateNo_Recharge() { lock (_lock) { if (count >= 10000) { count = 1; } var number = "R" + DateTime.Now.ToString("yyMMddHHmmssfff") + count.ToString("0000"); count++; return number; } } } }