diff --git a/Mtxfw.Utility/Mtxfw.Utility.csproj b/Mtxfw.Utility/Mtxfw.Utility.csproj index dff6350..06b31fc 100644 --- a/Mtxfw.Utility/Mtxfw.Utility.csproj +++ b/Mtxfw.Utility/Mtxfw.Utility.csproj @@ -212,8 +212,6 @@ - - @@ -230,6 +228,7 @@ + diff --git a/Mtxfw.Utility/WXPay.MiniProgramPay.cs b/Mtxfw.Utility/WXPay.MiniProgramPay.cs new file mode 100644 index 0000000..c86fca6 --- /dev/null +++ b/Mtxfw.Utility/WXPay.MiniProgramPay.cs @@ -0,0 +1,726 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.IO; +using System.Text; +using LitJson; + +namespace Mtxfw.Utility +{ + /// + /// 微信小程序支付结果类 + /// 用于封装创建JSAPI支付订单后的返回结果 + /// + public class MiniProgramPayResult + { + /// + /// 支付订单创建是否成功 + /// true: 成功 false: 失败 + /// + public bool Success { get; set; } + + /// + /// 结果消息描述 + /// 成功时为"下单成功",失败时为具体错误信息 + /// + public string Message { get; set; } + + /// + /// 时间戳 + /// 自1970年1月1日以来的秒数,用于小程序端调起支付 + /// + public string TimeStamp { get; set; } + + /// + /// 随机字符串 + /// 32位以内的随机串,用于签名计算 + /// + public string NonceStr { get; set; } + + /// + /// 订单详情扩展字符串 + /// 格式: prepay_id=xxx,小程序端调起支付时需要此参数 + /// + public string Package { get; set; } + + /// + /// 签名 + /// 用于小程序端调起支付时的安全校验 + /// + public string PaySign { get; set; } + + /// + /// 签名类型 + /// 固定值: MD5 + /// + public string SignType { get; set; } + + /// + /// 预支付交易会话标识 + /// 微信返回的预支付ID,有效期2小时 + /// + public string PrepayId { get; set; } + + /// + /// 商户订单号 + /// 商户系统内部的订单号,要求32个字符内,唯一性 + /// + public string OutTradeNo { get; set; } + + /// + /// 微信支付订单号 + /// 微信支付系统生成的订单号,查询订单时使用 + /// + public string TransactionId { get; set; } + + /// + /// 错误码 + /// 支付失败时的错误代码,如: NOTENOUGH(余额不足) + /// + public string ErrCode { get; set; } + + /// + /// 错误码描述 + /// 支付失败时的错误详细信息 + /// + public string ErrCodeDes { get; set; } + } + + /// + /// 微信小程序支付回调通知结果类 + /// 用于封装处理支付回调通知后的返回结果 + /// + public class MiniProgramPayNotifyResult + { + /// + /// 支付回调处理是否成功 + /// true: 成功 false: 失败 + /// + public bool Success { get; set; } + + /// + /// 结果消息描述 + /// + public string Message { get; set; } + + /// + /// 商户订单号 + /// 商户系统内部的订单号 + /// + public string OutTradeNo { get; set; } + + /// + /// 微信支付订单号 + /// 微信支付系统生成的订单号 + /// + public string TransactionId { get; set; } + + /// + /// 订单总金额 + /// 单位: 分,需要转换为元显示 + /// + public int TotalFee { get; set; } + + /// + /// 用户标识 + /// 用户在商户appid下的唯一标识 + /// + public string OpenId { get; set; } + + /// + /// 支付完成时间 + /// 格式: yyyyMMddHHmmss + /// + public string TimeEnd { get; set; } + + /// + /// 银行类型 + /// 银行编码,如: CMC(招商银行) + /// + public string BankType { get; set; } + + /// + /// 返回给微信的XML响应 + /// 用于告知微信支付后台处理结果 + /// + public string ResultXml { get; set; } + } + + /// + /// 微信小程序支付核心类 + /// 提供创建JSAPI支付订单、处理支付回调、验证支付结果等核心功能 + /// + public class MiniProgramPay + { + /// + /// 配置对象,包含微信支付相关配置信息 + /// + private Config _config; + + /// + /// 接口超时时间,单位: 秒,默认6秒 + /// + private int _timeout = 6; + + /// + /// 构造函数 + /// + /// 配置对象,包含appid、商户号、密钥等信息 + public MiniProgramPay(Config config) + { + _config = config; + } + + /// + /// 构造函数(带超时设置) + /// + /// 配置对象 + /// 接口超时时间,单位: 秒 + public MiniProgramPay(Config config, int timeout) + { + _config = config; + _timeout = timeout; + } + + /// + /// 创建JSAPI支付订单 + /// 调用微信统一下单接口,获取小程序调起支付所需的参数 + /// + /// 用户标识,用户在商户appid下的唯一标识,必填 + /// 商户订单号,商户系统内部订单号,要求32个字符内,必填 + /// 订单总金额,单位: 分,必填 + /// 商品描述,如: "腾讯充值中心-QQ会员充值",必填 + /// 附加数据,在查询API和支付通知中原样返回,可选 + /// 支付结果通知回调地址,可选,不传则使用配置中的地址 + /// MiniProgramPayResult对象,包含支付参数或错误信息 + public MiniProgramPayResult CreateJsApiOrder(string openid, string outTradeNo, int totalFee, string body, string attach = "", string notifyUrl = "") + { + MiniProgramPayResult result = new MiniProgramPayResult(); + try + { + // 参数校验 - openid必填 + if (string.IsNullOrEmpty(openid)) + { + result.Success = false; + result.Message = "openid不能为空"; + return result; + } + // 参数校验 - 商户订单号必填 + if (string.IsNullOrEmpty(outTradeNo)) + { + result.Success = false; + result.Message = "商户订单号不能为空"; + return result; + } + // 参数校验 - 支付金额必须大于0 + if (totalFee <= 0) + { + result.Success = false; + result.Message = "支付金额必须大于0"; + return result; + } + // 参数校验 - 商品描述必填 + if (string.IsNullOrEmpty(body)) + { + result.Success = false; + result.Message = "商品描述不能为空"; + return result; + } + + // 构建统一下单请求参数 + WxPayData data = new WxPayData(); + data.SetValue("body", body); // 商品描述 + // 附加数据,可选 + if (!string.IsNullOrEmpty(attach)) + { + data.SetValue("attach", attach); + } + data.SetValue("out_trade_no", outTradeNo); // 商户订单号 + data.SetValue("total_fee", totalFee); // 订单总金额,单位: 分 + data.SetValue("time_start", DateTime.Now.ToString("yyyyMMddHHmmss")); // 交易起始时间 + data.SetValue("time_expire", DateTime.Now.AddMinutes(10).ToString("yyyyMMddHHmmss")); // 交易结束时间,10分钟后过期 + data.SetValue("trade_type", "JSAPI"); // 交易类型,小程序支付固定为JSAPI + data.SetValue("openid", openid); // 用户标识 + + // 支付结果通知回调地址,可选 + if (!string.IsNullOrEmpty(notifyUrl)) + { + data.SetValue("notify_url", HttpUtility.UrlEncode(notifyUrl)); + } + + // 调用微信统一下单接口 + WxPayData unifiedOrderResult = WxPayApi.UnifiedOrder(_config, data, _timeout); + + // 检查统一下单是否成功 + if (!unifiedOrderResult.IsSet("appid") || !unifiedOrderResult.IsSet("prepay_id") || unifiedOrderResult.GetValue("prepay_id").ToString() == "") + { + result.Success = false; + result.Message = "统一下单失败"; + // 获取微信返回的错误信息 + if (unifiedOrderResult.IsSet("return_msg")) + { + result.Message = unifiedOrderResult.GetValue("return_msg").ToString(); + } + // 获取错误码描述 + if (unifiedOrderResult.IsSet("err_code_des")) + { + result.ErrCodeDes = unifiedOrderResult.GetValue("err_code_des").ToString(); + } + // 获取错误码 + if (unifiedOrderResult.IsSet("err_code")) + { + result.ErrCode = unifiedOrderResult.GetValue("err_code").ToString(); + } + Log.Error("MiniProgramPay", "UnifiedOrder failed: " + unifiedOrderResult.ToJson()); + return result; + } + + // 获取预支付交易会话标识 + string prepayId = unifiedOrderResult.GetValue("prepay_id").ToString(); + result.PrepayId = prepayId; + result.OutTradeNo = outTradeNo; + + // 构建小程序调起支付所需的参数 + WxPayData jsApiParam = new WxPayData(); + jsApiParam.SetValue("appId", unifiedOrderResult.GetValue("appid")); // 公众号ID + jsApiParam.SetValue("timeStamp", WxPayApi.GenerateTimeStamp()); // 时间戳 + jsApiParam.SetValue("nonceStr", WxPayApi.GenerateNonceStr()); // 随机字符串 + jsApiParam.SetValue("package", "prepay_id=" + prepayId); // 订单详情 + jsApiParam.SetValue("signType", "MD5"); // 签名类型 + jsApiParam.SetValue("paySign", jsApiParam.MakeSign(_config)); // 签名 + + // 设置返回结果 + result.Success = true; + result.Message = "下单成功"; + result.TimeStamp = jsApiParam.GetValue("timeStamp").ToString(); + result.NonceStr = jsApiParam.GetValue("nonceStr").ToString(); + result.Package = jsApiParam.GetValue("package").ToString(); + result.PaySign = jsApiParam.GetValue("paySign").ToString(); + result.SignType = "MD5"; + + Log.Info("MiniProgramPay", "CreateJsApiOrder success, outTradeNo: " + outTradeNo); + return result; + } + catch (WxPayException ex) + { + // 微信支付异常处理 + result.Success = false; + result.Message = ex.Message; + Log.Error("MiniProgramPay", "WxPayException: " + ex.Message); + return result; + } + catch (Exception ex) + { + // 系统异常处理 + result.Success = false; + result.Message = "系统异常"; + Log.Error("MiniProgramPay", "Exception: " + ex.ToString()); + return result; + } + } + + /// + /// 处理支付回调通知 + /// 接收微信支付后台发送的支付结果,验证签名并返回处理结果 + /// + /// HTTP请求输入流,包含微信发送的XML数据 + /// MiniProgramPayNotifyResult对象,包含支付结果信息 + public MiniProgramPayNotifyResult ProcessNotify(Stream inputStream) + { + MiniProgramPayNotifyResult result = new MiniProgramPayNotifyResult(); + try + { + // 读取微信支付后台发送的XML数据 + int count = 0; + byte[] buffer = new byte[1024]; + StringBuilder builder = new StringBuilder(); + while ((count = inputStream.Read(buffer, 0, 1024)) > 0) + { + builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); + } + inputStream.Flush(); + inputStream.Close(); + inputStream.Dispose(); + + string notifyData = builder.ToString(); + Log.Info("MiniProgramPay", "Receive notify data: " + notifyData); + + // 检查回调数据是否为空 + if (string.IsNullOrEmpty(notifyData)) + { + result.Success = false; + result.Message = "回调数据为空"; + result.ResultXml = BuildNotifyResponse(false, "回调数据为空"); + return result; + } + + // 解析XML数据并验证签名 + WxPayData data = new WxPayData(); + try + { + data.FromXml(_config, notifyData); + } + catch (WxPayException ex) + { + // 签名验证失败 + result.Success = false; + result.Message = "签名验证失败"; + result.ResultXml = BuildNotifyResponse(false, ex.Message); + Log.Error("MiniProgramPay", "Sign check failed: " + ex.Message); + return result; + } + + // 检查返回状态码是否为SUCCESS + if (!data.IsSet("return_code") || data.GetValue("return_code").ToString() != "SUCCESS") + { + result.Success = false; + result.Message = "返回状态码不正确"; + result.ResultXml = BuildNotifyResponse(false, "返回状态码不正确"); + return result; + } + + // 检查业务结果是否为SUCCESS + if (!data.IsSet("result_code") || data.GetValue("result_code").ToString() != "SUCCESS") + { + result.Success = false; + result.Message = "业务结果不正确"; + if (data.IsSet("err_code_des")) + { + result.Message = data.GetValue("err_code_des").ToString(); + } + result.ResultXml = BuildNotifyResponse(false, result.Message); + return result; + } + + // 检查微信订单号是否存在 + if (!data.IsSet("transaction_id")) + { + result.Success = false; + result.Message = "微信订单号不存在"; + result.ResultXml = BuildNotifyResponse(false, "微信订单号不存在"); + return result; + } + + // 提取支付结果数据 + string transactionId = data.GetValue("transaction_id").ToString(); // 微信支付订单号 + string outTradeNo = data.IsSet("out_trade_no") ? data.GetValue("out_trade_no").ToString() : ""; // 商户订单号 + int totalFee = data.IsSet("total_fee") ? int.Parse(data.GetValue("total_fee").ToString()) : 0; // 订单总金额,单位: 分 + string openid = data.IsSet("openid") ? data.GetValue("openid").ToString() : ""; // 用户标识 + string timeEnd = data.IsSet("time_end") ? data.GetValue("time_end").ToString() : ""; // 支付完成时间 + string bankType = data.IsSet("bank_type") ? data.GetValue("bank_type").ToString() : ""; // 银行类型 + + // 验证订单真实性 - 通过查询订单接口验证 + bool queryResult = VerifyPaymentResult(transactionId); + if (!queryResult) + { + result.Success = false; + result.Message = "订单查询验证失败"; + result.ResultXml = BuildNotifyResponse(false, "订单查询验证失败"); + Log.Error("MiniProgramPay", "Order verify failed, transactionId: " + transactionId); + return result; + } + + // 设置返回结果 + result.Success = true; + result.Message = "支付成功"; + result.TransactionId = transactionId; + result.OutTradeNo = outTradeNo; + result.TotalFee = totalFee; + result.OpenId = openid; + result.TimeEnd = timeEnd; + result.BankType = bankType; + result.ResultXml = BuildNotifyResponse(true, "OK"); + + Log.Info("MiniProgramPay", "ProcessNotify success, transactionId: " + transactionId + ", outTradeNo: " + outTradeNo); + return result; + } + catch (Exception ex) + { + // 系统异常处理 + result.Success = false; + result.Message = "系统异常"; + result.ResultXml = BuildNotifyResponse(false, "系统异常"); + Log.Error("MiniProgramPay", "ProcessNotify exception: " + ex.ToString()); + return result; + } + } + + /// + /// 验证支付结果(通过微信订单号) + /// 调用微信订单查询接口验证订单是否支付成功 + /// + /// 微信支付订单号 + /// true: 验证成功 false: 验证失败 + public bool VerifyPaymentResult(string transactionId) + { + try + { + if (string.IsNullOrEmpty(transactionId)) + { + return false; + } + + // 构建查询订单请求参数 + WxPayData req = new WxPayData(); + req.SetValue("transaction_id", transactionId); + // 调用订单查询接口 + WxPayData res = WxPayApi.OrderQuery(_config, req, _timeout); + + // 检查查询结果 + // return_code: 返回状态码,SUCCESS/FAIL + // result_code: 业务结果,SUCCESS/FAIL + // trade_state: 交易状态,SUCCESS—支付成功 + if (res.IsSet("return_code") && res.GetValue("return_code").ToString() == "SUCCESS" && + res.IsSet("result_code") && res.GetValue("result_code").ToString() == "SUCCESS" && + res.IsSet("trade_state") && res.GetValue("trade_state").ToString() == "SUCCESS") + { + Log.Info("MiniProgramPay", "VerifyPaymentResult success, transactionId: " + transactionId); + return true; + } + + Log.Error("MiniProgramPay", "VerifyPaymentResult failed, transactionId: " + transactionId + ", response: " + res.ToJson()); + return false; + } + catch (Exception ex) + { + Log.Error("MiniProgramPay", "VerifyPaymentResult exception: " + ex.ToString()); + return false; + } + } + + /// + /// 验证支付结果(通过商户订单号) + /// 调用微信订单查询接口验证订单是否支付成功 + /// + /// 商户订单号 + /// true: 验证成功 false: 验证失败 + public bool VerifyPaymentResultByOutTradeNo(string outTradeNo) + { + try + { + if (string.IsNullOrEmpty(outTradeNo)) + { + return false; + } + + // 构建查询订单请求参数 + WxPayData req = new WxPayData(); + req.SetValue("out_trade_no", outTradeNo); + // 调用订单查询接口 + WxPayData res = WxPayApi.OrderQuery(_config, req, _timeout); + + // 检查查询结果 + if (res.IsSet("return_code") && res.GetValue("return_code").ToString() == "SUCCESS" && + res.IsSet("result_code") && res.GetValue("result_code").ToString() == "SUCCESS" && + res.IsSet("trade_state") && res.GetValue("trade_state").ToString() == "SUCCESS") + { + Log.Info("MiniProgramPay", "VerifyPaymentResultByOutTradeNo success, outTradeNo: " + outTradeNo); + return true; + } + + Log.Error("MiniProgramPay", "VerifyPaymentResultByOutTradeNo failed, outTradeNo: " + outTradeNo + ", response: " + res.ToJson()); + return false; + } + catch (Exception ex) + { + Log.Error("MiniProgramPay", "VerifyPaymentResultByOutTradeNo exception: " + ex.ToString()); + return false; + } + } + + /// + /// 查询订单 + /// 通过微信订单号或商户订单号查询订单状态 + /// + /// 微信支付订单号,与outTradeNo二选一 + /// 商户订单号,与transactionId二选一 + /// WxPayData对象,包含订单详细信息 + public WxPayData QueryOrder(string transactionId = "", string outTradeNo = "") + { + try + { + WxPayData req = new WxPayData(); + // 优先使用微信订单号查询 + if (!string.IsNullOrEmpty(transactionId)) + { + req.SetValue("transaction_id", transactionId); + } + else if (!string.IsNullOrEmpty(outTradeNo)) + { + req.SetValue("out_trade_no", outTradeNo); + } + else + { + throw new WxPayException("transaction_id和out_trade_no至少填写一个"); + } + + // 调用订单查询接口 + WxPayData res = WxPayApi.OrderQuery(_config, req, _timeout); + return res; + } + catch (Exception ex) + { + Log.Error("MiniProgramPay", "QueryOrder exception: " + ex.ToString()); + throw; + } + } + + /// + /// 关闭订单 + /// 当订单长时间未支付时,可调用此接口关闭订单 + /// 注意:订单生成后不能马上调用关闭订单接口,最短调用时间间隔为5分钟 + /// + /// 商户订单号 + /// WxPayData对象,包含关闭结果 + public WxPayData CloseOrder(string outTradeNo) + { + try + { + if (string.IsNullOrEmpty(outTradeNo)) + { + throw new WxPayException("商户订单号不能为空"); + } + + // 构建关闭订单请求参数 + WxPayData req = new WxPayData(); + req.SetValue("out_trade_no", outTradeNo); + // 调用关闭订单接口 + WxPayData res = WxPayApi.CloseOrder(_config, req, _timeout); + return res; + } + catch (Exception ex) + { + Log.Error("MiniProgramPay", "CloseOrder exception: " + ex.ToString()); + throw; + } + } + + /// + /// 申请退款 + /// 当交易发生错误或交易失败时,商户可通过此接口退款 + /// 注意:退款需要商户证书 + /// + /// 商户订单号 + /// 商户退款单号,商户系统内部的退款单号,要求唯一 + /// 订单总金额,单位: 分 + /// 退款金额,单位: 分,不能大于订单总金额 + /// 操作员ID,可选,默认为商户号 + /// WxPayData对象,包含退款结果 + public WxPayData Refund(string outTradeNo, string outRefundNo, int totalFee, int refundFee, string opUserId = "") + { + try + { + // 参数校验 + if (string.IsNullOrEmpty(outTradeNo)) + { + throw new WxPayException("商户订单号不能为空"); + } + if (string.IsNullOrEmpty(outRefundNo)) + { + throw new WxPayException("商户退款单号不能为空"); + } + if (totalFee <= 0) + { + throw new WxPayException("订单总金额必须大于0"); + } + if (refundFee <= 0) + { + throw new WxPayException("退款金额必须大于0"); + } + if (refundFee > totalFee) + { + throw new WxPayException("退款金额不能大于订单总金额"); + } + + // 构建退款请求参数 + WxPayData req = new WxPayData(); + req.SetValue("out_trade_no", outTradeNo); // 商户订单号 + req.SetValue("out_refund_no", outRefundNo); // 商户退款单号 + req.SetValue("total_fee", totalFee); // 订单总金额,单位: 分 + req.SetValue("refund_fee", refundFee); // 退款金额,单位: 分 + // 操作员ID,默认为商户号 + if (string.IsNullOrEmpty(opUserId)) + { + req.SetValue("op_user_id", _config.webPARTNER); + } + else + { + req.SetValue("op_user_id", opUserId); + } + + // 调用退款接口 + WxPayData res = WxPayApi.Refund(_config, req, _timeout); + return res; + } + catch (Exception ex) + { + Log.Error("MiniProgramPay", "Refund exception: " + ex.ToString()); + throw; + } + } + + /// + /// 构建回调通知响应XML + /// 用于返回给微信支付后台的处理结果 + /// + /// 处理是否成功 + /// 消息内容 + /// XML格式的响应字符串 + public string BuildNotifyResponse(bool success, string message) + { + WxPayData res = new WxPayData(); + res.SetValue("return_code", success ? "SUCCESS" : "FAIL"); + res.SetValue("return_msg", message); + return res.ToXml(); + } + + /// + /// 生成商户订单号 + /// 格式: 商户号 + 时间戳 + 3位随机数 + /// + /// 配置对象 + /// 商户订单号字符串 + public static string GenerateOutTradeNo(Config config) + { + var ran = new Random(); + return string.Format("{0}{1}{2}", config.webPARTNER, DateTime.Now.ToString("yyyyMMddHHmmss"), ran.Next(999)); + } + + /// + /// 获取支付结果JSON字符串 + /// 用于返回给前端小程序的支付参数 + /// + /// 支付结果对象 + /// JSON格式的字符串 + public string GetPayResultJson(MiniProgramPayResult result) + { + if (result.Success) + { + // 成功时返回支付参数 + return JsonMapper.ToJson(new + { + status = 1, // 状态码,1表示成功 + msg = result.Message, // 消息 + timeStamp = result.TimeStamp, // 时间戳 + noncestr = result.NonceStr, // 随机字符串 + package = result.Package, // 订单详情 + paySign = result.PaySign, // 签名 + signType = result.SignType, // 签名类型 + prepayId = result.PrepayId, // 预支付ID + outTradeNo = result.OutTradeNo // 商户订单号 + }); + } + else + { + // 失败时返回错误信息 + return JsonMapper.ToJson(new + { + status = 0, // 状态码,0表示失败 + msg = result.Message, // 错误消息 + errCode = result.ErrCode, // 错误码 + errCodeDes = result.ErrCodeDes // 错误描述 + }); + } + } + } +} diff --git a/Mtxfw.VipSite/App_Data/Config.xml b/Mtxfw.VipSite/App_Data/Config.xml index c24088b..d9fc980 100644 --- a/Mtxfw.VipSite/App_Data/Config.xml +++ b/Mtxfw.VipSite/App_Data/Config.xml @@ -228,7 +228,7 @@ - + diff --git a/Mtxfw.VipSite/Properties/PublishProfiles/FolderProfile1.pubxml b/Mtxfw.VipSite/Properties/PublishProfiles/FolderProfile1.pubxml new file mode 100644 index 0000000..11924f2 --- /dev/null +++ b/Mtxfw.VipSite/Properties/PublishProfiles/FolderProfile1.pubxml @@ -0,0 +1,15 @@ + + + + + false + false + true + Release + Any CPU + FileSystem + D:\renyipublic + FileSystem + <_TargetId>Folder + + \ No newline at end of file diff --git a/Mtxfw.VipSite/left.aspx b/Mtxfw.VipSite/left.aspx index f01c389..5ab9686 100644 --- a/Mtxfw.VipSite/left.aspx +++ b/Mtxfw.VipSite/left.aspx @@ -329,10 +329,10 @@