Files
RenyiServer/Mtxfw.Utility/WXPay.MiniProgramPay.cs
2026-03-18 20:21:04 +08:00

727 lines
29 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Web;
using System.IO;
using System.Text;
using LitJson;
namespace Mtxfw.Utility
{
/// <summary>
/// 微信小程序支付结果类
/// 用于封装创建JSAPI支付订单后的返回结果
/// </summary>
public class MiniProgramPayResult
{
/// <summary>
/// 支付订单创建是否成功
/// true: 成功 false: 失败
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 结果消息描述
/// 成功时为"下单成功",失败时为具体错误信息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 时间戳
/// 自1970年1月1日以来的秒数用于小程序端调起支付
/// </summary>
public string TimeStamp { get; set; }
/// <summary>
/// 随机字符串
/// 32位以内的随机串用于签名计算
/// </summary>
public string NonceStr { get; set; }
/// <summary>
/// 订单详情扩展字符串
/// 格式: prepay_id=xxx小程序端调起支付时需要此参数
/// </summary>
public string Package { get; set; }
/// <summary>
/// 签名
/// 用于小程序端调起支付时的安全校验
/// </summary>
public string PaySign { get; set; }
/// <summary>
/// 签名类型
/// 固定值: MD5
/// </summary>
public string SignType { get; set; }
/// <summary>
/// 预支付交易会话标识
/// 微信返回的预支付ID有效期2小时
/// </summary>
public string PrepayId { get; set; }
/// <summary>
/// 商户订单号
/// 商户系统内部的订单号要求32个字符内唯一性
/// </summary>
public string OutTradeNo { get; set; }
/// <summary>
/// 微信支付订单号
/// 微信支付系统生成的订单号,查询订单时使用
/// </summary>
public string TransactionId { get; set; }
/// <summary>
/// 错误码
/// 支付失败时的错误代码,如: NOTENOUGH(余额不足)
/// </summary>
public string ErrCode { get; set; }
/// <summary>
/// 错误码描述
/// 支付失败时的错误详细信息
/// </summary>
public string ErrCodeDes { get; set; }
}
/// <summary>
/// 微信小程序支付回调通知结果类
/// 用于封装处理支付回调通知后的返回结果
/// </summary>
public class MiniProgramPayNotifyResult
{
/// <summary>
/// 支付回调处理是否成功
/// true: 成功 false: 失败
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 结果消息描述
/// </summary>
public string Message { get; set; }
/// <summary>
/// 商户订单号
/// 商户系统内部的订单号
/// </summary>
public string OutTradeNo { get; set; }
/// <summary>
/// 微信支付订单号
/// 微信支付系统生成的订单号
/// </summary>
public string TransactionId { get; set; }
/// <summary>
/// 订单总金额
/// 单位: 分,需要转换为元显示
/// </summary>
public int TotalFee { get; set; }
/// <summary>
/// 用户标识
/// 用户在商户appid下的唯一标识
/// </summary>
public string OpenId { get; set; }
/// <summary>
/// 支付完成时间
/// 格式: yyyyMMddHHmmss
/// </summary>
public string TimeEnd { get; set; }
/// <summary>
/// 银行类型
/// 银行编码,如: CMC(招商银行)
/// </summary>
public string BankType { get; set; }
/// <summary>
/// 返回给微信的XML响应
/// 用于告知微信支付后台处理结果
/// </summary>
public string ResultXml { get; set; }
}
/// <summary>
/// 微信小程序支付核心类
/// 提供创建JSAPI支付订单、处理支付回调、验证支付结果等核心功能
/// </summary>
public class MiniProgramPay
{
/// <summary>
/// 配置对象,包含微信支付相关配置信息
/// </summary>
private Config _config;
/// <summary>
/// 接口超时时间,单位: 秒默认6秒
/// </summary>
private int _timeout = 6;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="config">配置对象包含appid、商户号、密钥等信息</param>
public MiniProgramPay(Config config)
{
_config = config;
}
/// <summary>
/// 构造函数(带超时设置)
/// </summary>
/// <param name="config">配置对象</param>
/// <param name="timeout">接口超时时间,单位: 秒</param>
public MiniProgramPay(Config config, int timeout)
{
_config = config;
_timeout = timeout;
}
/// <summary>
/// 创建JSAPI支付订单
/// 调用微信统一下单接口,获取小程序调起支付所需的参数
/// </summary>
/// <param name="openid">用户标识用户在商户appid下的唯一标识必填</param>
/// <param name="outTradeNo">商户订单号商户系统内部订单号要求32个字符内必填</param>
/// <param name="totalFee">订单总金额,单位: 分,必填</param>
/// <param name="body">商品描述,如: "腾讯充值中心-QQ会员充值",必填</param>
/// <param name="attach">附加数据在查询API和支付通知中原样返回可选</param>
/// <param name="notifyUrl">支付结果通知回调地址,可选,不传则使用配置中的地址</param>
/// <returns>MiniProgramPayResult对象包含支付参数或错误信息</returns>
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;
}
}
/// <summary>
/// 处理支付回调通知
/// 接收微信支付后台发送的支付结果,验证签名并返回处理结果
/// </summary>
/// <param name="inputStream">HTTP请求输入流包含微信发送的XML数据</param>
/// <returns>MiniProgramPayNotifyResult对象包含支付结果信息</returns>
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;
}
}
/// <summary>
/// 验证支付结果(通过微信订单号)
/// 调用微信订单查询接口验证订单是否支付成功
/// </summary>
/// <param name="transactionId">微信支付订单号</param>
/// <returns>true: 验证成功 false: 验证失败</returns>
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;
}
}
/// <summary>
/// 验证支付结果(通过商户订单号)
/// 调用微信订单查询接口验证订单是否支付成功
/// </summary>
/// <param name="outTradeNo">商户订单号</param>
/// <returns>true: 验证成功 false: 验证失败</returns>
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;
}
}
/// <summary>
/// 查询订单
/// 通过微信订单号或商户订单号查询订单状态
/// </summary>
/// <param name="transactionId">微信支付订单号与outTradeNo二选一</param>
/// <param name="outTradeNo">商户订单号与transactionId二选一</param>
/// <returns>WxPayData对象包含订单详细信息</returns>
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;
}
}
/// <summary>
/// 关闭订单
/// 当订单长时间未支付时,可调用此接口关闭订单
/// 注意订单生成后不能马上调用关闭订单接口最短调用时间间隔为5分钟
/// </summary>
/// <param name="outTradeNo">商户订单号</param>
/// <returns>WxPayData对象包含关闭结果</returns>
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;
}
}
/// <summary>
/// 申请退款
/// 当交易发生错误或交易失败时,商户可通过此接口退款
/// 注意:退款需要商户证书
/// </summary>
/// <param name="outTradeNo">商户订单号</param>
/// <param name="outRefundNo">商户退款单号,商户系统内部的退款单号,要求唯一</param>
/// <param name="totalFee">订单总金额,单位: 分</param>
/// <param name="refundFee">退款金额,单位: 分,不能大于订单总金额</param>
/// <param name="opUserId">操作员ID可选默认为商户号</param>
/// <returns>WxPayData对象包含退款结果</returns>
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;
}
}
/// <summary>
/// 构建回调通知响应XML
/// 用于返回给微信支付后台的处理结果
/// </summary>
/// <param name="success">处理是否成功</param>
/// <param name="message">消息内容</param>
/// <returns>XML格式的响应字符串</returns>
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();
}
/// <summary>
/// 生成商户订单号
/// 格式: 商户号 + 时间戳 + 3位随机数
/// </summary>
/// <param name="config">配置对象</param>
/// <returns>商户订单号字符串</returns>
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));
}
/// <summary>
/// 获取支付结果JSON字符串
/// 用于返回给前端小程序的支付参数
/// </summary>
/// <param name="result">支付结果对象</param>
/// <returns>JSON格式的字符串</returns>
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 // 错误描述
});
}
}
}
}