简介面向C#开发者的微信支付集成方案代码DEMO包专注实现微信企业支付到个人、企业付款到零钱两大高频场景同时覆盖JSAPI支付、刷卡支付与扫码支付流程。包内包含93个文件核心由47个cs源代码、13个aspx页面、8个dll动态库以及5个config配置文件构成完整封装CS_sdk_v3.0.9调用逻辑清晰展现商户订单信息设置、签名验证、支付请求发起与结果接收等关键环节。资源约2.13MB目录结构紧凑便于按模块对照学习。订单退款、订单查询、退款查询及账单下载接口均有对应实现可帮助开发者快速理解微信支付API的调用链排查签名与参数配置问题缩短对接联调周期。已有1574人学习下载适合刚接手微信支付对接、需要参考可运行代码的C#工程师。1. 微信企业付款到零钱从商户系统到用户钱包的最后一公里做支付相关开发的同行应该都遇到过这种需求平台要给用户返现、发红包、结算佣金钱不能走人工转账必须由系统自动从商户号打到用户微信零钱。微信把这个能力封装成了「企业付款到零钱」接口C# 服务端可以直接调用。这篇笔记要拆的 DEMO 就是干这件事的包含完整的转账请求、证书加载、结果处理代码跑通后你就能在自己的系统里给用户零钱打款。先说结论这个接口比普通 JSAPI 支付简单因为不需要用户确认商户后台直接扣款到对方零钱实时到账。但它的门槛在「双向证书」和「商户平台配置」代码本身不算复杂。适合已经在用微信支付、需要做自动打款场景的 .NET 开发者新手照着配环境也能跑通。2. 转账前的准备工作开通产品、配置证书、搞清余额2.1 企业付款到零钱的接口形态与适用边界微信企业付款到零钱在接口文档里的正式名称是「企业付款」V2 版本的请求地址是https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers。它解决的是「商户号余额 → 用户微信零钱」的单向转账用户不需要操作钱直接进零钱余额。这个接口有几个硬性约束先讲清楚再动手单笔转账金额最低 1 元最高单笔 20000 元部分行业有特殊限额以商户号实际配置为准。商户号余额必须充足扣的是「可用余额」不是「冻结金额」。需要安装 API 证书apiclient_cert.p12请求时必须用证书做双向 TLS 认证没有证书直接报CERTIFICATE_ERROR。用户微信号必须已关注对应公众号未必但用户的微信账号必须是已实名且正常状态。不支持给已注销微信支付的用户转账。这接口和「企业红包」的区别在于红包有金额上限一般单笔 200 元且需要用户打开才能领取企业付款到零钱是直接打入零钱无感且适合做结算。如果你的场景是退款、佣金结算、返现选企业付款到零钱更合适。2.2 商户平台配置产品开通、API 密钥、IP 白名单在写代码之前先登录微信商户平台pay.weixin.qq.com完成三件事缺一不可开通产品在「产品中心」里找到「企业付款到零钱」点击开通。这里会让你选择付款方式一般选「API 接口付款」。开通需要 13 个工作日审核所以别等上线前一天才申请。设置 API 密钥在「账户中心 → API 安全」里设置 APIv3 密钥32 位字母数字组合。这个密钥用于生成签名泄露了别人就能冒充你的商户号发起转账务必妥善保管。配置 IP 白名单「API 安全」里可以设置允许调用接口的服务器 IP。如果服务器 IP 会变比如本地调试可以临时加白但正式环境建议固定出口 IP。另外还需要下载 API 证书。在「API 安全 → API 证书」里申请新证书下载得到apiclient_cert.p12文件这个文件的密码默认是商户号。有的开发者会顺便下载apiclient_key.pem和apiclient_cert.pemV2 接口用 p12 就够了但如果你用的是一些 SDK 库可能需要 p12 转 pem 后使用。2.3 本地开发环境证书格式转换与 .NET 项目准备C# 直接加载 p12 其实很简单用X509Certificate2类即可。不过有两点注意.NET Framework 和 .NET Core / .NET 5 在加载 p12 时行为略有差异尤其是私钥权限。.NET Core 下必须指定X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable否则后续用私钥签名时可能报Keyset does not exist。p12 文件建议放在项目目录下设置「复制到输出目录 始终复制」避免部署时遗漏。另外如果是从微信商户平台下载的 p12 文件它的密码是商户号mch_id不是你在「API 安全」里设置的 APIv3 密钥。这个非常容易搞混我在第一次对接时就是在这里卡了半天。# 如果后续需要 p12 转 pem可以用 openssl 命令行linux/mac 自带windows 需安装 # 注意转换时需要输入 p12 的密码商户号 openssl pkcs12 -in apiclient_cert.p12 -clcerts -nokeys -out apiclient_cert.pem openssl pkcs12 -in apiclient_cert.p12 -nocerts -nodes -out apiclient_key.pem这段命令的逻辑第一条导出证书公钥部分第二条导出私钥部分。微信的 V2 接口文档里要求的证书其实就是这对 pem 文件但 C# 里直接加载 p12 更方便所以这条命令不是必须的只有当你要用非 .NET 环境比如 PHP、Python调试时才用得上。3. C# 核心实现参数组装、签名、证书请求与结果解析3.1 请求参数与签名规则企业付款到零钱的请求参数是 XML 格式核心字段如下参数名说明必填mch_appid商户号绑定的公众号 appid是mchid商户号是device_info设备号可空否nonce_str随机字符串不长于 32 位是partner_trade_no商户订单号需唯一最长 32 字符是openid用户 openid需在 appid 下是check_name校验用户姓名选项NO_CHECK / OPTION_CHECK / FORCE_CHECK是re_user_name收款用户姓名check_name 不为 NO_CHECK 时必填按需amount转账金额单位分是desc转账说明必填最长 99 字符是spbill_create_ip调用接口的服务器 IP是签名算法和微信支付 V2 一致把请求参数除 sign 外按字典序排列拼接键值对末尾拼接keyAPIv3密钥然后做 MD5转大写作为 sign 字段放入 XML。这里注意amount 单位是「分」比如转账 100 元amount 传 10000。这个坑几乎每个新手都会踩我在测试环境转过一次「10000 元」还好金额填的是 1 元否则就真打出去了。3.2 C# 代码Demo 主流程与签名逻辑下面直接给出关键代码。这个 DEMO 是我按实际项目经验整理出来的你拿到后改几个参数就能跑通。using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Xml; public class WeChatTransferDemo { private readonly string _mchId 你的商户号; private readonly string _appId 你的APPID; private readonly string _apiKey 你的APIv3密钥; // 注意这是密钥不是p12密码 private readonly string _p12Path AppDomain.CurrentDomain.BaseDirectory apiclient_cert.p12; // 企业付款到零钱 public string Transfer(string openId, string tradeNo, int amountFen, string desc, string ip) { var dict new SortedDictionarystring, string { { mch_appid, _appId }, { mchid, _mchId }, { nonce_str, Guid.NewGuid().ToString().Replace(-, ).Substring(0, 32) }, { partner_trade_no, tradeNo }, { openid, openId }, { check_name, NO_CHECK }, // 不校验姓名一般返佣场景用这个 { amount, amountFen.ToString() }, { desc, desc }, { spbill_create_ip, ip } }; dict[sign] GenerateSign(dict, _apiKey); string xml BuildXml(dict); string response PostWithCert(xml, https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers); return response; } // MD5签名参数按字典序拼接后加key再做MD5 private string GenerateSign(SortedDictionarystring, string dict, string key) { string rawStr string.Join(, dict.Select(x ${x.Key}{x.Value})) $key{key}; using (var md5 MD5.Create()) { byte[] bytes md5.ComputeHash(Encoding.UTF8.GetBytes(rawStr)); var sb new StringBuilder(); foreach (var b in bytes) sb.Append(b.ToString(x2)); return sb.ToString().ToUpper(); } } private string BuildXml(SortedDictionarystring, string dict) { var sb new StringBuilder(xml); foreach (var kv in dict) sb.Append(${kv.Key}{kv.Value}/{kv.Key}); sb.Append(/xml); return sb.ToString(); } }这段代码的逻辑说明SortedDictionary是字典序排列的第一步GenerateSign里再把所有keyvalue用拼接末尾加keyAPI密钥MD5 后转大写。BuildXml直接拼 XML微信 V2 接口就是这么简单粗暴不需要额外的 XML 序列化库。参数说明check_name这里用了NO_CHECK意思是不校验收款人姓名只需 openid 即可。如果你担心打错人可以设为OPTION_CHECK并传入re_user_name微信会校验实名是否匹配不匹配则转账失败。3.3 携带证书发起 HTTPS 请求HttpWebRequest 实践上面代码里有个PostWithCert方法没展开这是整个 DEMO 最关键的部分。微信企业付款强制要求「双向 TLS 认证」也就是服务器也要校验客户端证书。用 C# 的HttpWebRequest加ClientCertificates即可private string PostWithCert(string xml, string url) { // 加载p12证书注意证书密码是商户号不是API密钥 var cert new X509Certificate2(_p12Path, _mchId, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); var request (HttpWebRequest)WebRequest.Create(url); request.Method POST; request.ContentType text/xml; request.ClientCertificates.Add(cert); request.Timeout 15000; // 15秒超时 byte[] body Encoding.UTF8.GetBytes(xml); request.ContentLength body.Length; using (var stream request.GetRequestStream()) { stream.Write(body, 0, body.Length); } using (var response (HttpWebResponse)request.GetResponse()) using (var reader new StreamReader(response.GetResponseStream(), Encoding.UTF8)) { return reader.ReadToEnd(); } }这段代码注意几点X509Certificate2构造函数的第二个参数是 p12 的密码这里必须是商户号mchid不是你在商户平台设置的 APIv3 密钥。X509KeyStorageFlags.MachineKeySet是为了避免 IIS 下访问私钥报权限错Exportable允许导出私钥某些 .NET Core 环境需要这个标记。Timeout设置 15 秒微信接口在高峰时段可能响应慢但转账请求建议短超时防止重复提交。如果超时了需要调用查询接口确认单子是否实际成功后面会讲。3.4 响应数据解析与业务状态判断转账接口的响应是 XML核心返回字段字段说明return_codeSUCCESS / FAIL通信层状态return_msg返回信息如签名失败、参数格式错误result_codeSUCCESS / FAIL业务层状态partner_trade_no商户订单号payment_no微信转账订单号payment_time转账时间格式 yyyy-MM-dd HH:mm:ss有个容易忽略的点return_code为 SUCCESS 只代表请求成功不代表转账成功必须看result_code。如果result_code为 FAILerr_code会给出具体错误码比如AMOUNT_LIMIT、NAME_MISMATCH、OPENID_ERROR。private (bool success, string msg) ParseResponse(string xml) { var doc new XmlDocument(); doc.LoadXml(xml); var root doc.DocumentElement; string returnCode root.SelectSingleNode(return_code)?.InnerText; string resultCode root.SelectSingleNode(result_code)?.InnerText; string errCode root.SelectSingleNode(err_code)?.InnerText; string errCodeDes root.SelectSingleNode(err_code_des)?.InnerText; string paymentNo root.SelectSingleNode(payment_no)?.InnerText; if (returnCode SUCCESS resultCode SUCCESS) { return (true, $转账成功微信单号{paymentNo}); } string msg errCodeDes ?? root.SelectSingleNode(return_msg)?.InnerText; return (false, $错误码{errCode ?? 无}原因{msg}); }解析逻辑简单但业务上要注意转账是资金操作返回失败时不要直接丢弃记录要把响应原文存到数据库方便排查和对账。我在实操中会把请求 XML 和响应 XML 都存进日志表出了纠纷一眼就能定位。4. 回调与查单确保转账幂等性的两个必要动作4.1 为什么不建议只靠同步响应企业付款到零钱这个接口没有主动回调通知和微信支付JSAPI/Native的支付回调是两码事。它不向你的服务器推送转账结果所以你需要主动查单来确认最终状态。有的开发者只依赖同步响应这是有隐患的网络超时、服务器重启、请求在半路被网关吃掉这几种情况下你拿不到响应但可能钱已经打出去了。同步响应没收到时只能靠查单接口兜底。4.2 查单接口企业付款查询查询接口地址是https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo请求参数比转账简单mchid、appid、partner_trade_no、nonce_str、sign。同样需要证书。public string QueryTransfer(string tradeNo) { var dict new SortedDictionarystring, string { { mch_appid, _appId }, { mchid, _mchId }, { partner_trade_no, tradeNo }, { nonce_str, Guid.NewGuid().ToString().Replace(-, ).Substring(0, 32) } }; dict[sign] GenerateSign(dict, _apiKey); string xml BuildXml(dict); // 复用 PostWithCert只是换 URL return PostWithCert(xml, https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo); }查单接口的响应里有两个重要字段status取值有PROCESSING处理中、SUCCESS成功、FAILED失败以及reason失败时的原因描述。转账发起后一般 35 秒就能查到明确状态但极端情况下会停留在 PROCESSING 几分钟。此时轮询查单间隔建议 510 秒一次最多查 5 次如果最后还是 PROCESSING建议人工介入或走退款流程。4.3 用商户订单号保证幂等幂等是这个场景最容易被人忽略的点。一次转账请求因网络原因被微信接收但你超时没收到响应你重试一次结果转了两次。避免方式每个partner_trade_no只允许对应一笔转账微信对相同单号的重复请求会直接返回失败或者返回原始结果。所以业务代码里发转账前先去数据库查一下这个订单号是否已经处理过。判断逻辑如下-- 转账记录表partner_trade_no 建立唯一索引 CREATE TABLE wechat_transfer_log ( id INT PRIMARY KEY IDENTITY(1,1), partner_trade_no VARCHAR(32) NOT NULL UNIQUE, openid VARCHAR(64) NOT NULL, amount_fen INT NOT NULL, status VARCHAR(16) NOT NULL, -- PENDING / SUCCESS / FAILED response_xml TEXT NULL, created_time DATETIME DEFAULT GETDATE() );发起转账前执行查询若状态为 SUCCESS直接返回成功若为 PENDING说明之前发起过但没确认结果此时转查询接口轮询只有不存在该单号时才真正发起转账。// 伪代码逻辑 if (transferLogRepo.Exists(partnerTradeNo)) { var r QueryTransfer(partnerTradeNo); // 根据查询结果更新本地状态 } else { var resp Transfer(openId, partnerTradeNo, amount, desc, ip); // 解析resp存库 }这套流程看起来多了一步查库但能避免绝大多数资金重复打款事故。做支付系统的都知道钱一旦多打出去追回流程非常痛苦。5. 避坑排查企业付款到零钱最常见的五个翻车场景5.1 报错CERTIFICATE_ERROR证书加载失败或类型错误现象请求返回return_codeFAILreturn_msgCERTIFICATE_ERROR。原因p12 文件路径不对、密码错误、证书没有正确附加到请求上。我遇到过的具体情况是把 APIv3 密钥当证书密码用了。解决确认加载证书的密码是商户号mch_id不是 API 密钥。另外IIS/Windows 服务环境建议显式加X509KeyStorageFlags.MachineKeySet否则私钥读取权限可能受限。5.2NO_AUTH或NOT_ENOUGH产品未开通或余额不足现象报错NO_AUTH商户无此接口权限或NOT_ENOUGH余额不足。原因前者是商户平台没开通企业付款产品或者开通了但接口权限未生效后者就是余额不够或者余额被冻结无法用作付款。解决去商户平台「产品中心」确认开通状态余额问题去「账户中心 → 账户余额」查看可用余额。特别注意分账冻结、退款冻结的资金不能用于企业付款。5.3AMOUNT_LIMIT金额超出限制现象单笔超过 2 万或者低于 1 元时报此错误此外还有「单日累计限额」的概念。原因微信对单笔、单日有阶梯限额不同商户额度不一样。解决查看商户平台「产品中心 → 企业付款到零钱」里的限额说明如果业务需要大额转账联系微信支付运营调整限额。我在电商平台的佣金结算场景里被日限额卡过一次后来改成分批打款才解决。5.4OPENID_ERROR或NAME_MISMATCH收款人信息不匹配现象openid 不存在或与商户号的绑定公众号不匹配姓名校验时不一致。原因openid 必须在mch_appid对应的公众号下获取如果用户是通过小程序登录拿到的 openid那和公众号 openid 不是同一个值。另外check_name 不为 NO_CHECK 时收款人实名姓名必须和输入一致。解决确认 appid 的来源。如果是小程序用户请在小程序后台绑定同一个商户号后用小程序的 openid 走接口或者要求用户授权手机号换取公众号 openid具体看产品设计。5.5 同步响应超时但实际转账已成功现象请求抛出WebException超时本地查了流水没有记录用户却反馈零钱到账了。原因响应在网络传输中丢失或者微信处理成功但响应延迟超过本地超时时间。解决超时后不要立即标记失败先查库有没有该单号的记录没有就调查询接口确认。我习惯把超时统一当作 UNKNOWN 状态用定时任务拉取未确认单去查状态。提示以上五个坑覆盖了 95% 的企业付款对接问题。如果遇到SYSTEMERROR一般是微信侧临时故障等几秒后重试即可不要频繁重复提交。6. 进阶用法设计与验证一个可靠的转账服务6.1 从 Demo 到生产三层结构Demo 里所有代码都堆在一起生产环境必须拆成三层接口层接收业务方请求校验参数生成partner_trade_no。服务层处理幂等调用微信 API解析响应更新数据库状态。对账层定时任务检查本地“PENDING”状态的记录调用查询接口补全状态并输出对账报表。我对每个层级的建议接口层参数校验放在第一位金额必须为正整数且小于等于配置上限服务层要加锁数据库唯一索引 分布式锁双保险防止并发请求导致重复转账对账层每 10 分钟跑一次把长时间停留在 PENDING 的记录单独列表告警。6.2 验证你的转账服务Demo 跑通后别急着接真实用户先在测试环境走一遍全流程开通测试商户号微信商户平台有测试环境沙箱申请一个测试商户号充值虚拟余额。造一个测试用户用自己的微信小号在公众号里完成 OAuth 授权拿到 openid。验证转账、查单、异常路径给测试用户转 1 分钱不可行最低 1 元。所以最小测试金额是 1 元。分别验证正常转账成功、重复单号幂等、余额不足报错、openid 错误报错。检查数据库记录确认每条转账都有请求和响应的原始 XML便于追溯。以下是我在测试环境常用的一套验证脚本思路# 转账1元测试成功 curl -k -X POST https://your-dev-server/api/transfer/test/1 # 重复提交相同订单号应返回已处理 curl -k -X POST https://your-dev-server/api/transfer/test/1 # 查询单号状态 curl -k -X GET https://your-dev-server/api/transfer/query?tradeNoTEST20250101001 # 预期输出第一笔返回成功单号第二笔返回该单号已存在第三笔返回状态 SUCCESS这套验证做完基本可以确认服务在真实环境下不会出大的资金事故。6.3 最有用的一个习惯所有转账请求强制写日志有一次线上环境用户返现没到账查了半天发现是服务器时间偏差导致签名不对。从那以后我每次对接微信支付类接口都强制把请求 XML、响应 XML、服务器时间戳、证书序列号记录到日志表出了任何问题都能复盘。建议你拿到这个 Demo 后先不改逻辑完整跑一遍再按上面三层结构重构。微信支付的文档一贯写得散网上能找到的 C# 案例也少这份 DEMO 至少能帮你省掉两三天的试错时间。项目里有完整的PostWithCert封装copy 过去替换参数就能用证书加载、签名、查单都是现成的直接在这个骨架上改你的业务字段就好。希望帮到你。本文还有配套的精品资源点击获取