Senparc.Weixin.TenPay 微信支付(APIv2)模块
Senparc.Weixin.TenPay 模块用于提供微信支付(V2)的支持能力。
注意: Senparc.Weixin.TenPay 中,命名空间 .V2
或 .V3
,只代表对应文档的 V2 或 V3 版本,对应服务器引擎 API 都是 APIv2,并非

微信全平台支持
微信公众号、小程序、企业微信、微信支付 V2 / V3、开放平台……

强大技术社区支持
线上讨论社区、图书、视频课程、各类文档

2012 开源至今
十年如一日持续更新 盛派出品值得信赖
源码
Senparc.Weixin SDK 源码
GitHub:https://github.com/JeffreySu/WeiXinMPSDK(更新更快)
Gitee:https://gitee.com/JeffreySu/WeiXinMPSDK (从 GitHub 同步)
打包代码
Nuget 包地址:https://www.nuget.org/packages/Senparc.Weixin.TenPay
本地源码文件
Senparc.Weixin.TenPay 源码位置:/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay
如何安装?
您可以直接引用 Senparc.Weixin 的源码进行开发,也可以引用已经打包完成的 dll(通过 Nuget 包,推荐),以方便随时获取官方的更新。注意:直接引用源码和引用 Nuget 包只能二选一。
引用程序集(推荐)
您可以通过 Visual Studio
、Visual Studio Code
、dotnet 命令行
等多种方式自动安装 Nuget 包。
在开发项目【解决方案资源管理器】中,对需要添加 Senparc.Weixin.Tenpay 的模块点击右键,点击【管理 Nuget 程序包】,在【浏览】标签中输入 Senparc.Weixin.Tenpay,点击右侧【安装】按钮。如下图所示:
首先,确认已经安装好 VS Code 以及 dotnet 命令行(安装 .NET SDK 后会自动安装)。
然后,打开解决方案或项目所在目录,按 Ctrl+~,打开终端面板:

进入需要添加 Senparc.Weixin.Tenpay 的模块的项目的目录,输入:
dotnet add package Senparc.Weixin.Tenpay
安装完成后,可查看对应 .csproj 文件,被添加引用,如:
<ItemGroup>
<PackageReference Include="Senparc.Weixin.Tenpay" Version="16.17.7" />
</ItemGroup>
首先,确认已经安装好 dotnet 命令行(安装 .NET SDK 后会自动安装)。
进入需要添加 Senparc.Weixin.Tenpay 的模块的项目的目录,输入:
dotnet add package Senparc.Weixin.Tenpay
安装完成后,可查看对应 .csproj 文件,被添加引用,如:
<ItemGroup>
<PackageReference Include="Senparc.Weixin.TenPay" Version="16.17.7" />
</ItemGroup>
如何使用?
全局注册
所有的 Senparc.Weixin SDK 注册过程都是类似的。
首先,完成所有 Senparc.Weixin SDK 的整体注册代码。在 Program.cs 中加入以下代码:
说明:
builder.Services.AddMemoryCache()
Senparc.Weixin 支持本机缓存、Redis、Memcached 等多种缓存策略,默认使用本机缓存,此时需要激活本地缓存。builder.Services.AddSenparcWeixinServices(builder.Configuration)
用于完成 Senparc.Weixin 的注册。app.UseSenparcWeixin()
方法用于配置和启用 Senparc.Weixin。
以上代码对于所有的 Senparc.Weixin 下级模块都是相同的,只需要 3 句代码。
本项目参考文件:
公众号注册
在上述代码中的第 27 行委托方法中插入代码,即可完成默认公众号以及微信支付的注册:
//注册公众号信息(可以执行多次,注册多个公众号)
register.RegisterMpAccount(weixinSetting, "【盛派网络小助手】公众号");
//注册微信支付(可以执行多次,注册多个微信支付)
register.RegisterTenpayOld(weixinSetting, "【盛派网络小助手】微信支付(V2)");
注意: 上述代码同时注册了公众号和微信支付(V2),因为微信支付需要提供微信用户的标识(OpenId),OpenId 必须来自微信公众号、小程序等模块。如果需要在小程序内使用微信支付,则需要对应注册小程序。
其中,weixinSetting
的值默认来自于 appsettings.json
:
"SenparcWeixinSetting": {
"IsDebug": true,
//公众号
"Token": "#{Token}#",
"EncodingAESKey": "#{EncodingAESKey}#",
"WeixinAppId": "#{WeixinAppId}#",
"WeixinAppSecret": "#{WeixinAppSecret}#",
//微信支付
//微信支付V3(旧版文档V3)
"TenPayV3_AppId": "#{TenPayV3_AppId}#",
"TenPayV3_AppSecret": "#{TenPayV3_AppSecret}#",
"TenPayV3_SubAppId": "#{TenPayV3_SubAppId}#",
"TenPayV3_SubAppSecret": "#{TenPayV3_SubAppSecret}#",
"TenPayV3_MchId": "#{TenPayV3_MchId}#",
"TenPayV3_SubMchId": "#{TenPayV3_SubMchId}#", //子商户,没有可留空
"TenPayV3_Key": "#{TenPayV3_Key}#",
/* 证书路径(APIv3 可不使用)
* 1、物理路径如:D:\\cert\\apiclient_cert.p12
* 2、相对路径,如:~/App_Data/cert/apiclient_cert.p12,注意:必须放在 App_Data 等受保护的目录下,避免泄露
* 备注:证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert
*/
"TenPayV3_CertPath": "#{TenPayV3_CertPath}#", //(V3 API 可不使用)证书路径
"TenPayV3_CertSecret": "#{TenPayV3_CertSecret}#", //(V3 API 可不使用)支付证书密码(原始密码和 MchId 相同)
"TenPayV3_TenpayNotify": "#{TenPayV3_TenpayNotify}#", //http://YourDomainName/TenpayV3/PayNotifyUrl
//如果不设置TenPayV3_WxOpenTenpayNotify,默认在 TenPayV3_TenpayNotify 的值最后加上 "WxOpen"
"TenPayV3_WxOpenTenpayNotify": "#{TenPayV3_WxOpenTenpayNotify}#" //http://YourDomainName/TenpayV3/PayNotifyUrlWxOpen
}
其中,Token
、EncodingAESKey
、WeixinAppId
和 WeixinAppSecret
对应了微信公众号后台的配置参数。
特别说明:TenPayV3_CertPath
参数同时支持完整的物理路径或相对路径(相对于程序根目录以 ~/
开头)。证书文件必须放在 App_Data 等受保护的目录下,避免泄露。
本项目参考文件:
配置完成。
提示:自动注册的信息可通过 Senparc.Weixin.Config.SenparcWeixinSetting
获取。
JSAPI 支付
在微信公众号网页中使用微信支付,必须借助 JSAPI,完成支付流程。
首先,需要创建一个商品页面,并在页面上提供一个下订单的入口,例如:一键购买。
(具体实现此处略)
当前示例中,提供了 3 个关键的页面:ProductList(商品列表)、ProductItem(商品详情) 和 JsApi(JSAPI 订单支付)。
ProductList 商品列表
后端
参考代码:TenPayV3Controller 下的 ProductList() 方法。
public ActionResult ProductList()
{
var products = ProductModel.GetFakeProductList();
return View(products);
}
本项目参考文件:
前端
本项目参考文件:
效果
ProductItem 商品详情
后端
参考代码:TenPayV3Controller 下的 ProductItem() 方法。
public ActionResult ProductItem(int productId, int hc)
{
var products = ProductModel.GetFakeProductList();
var product = products.FirstOrDefault(z => z.Id == productId);
if (product == null || product.GetHashCode() != hc)
{
return Content("商品信息不存在,或非法进入!2003");
}
//判断是否正在微信端
if (Senparc.Weixin.BrowserUtility.BrowserUtility.SideInWeixinBrowser(HttpContext))
{
//正在微信端,直接跳转到微信支付页面
return RedirectToAction("JsApi", new { productId = productId, hc = hc });
}
else
{
//在PC端打开,提供二维码扫描进行支付
return View(product);
}
}
说明:上述代码使用
SideInWeixinBrowser()
方法对当前页面的运行环境做了判断,如果是在微信内打开,则直接进入 JsApi 支付页面(见下放 JsApi 的介绍),如果在非微信内部打开(如 PC),则展示商品详情,并提供多种支付方式的选择。
本项目参考文件:
前端
本项目参考文件:
效果
从 商品列表 中选择一个商品点击,如果在 PC 端,则可以看到详情页面:
上述的支付方式二:“扫一扫”支付下方有一个根据所选商品自动生成的二维码,使用手机微信扫一扫,即可进入对应的商品订单页面(即 JsApi 订单页面)。
JSAPI 订单支付页面
后端
用户点击下单按钮后,需要在后台生成一个预支付订单并在页面上登记,代码请参考 TenPayV3Controller.JsApi()
[CustomOAuth(null, "/TenpayV3/OAuthCallback")]
public ActionResult JsApi(int productId, int hc)
{
try
{
//获取产品信息
//...
//调用 JsApi 获取预支付信息
//...
return View();
}
catch (Exception ex)
{
//...
}
}
本项目参考文件:
上述代码中,传入的 productId
参数是商品的编号,此处作为 Sample 演示,是从内存中模拟列表并查询,实际项目中,商品信息一般存储在数据库中,根据 productId
从数据中查找商品数据;hc
函数是为了确保当前内存信息的有效性而设置的对应商品信息的 HashCode,实际开发项目中无需使用,可忽略。
此过程中,最关键的代码是:var result = TenPayOldV3.Unifiedorder(xmlDataInfo)
,result.prepay_id
即“预支付ID”,前端页面必须凭借 prepay_id 才能让手机端唤起微信支付。此时,当前订单编号已经在微信支付后台进行了注册。
注意:该方法使用了 [CustomOAuth] 特性,用于自动使用微信公众号的 OAuth 功能识别用户身份,此功能属于公众号范畴,不在这里展开。
前端
前端的关键操作是当用户点击“支付”按钮后,执行 JS 代码:
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": "", //公众号名称,由商户传入
"timeStamp": "", //时间戳
"nonceStr": "", //随机串
"package": "",//扩展包
"signType": "MD5", //微信签名方式:MD5
"paySign": "" //微信签名
}, function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
//支付成功
}
});
本项目参考文件:
效果
支付回调
当用户在微信端完成支付后,微信服务器会主动推送一条通知到应用服务器。这条信息只会在微信官方服务器和应用服务器之间发生,不会有用户的参与,并且附加签名校验,因此才是可信的。
注意:千万不能信任手机客户端完成支付的状态,并以此通知应用服务器用户已完成支付!
以 JsApi 支付为例,当发起统一支付时,会要求填写回调地址(TenPayV3UnifiedorderRequestData
中的 notifyUrl
参数,见 JSAPI 支付 相关说明)。
注意:不同的支付方式提供回调地址的设置可能不同,如“Native 支付”,则是在微信支付的管理后台设置。
定义回调入口
/// <summary>
/// JS-SDK支付回调地址(在统一下单接口中设置notify_url)
/// </summary>
/// <returns></returns>
public ActionResult PayNotifyUrl()
{
try
{
ResponseHandler resHandler = new ResponseHandler(HttpContext);
string return_code = resHandler.GetParameter("return_code");
string return_msg = resHandler.GetParameter("return_msg");
string res = null;
resHandler.SetKey(TenPayV3Info.Key);
//验证请求是否从微信发过来(安全)
if (resHandler.IsTenpaySign() && return_code.ToUpper() == "SUCCESS")
{
res = "success";//正确的订单处理
//直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息!
}
else
{
res = "wrong";//错误的订单处理
}
/* 这里可以进行订单处理的逻辑 */
string xml = string.Format(@"<xml>
<return_code><![CDATA[{0}]]></return_code>
<return_msg><![CDATA[{1}]]></return_msg>
</xml>", return_code, return_msg);
return Content(xml, "text/xml");
}
catch (Exception ex)
{
WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
throw;
}
}
本项目参考文件:
Native 支付
Native 支付用于线下(或微信环境以外)的支付,通过微信扫描二维码,唤起个人微信支付完成支付过程。
生成二维码的控件很多,以 ZXing.Net 为例,在 TenPayV3Controller
中创建方法:
/// <summary>
/// 原生支付 模式一
/// </summary>
/// <returns></returns>
public ActionResult Native()
{
try
{
RequestHandler nativeHandler = new RequestHandler(null);
string timeStamp = TenPayV3Util.GetTimestamp();
string nonceStr = TenPayV3Util.GetNoncestr();
//商品Id,用户自行定义
string productId = SystemTime.Now.ToString("yyyyMMddHHmmss");
nativeHandler.SetParameter("appid", TenPayV3Info.AppId);
nativeHandler.SetParameter("mch_id", TenPayV3Info.MchId);
nativeHandler.SetParameter("time_stamp", timeStamp);
nativeHandler.SetParameter("nonce_str", nonceStr);
nativeHandler.SetParameter("product_id", productId);
string sign = nativeHandler.CreateMd5Sign("key", TenPayV3Info.Key);
var url = TenPayOldV3.NativePay(TenPayV3Info.AppId, timeStamp, TenPayV3Info.MchId, nonceStr, productId, sign);
BitMatrix bitMatrix;
bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 600, 600);
var bw = new ZXing.BarcodeWriterPixelData();
var pixelData = bw.Write(bitMatrix);
var bitmap = new System.Drawing.Bitmap(pixelData.Width, pixelData.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
var fileStream = new MemoryStream();
var bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, pixelData.Width, pixelData.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
try
{
// we assume that the row stride of the bitmap is aligned to 4 byte multiplied by the width of the image
System.Runtime.InteropServices.Marshal.Copy(pixelData.Pixels, 0, bitmapData.Scan0, pixelData.Pixels.Length);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
bitmap.Save(_fileStream, System.Drawing.Imaging.ImageFormat.Png);
_fileStream.Seek(0, SeekOrigin.Begin);
return File(_fileStream, "image/png");
}
catch (Exception ex)
{
SenparcTrace.SendCustomLog("TenPayV3.Native 执行出错", ex.Message);
SenparcTrace.BaseExceptionLog(ex);
throw;
}
}
本项目参考文件:
上述过程将自动生成对应于指定商户、指定商品(productId)的付款二维码,前端 HTML 调用方式如下:
<img src="/TenPayV3/Native" alt="扫码付款" />
用户扫码完成支付后,微信服务器会自动请求回调地址,如 /TenPayV3/NativeNotifyUrl,代码如下:
public ActionResult NativeNotifyUrl()
{
ResponseHandler resHandler = new ResponseHandler(null);
//返回给微信的请求
RequestHandler res = new RequestHandler(null);
string openId = resHandler.GetParameter("openid");
string productId = resHandler.GetParameter("product_id");
if (openId == null || productId == null)
{
res.SetParameter("return_code", "FAIL");
res.SetParameter("return_msg", "回调数据异常");
}
//创建支付应答对象
//RequestHandler packageReqHandler = new RequestHandler(null);
var sp_billno = SystemTime.Now.ToString("HHmmss") + TenPayV3Util.BuildRandomStr(26);//最多32位
var nonceStr = TenPayV3Util.GetNoncestr();
var xmlDataInfo = new TenPayV3UnifiedorderRequestData(TenPayV3Info.AppId, TenPayV3Info.MchId, "test", sp_billno, 1, HttpContext.UserHostAddress()?.ToString(), TenPayV3Info.TenPayV3Notify, TenPay.TenPayV3Type.JSAPI, openId, TenPayV3Info.Key, nonceStr);
try
{
//调用统一订单接口
var result = TenPayOldV3.Unifiedorder(xmlDataInfo);
//创建应答信息返回给微信
res.SetParameter("return_code", result.return_code);
res.SetParameter("return_msg", result.return_msg ?? "OK");
res.SetParameter("appid", result.appid);
res.SetParameter("mch_id", result.mch_id);
res.SetParameter("nonce_str", result.nonce_str);
res.SetParameter("prepay_id", result.prepay_id);
res.SetParameter("result_code", result.result_code);
res.SetParameter("err_code_des", "OK");
string nativeReqSign = res.CreateMd5Sign("key", TenPayV3Info.Key);
res.SetParameter("sign", nativeReqSign);
}
catch (Exception)
{
res.SetParameter("return_code", "FAIL");
res.SetParameter("return_msg", "统一下单失败");
}
return Content(res.ParseXML());
}
本项目参考文件:
提示:
Native 支付的回调地址设置位置位于:微信支付后台 > 产品中心 > 开发配置 > Native支付回调链接。
![]()
Native 支付回调链接设置
退款
退款方法核心代码如下:
/// <summary>
/// 退款申请接口
/// </summary>
/// <returns></returns>
public ActionResult Refund()
{
try
{
string nonceStr = TenPayV3Util.GetNoncestr();
string outTradeNo = HttpContext.Session.GetString("BillNo");
string outRefundNo = "OutRefunNo-" + SystemTime.Now.Ticks;
int totalFee = int.Parse(HttpContext.Session.GetString("BillFee"));
int refundFee = totalFee;
string opUserId = TenPayV3Info.MchId;
var notifyUrl = "https://sdk.weixin.senparc.com/TenPayV3/RefundNotifyUrl";
var dataInfo = new TenPayV3RefundRequestData(TenPayV3Info.AppId, TenPayV3Info.MchId, TenPayV3Info.Key,
null, nonceStr, null, outTradeNo, outRefundNo, totalFee, refundFee, opUserId, null, notifyUrl: notifyUrl);
var result = TenPayOldV3.Refund(_serviceProvider, dataInfo);//证书地址、密码,在配置文件中设置,并在注册微信支付信息时自动记录
ViewData["Message"] = $"退款结果:{result.result_code} {result.err_code_des}。您可以刷新当前页面查看最新结果。";
return View();
}
catch (Exception ex)
{
WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
throw;
}
}
本项目参考文件:
说明:上述代码为了方便演示,并限定在没有登录功能的情况下只能退款本人自己支付过的订单,因此将 BillNo(订单号)存在 Session 中,实际开发过程中可放入 URL 或 Post 参数中进行请求,并注意做好权限验证!
退款回调
在退款接口调用过程中,有一个 notifyUrl
的参数,此地址用于接收微信服务器发送的退款信息回调信息。代码如下:
/// <summary>
/// 退款通知地址
/// </summary>
/// <returns></returns>
public ActionResult RefundNotifyUrl()
{
string responseCode = "FAIL";
string responseMsg = "FAIL";
try
{
ResponseHandler resHandler = new ResponseHandler(HttpContext);
string return_code = resHandler.GetParameter("return_code");
string return_msg = resHandler.GetParameter("return_msg");
WeixinTrace.SendCustomLog("跟踪RefundNotifyUrl信息", resHandler.ParseXML());
if (return_code == "SUCCESS")
{
responseCode = "SUCCESS";
responseMsg = "OK";
string appId = resHandler.GetParameter("appid");
string mch_id = resHandler.GetParameter("mch_id");
string nonce_str = resHandler.GetParameter("nonce_str");
string req_info = resHandler.GetParameter("req_info");
if (!appId.Equals(Senparc.Weixin.Config.SenparcWeixinSetting.TenPayV3_AppId))
{
/*
* 注意:
* 这里添加过滤只是因为盛派Demo经常有其他公众号错误地设置了我们的地址,
* 导致无法正常解密,平常使用不需要过滤!
*/
SenparcTrace.SendCustomLog("RefundNotifyUrl 的 AppId 不正确",
$"appId:{appId}\r\nmch_id:{mch_id}\r\nreq_info:{req_info}");
return Content("faild");
}
var decodeReqInfo = TenPayV3Util.DecodeRefundReqInfo(req_info, TenPayV3Info.Key);//解密
var decodeDoc = XDocument.Parse(decodeReqInfo);
//获取接口中需要用到的信息
string transaction_id = decodeDoc.Root.Element("transaction_id").Value;
string out_trade_no = decodeDoc.Root.Element("out_trade_no").Value;
string refund_id = decodeDoc.Root.Element("refund_id").Value;
string out_refund_no = decodeDoc.Root.Element("out_refund_no").Value;
int total_fee = int.Parse(decodeDoc.Root.Element("total_fee").Value);
int? settlement_total_fee = decodeDoc.Root.Element("settlement_total_fee") != null
? int.Parse(decodeDoc.Root.Element("settlement_total_fee").Value)
: null as int?;
int refund_fee = int.Parse(decodeDoc.Root.Element("refund_fee").Value);
int tosettlement_refund_feetal_fee = int.Parse(decodeDoc.Root.Element("settlement_refund_fee").Value);
string refund_status = decodeDoc.Root.Element("refund_status").Value;
string success_time = decodeDoc.Root.Element("success_time").Value;
string refund_recv_accout = decodeDoc.Root.Element("refund_recv_accout").Value;
string refund_account = decodeDoc.Root.Element("refund_account").Value;
string refund_request_source = decodeDoc.Root.Element("refund_request_source").Value;
//验证通过,进行后续业务处理
}
}
catch (Exception ex)
{
responseMsg = ex.Message;
WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
}
string xml = string.Format(@"<xml>
<return_code><![CDATA[{0}]]></return_code>
<return_msg><![CDATA[{1}]]></return_msg>
</xml>", responseCode, responseMsg);
return Content(xml, "text/xml");
}
本项目参考文件:
进阶
当前示例展示了最基本和常用的功能,如果您需要了解更多高级玩法,并且有扎实的编程功底,请参考完整示例。
完整示例解决方案文件:
务必阅读 readme 文件:
关于
使用 Senparc.Weixin,您可以方便快速地开发微信全平台的应用(包括微信公众号、小程序、小游戏、企业号、开放平台、微信支付、JS-SDK、微信硬件/蓝牙,等等)。本项目的 Demo 同样适合初学者进行 .NET 编程学习。
目前 Senparc.Weixin 已经支持几乎所有微信平台模块和接口,并同时支持 .NET 3.5 / 4.0 / 4.5 / .NET Standard 2.x / .NET Core 2.x / .NET Core 3.x / .NET 6.0 多种框架。
Senparc.Weixin SDK 是目前使用率最高的微信 .NET SDK,也是国内最受欢迎的 .NET 开源项目之一,是唯一入选 2021“科创中国”开源创新榜[1] [2] 的 .NET 项目。
项目自 2012 年开源,2013 年 1 月起正式发布到 GitHub。10 年来,我们一直保持着项目的持续更新,并将完整的源代码以及设计思想毫无保留地分享给大家,希望有更多的人可以从中受益,理解并传播开源的精神,一同助力中国开源事业!感恩一路上给我们提供帮助的朋友们!
团队
Senparc.Weixin 由盛派网络及盛派开发者社区核心团队负责维护,同时正在得到大量来自社区成员和社会各界的支持,欢迎加入我们!
支持
Senparc.Weixin 提供 100% 源码、线上 Sample、文档、图书、视频课程、线上开发者平台、问答平台、QQ / 微信群,以及不定期的线上/线下分享会等各种形式的支持服务,并坚持不间断维护源码,发布新版本。
联系邮箱:zsu@senparc.com
开源协议
Senaprc.Weixin 使用 APACHE LICENSE V2.0 开源协议,支持商用。