727 lines
29 KiB
C#
727 lines
29 KiB
C#
|
|
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 // 错误描述
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|