
简介这是一套面向Python开发者的一站式微信生态开发工具包覆盖微信登录、公众号管理、微信支付与消息处理四大核心场景适用于快速构建微信小程序后端、企业公众号服务或H5应用的微信集成模块。资源共24个文件包含11个核心Python源码如mp.py、pay.py、msg.py等模块化实现、6个reStructuredText格式文档涵盖快速入门、各模块API说明及配置指南、3个文本说明文件及基础构建脚本bat/makefile/.gitignore整体仅34KB轻量易集成。已有141人学习下载适合中初级Python工程师在实际项目中快速接入微信能力。读者可直接复用模块化代码结构参考清晰的文档目录组织方式结合示例example/下mp.py、msg.py等理解参数初始化如WEIXIN_TOKEN、异常体系WeixinLoginError等子类及pip安装流程显著降低微信官方SDK对接门槛。1. 微信SDK不是“一个包”而是四类能力的集成枢纽支付、公众号、登录、消息处理必须分层接入很多人下载到微信SDK - 包括微信支付、微信公众号、微信登陆、微信消息处理等.zip后第一反应是解压、导入、调用WeChatSDK.init()—— 然后报错ClassNotFoundException或Missing appid。这不是 SDK 本身有问题而是混淆了微信生态的底层逻辑微信支付、公众号、登录、消息处理四者共用同一套签名与鉴权体系如 JSAPI 签名、OAuth2 scope、AES 加密但完全独立部署、独立配置、独立回调地址、独立证书体系。你无法用一个appid同时开通公众号网页授权和小程序支付也无法用公众号的mch_id去调用微信支付 v3 的https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi接口。本文面向已注册微信开放平台或公众号/商户平台的开发者聚焦如何基于官方 SDK 规范把这四类能力拆解为可验证、可调试、可灰度上线的最小单元。不讲概念复读只讲你在wechat-java-sdk或weixin-java-pay中真正要改哪几行代码、配哪几个环境变量、查哪几条日志才能让「用户点击支付按钮后弹出微信支付页」这件事稳定发生。2. 微信支付 v3 接口必须用私钥签名 平台证书验签绕过证书校验等于放弃安全底线微信支付 v3 接口如 JSAPI 支付、订单查询、退款强制要求使用 RSA 私钥签名请求体并用平台证书验证响应体。这是与 v2 版本最根本的区别——v2 只需 MD5 签名而 v3 要求双向 TLS证书链校验。很多团队在测试环境直接关闭 SSL 验证如 OkHttp 设置hostnameVerifier (hostname, session) - true导致上线后401 Unauthorized或422 Unprocessable Entity错误频发却查不到根源。2.1 获取并加载平台证书不是下载一次就完事而是每 24 小时自动轮换微信支付平台证书由微信侧定期更新有效期 30 天提前 7 天发布新证书必须通过GET https://api.mch.weixin.qq.com/v3/certificates接口获取。该接口本身也需要签名且返回的是加密的证书内容含encrypt_certificate.ciphertext字段需用你的 APIv3 密钥解密。# 第一步用你的 APIv3 密钥32位字符串解密 encrypt_certificate.ciphertext # 示例ciphertext U8...XQ, associated_data certificate, nonce a1b2c3... echo U8...XQ | base64 -d | openssl enc -aes-256-gcm -d \ -K 3233343536373839303132333435363738393031323334353637383930313233 \ -iv a1b2c3... \ -p -md sha256 -nosalt \ -aead certificate \ -out wechat_platform_cert.pem提示-K参数必须是十六进制格式的 APIv3 密钥非明文字符串。若你拿到的是明文abcdef1234567890...需先转为 hexecho -n abcdef1234567890... | xxd -p -c 256。否则解密失败证书内容为空。2.2 在 Java 项目中加载证书并初始化 HttpClient使用weixin-java-pay4.5.0 版本时必须显式传入WechatPayHttpClientBuilder构建的 client而非默认OkHttpClient// 加载平台证书PEM 格式 InputStream certStream getClass().getResourceAsStream(/cert/wechat_platform_cert.pem); X509Certificate certificate (X509Certificate) CertificateFactory .getInstance(X.509).generateCertificate(certStream); // 加载商户私钥PKCS#8 格式非 PKCS#1 InputStream keyStream getClass().getResourceAsStream(/cert/apiclient_key.pem); PrivateKey privateKey KeyFactory.getInstance(RSA) .generatePrivate(new PKCS8EncodedKeySpec(Files.readAllBytes(keyStream.toPath()))); // 构建带证书校验的 HttpClient CloseableHttpClient httpClient WechatPayHttpClientBuilder.create() .withMerchant(1900000109, MD5, privateKey) // 商户号、签名类型、私钥 .withWechatPay(certificate) // 平台证书 .build(); // 初始化支付服务 WxPayService payService new WxPayServiceImpl(); payService.setConfig(new WxPayConfig() {{ setAppId(wx1234567890abcdef); // 公众号/小程序 AppID setMchId(1900000109); setMchKey(your-api-v3-key-here); // 注意此处是 APIv3 密钥非 APIv2 密钥 setHttpClient(httpClient); }});关键参数说明参数作用常见错误setAppId()必须与调起 JSAPI 的公众号或小程序 AppID 一致误填商户号或开放平台 AppIDsetMchId()微信支付商户号10 位纯数字误填子商户号或服务商商户号未加sub_mch_idsetMchKey()APIv3 密钥32 位字符串用于解密平台证书和生成请求签名与 APIv2 的key混淆导致签名失败withWechatPay(certificate)强制校验响应体中的Wechatpay-Signature头缺失此步响应可能被中间人篡改而不自知2.3 JSAPI 支付预下单必须传openid且需确保其属于当前公众号JSAPI 支付要求payer.openid字段必须是用户在当前公众号下的 openid非 unionid。若用户通过扫码关注公众号后跳转到 H5 页面需先用snsapi_base静默授权获取 openid// 1. 生成授权 URLscopesnsapi_base无需用户确认 String authUrl https://open.weixin.qq.com/connect/oauth2/authorize? appidwx1234567890abcdef redirect_uri URLEncoder.encode(https://yourdomain.com/pay?order_id123, UTF-8) response_typecodescopesnsapi_basestate123#wechat_redirect; // 2. 用户跳转后后端用 code 换取 openid WxMpService mpService new WxMpServiceImpl(); mpService.setWxMpConfigStorage(new WxMpInMemoryConfigStorage() {{ setAppId(wx1234567890abcdef); setSecret(your-app-secret); }}); WxMpOAuth2AccessToken token mpService.getOAuth2Service().getAccessToken(code); String openid token.getOpenId(); // 此 openid 才可用于 JSAPI 支付 // 3. 调用预下单接口 WxPayUnifiedOrderRequest orderRequest new WxPayUnifiedOrderRequest(); orderRequest.setAppid(wx1234567890abcdef); orderRequest.setMchId(1900000109); orderRequest.setNonceStr(UUID.randomUUID().toString().replace(-, )); orderRequest.setBody(商品名称); orderRequest.setOutTradeNo(ORDER_ System.currentTimeMillis()); orderRequest.setTotalFee(100); // 单位分 orderRequest.setSpbillCreateIp(127.0.0.1); orderRequest.setNotifyUrl(https://yourdomain.com/pay/callback); orderRequest.setTradeType(JSAPI); orderRequest.setOpenid(openid); // 关键必须是当前公众号下的 openid WxPayUnifiedOrderResult result payService.unifiedOrder(orderRequest);注意WxPayUnifiedOrderRequest.setOpenid()是 JSAPI 支付唯一合法方式。若传入unionid或其他公众号的openid微信会返回{errcode:0,errmsg:ok,result_code:FAIL,err_code:INVALID_OPENID}但 HTTP 状态码仍是 200极易被忽略。3. 微信公众号消息处理必须区分明文/兼容/安全模式且 Token 验证失败会导致 404公众号后台配置服务器地址时微信会向你的 URL 发送 GET 请求进行 Token 验证含signature,timestamp,nonce,echostr四个参数。只有验证通过后续的文本、事件、图片等消息才会 POST 到该地址。但很多开发者卡在第一步明明代码写了验签逻辑微信却始终提示「配置失败请检查 URL 是否正确」。3.1 Token 验证的三要素排序、拼接、SHA1微信验签规则是将token、timestamp、nonce三个字符串按字典序升序排列拼接后做 SHA1 运算与signature参数比对。注意不是appid参与排序也不是echostr参与计算。GetMapping(/wechat/callback) public ResponseEntityString verifyToken( RequestParam String signature, RequestParam String timestamp, RequestParam String nonce, RequestParam String echostr) { // 1. 获取公众号后台配置的 Token必须与后台完全一致区分大小写 String token your_wechat_token_here; // 2. 按字典序排序并拼接 ListString params Arrays.asList(token, timestamp, nonce); Collections.sort(params); String plainText String.join(, params); // 3. 计算 SHA1 String computedSignature DigestUtils.sha1Hex(plainText); // 4. 比对并返回 echostr if (computedSignature.equals(signature)) { return ResponseEntity.ok(echostr); } else { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } }常见失败原因排查表现象原因解决方案微信提示「配置失败」token与公众号后台填写的不一致空格、大小写、中文标点复制后台 Token用System.out.println([ token ])查看实际值signature总是不匹配timestamp是秒级时间戳但部分框架自动转为毫秒显式用String.valueOf(System.currentTimeMillis() / 1000)生成测试值本地能验签线上失败Nginx/Apache 重写了 query string或 CDN 缓存了 GET 请求关闭 CDN 缓存检查反向代理是否 strip query 参数3.2 消息解密安全模式下必须用 AES-256-CBC 解密原始 XML当公众号启用「安全模式」时所有 POST 消息体是加密的Encrypt字段需用EncodingAESKey和AppID解密。EncodingAESKey是 43 位 Base64 字符串非 32 位解密后才是标准的 XML 消息。PostMapping(/wechat/callback) public ResponseEntityString handleEncryptedMessage( RequestBody String encryptedXml, RequestParam String msg_signature, RequestParam String timestamp, RequestParam String nonce) { // 从加密 XML 中提取 Encrypt 字段 String encrypt extractEncryptFromXml(encryptedXml); // 自定义解析方法 // 使用 EncodingAESKey 解密注意AES Key 是 43 位 Base64需补全为 32 字节 String encodingAesKey your_encoding_aes_key_here; // 后台配置的 43 位字符串 byte[] aesKey Base64.getDecoder().decode(encodingAesKey ); // 补等号 // AES-256-CBC 解密 Cipher cipher Cipher.getInstance(AES/CBC/NoPadding); SecretKeySpec keySpec new SecretKeySpec(aesKey, AES); IvParameterSpec iv new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); byte[] decrypted cipher.doFinal(Base64.getDecoder().decode(encrypt)); String xml new String(decrypted, StandardCharsets.UTF_8); // 去除 PKCS#7 填充 int padLen xml.charAt(xml.length() - 1); xml xml.substring(0, xml.length() - padLen); // 解析 XML示例文本消息 Document doc Jsoup.parse(xml, , Parser.xmlParser()); String fromUserName doc.select(FromUserName).text(); String content doc.select(Content).text(); // 业务逻辑自动回复 String replyXml buildTextReply(fromUserName, 收到 content); return ResponseEntity.ok(replyXml); }提示EncodingAESKey的 Base64 解码后长度应为 32 字节。若解码失败检查是否漏掉末尾的符号或是否用了 URL-safe Base64微信使用标准 Base64。4. 微信登录OAuth2 网页授权必须用 snsapi_userinfo 获取用户信息且 scope 不能混用微信网页授权登录分为两种 scopesnsapi_base静默授权仅得 openid和snsapi_userinfo需用户确认可得昵称、头像等。很多开发者误以为snsapi_base能获取用户信息导致前端调用wx.login()后后端拿不到nickname只能显示「未知用户」。4.1 完整授权流程redirect_uri 必须 URL Encode且域名已在公众号白名单// 1. 生成授权链接注意redirect_uri 必须是公众号后台配置的域名 String redirectUri https://yourdomain.com/auth/callback; String encodedRedirect URLEncoder.encode(redirectUri, UTF-8); String authUrl https://open.weixin.qq.com/connect/oauth2/authorize? appidwx1234567890abcdef redirect_uri encodedRedirect response_typecode scopesnsapi_userinfo // 关键此处必须是 snsapi_userinfo stateabc123#wechat_redirect; // 2. 用户跳转后用 code 换取 access_token 和用户信息 String accessTokenUrl https://api.weixin.qq.com/sns/oauth2/access_token? appidwx1234567890abcdef secretyour_app_secret code code grant_typeauthorization_code; // 调用后得到 JSON // { // access_token:ACCESS_TOKEN, // expires_in:7200, // refresh_token:REFRESH_TOKEN, // openid:OPENID, // scope:snsapi_userinfo // } // 3. 用 access_token openid 获取用户详细信息 String userInfoUrl https://api.weixin.qq.com/sns/userinfo? access_token accessToken openid openid langzh_CN; // 返回 // { // openid: OPENID, // nickname:张三, // sex:1, // province:广东, // city:深圳, // country:中国, // headimgurl:http://thirdwx.qlogo.cn/mmopen/..., // privilege:[], // unionid:o6_bmasdasdsad6_2sgVZhY4PzTJlBQ // }scope 选择决策树业务需求推荐 scope说明仅需识别用户如统计 PV/UVsnsapi_base不需用户确认静默获取 openid需展示用户昵称、头像如个人中心snsapi_userinfo必须用户点击「确认授权」且redirect_uri域名必须在公众号「网页授权域名」白名单中需跨公众号/小程序识别同一用户snsapi_userinfounionidunionid仅在用户关注了公众号或绑定过小程序时返回且需同一微信开放平台下注意snsapi_base换取的access_token不能用于调用sns/userinfo接口会返回{errcode:40003,errmsg:invalid openid}。必须用snsapi_userinfo流程获取的access_token。5. 微信消息处理与支付回调的幂等性设计用 Redis 时间窗口防重放而非简单数据库去重微信支付回调notify_url和公众号事件消息如subscribe,SCAN都存在重复推送风险。微信官方文档明确说明「为保证安全性微信会多次推送同一事件直到收到成功响应HTTP 200」。若你的支付回调逻辑未做幂等控制可能导致用户充值两次、优惠券发放两次等资损事故。5.1 基于 Redis 的分布式幂等锁5 分钟窗口 订单号维度PostMapping(/pay/callback) public ResponseEntityString handlePayCallback(RequestBody String xml) { try { // 1. 解析 XML 获取 out_trade_no 和 transaction_id Document doc Jsoup.parse(xml, , Parser.xmlParser()); String outTradeNo doc.select(out_trade_no).text(); String transactionId doc.select(transaction_id).text(); String resultCode doc.select(result_code).text(); // 2. 用 Redis SETNX 实现幂等key: pay_callback:${outTradeNo}, value: ${transactionId} String redisKey pay_callback: outTradeNo; String lockValue transactionId; // 设置过期时间 5 分钟防止死锁 Boolean isLocked redisTemplate.opsForValue() .setIfAbsent(redisKey, lockValue, Duration.ofMinutes(5)); if (!Boolean.TRUE.equals(isLocked)) { // 已存在说明是重复回调 String existingTxn redisTemplate.opsForValue().get(redisKey); log.warn(Duplicate callback for out_trade_no{}, existing_txn{}, outTradeNo, existingTxn); return ResponseEntity.ok(success); } // 3. 业务逻辑更新订单状态、发券、扣库存 if (SUCCESS.equals(resultCode)) { orderService.updateOrderStatus(outTradeNo, OrderStatus.PAID); couponService.sendCoupon(outTradeNo); inventoryService.deductStock(outTradeNo); } // 4. 返回 success 告诉微信停止重试 return ResponseEntity.ok(success); } catch (Exception e) { log.error(Error handling pay callback, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } }为什么不用数据库唯一索引数据库唯一索引如UNIQUE(out_trade_no)在高并发下仍可能因网络延迟导致两条请求同时通过唯一性校验race conditionRedisSETNX是原子操作且支持过期时间天然适合短时幂等场景out_trade_no是业务方生成的唯一订单号天然适合作为幂等 key无需额外字段。5.2 支付投诉回调的特殊处理必须校验投诉单号complaint_id和投诉时间complaint_time微信支付投诉回调https://api.mch.weixin.qq.com/v3/merchant/complaints/notify不同于普通支付回调它包含complaint_id、complaint_time、complaint_content等字段且投诉单号全局唯一。必须用complaint_id作为幂等 key而非out_trade_no因为同一笔订单可能被多次投诉。PostMapping(/complaint/notify) public ResponseEntityString handleComplaintNotify(RequestBody String json) { JSONObject payload new JSONObject(json); String complaintId payload.getString(complaint_id); String complaintTime payload.getString(complaint_time); // ISO8601 格式 // 用 complaint_id 做幂等 String redisKey complaint_notify: complaintId; Boolean isProcessed redisTemplate.opsForValue() .setIfAbsent(redisKey, 1, Duration.ofHours(24)); if (!Boolean.TRUE.equals(isProcessed)) { log.info(Skip duplicate complaint notify: {}, complaintId); return ResponseEntity.ok(); } // 解析投诉内容并触发客服工单 String content payload.getString(complaint_content); String merchantOrderId payload.getJSONObject(order_info).getString(out_trade_no); ticketService.createTicket(complaintId, merchantOrderId, content); return ResponseEntity.ok(); }关键点投诉回调的complaint_id是微信侧生成的全局唯一 ID长度固定为 28 位如COMP20230901123456789012345678可直接用作 Redis key无需哈希。6. 微信公众号配置分享样式用 JS-SDK 的 updateAppMessageShareData 动态设置朋友圈标题与缩略图微信公众号文章分享到朋友圈时默认显示标题为title标签内容缩略图为第一个img。但运营常需根据用户行为动态修改分享文案如「邀请好友得红包」、「限时折扣」这就必须使用微信 JS-SDK 的updateAppMessageShareData方法而非静态 HTML 配置。6.1 JS-SDK 签名生成必须用当前页面 URL含 query string且不能带 hashJS-SDK 的config接口需要jsapi_ticket和当前页面 URL 生成签名。URL 必须与浏览器地址栏完全一致包括 query string但必须去掉#及之后的内容。例如页面 URL 是https://yourdomain.com/article?id123#section2则签名用的 URL 是https://yourdomain.com/article?id123。// 1. 后端提供签名接口/wechat/jssdk-sign?url... // 返回 { appId, timestamp, nonceStr, signature } // 2. 前端调用 wx.config wx.config({ debug: false, appId: wx1234567890abcdef, timestamp: 1693526400, nonceStr: a1b2c3d4e5f67890, signature: a1b2c3...x9y0, jsApiList: [updateAppMessageShareData, updateTimelineShareData] }); // 3. 页面加载后动态设置分享数据 wx.ready(function() { // 分享给朋友 wx.updateAppMessageShareData({ title: 【限时】邀请好友得 10 元红包, desc: 点击领取无门槛使用, link: window.location.href.split(#)[0], // 去掉 hash imgUrl: https://yourdomain.com/share-icon.png }); // 分享到朋友圈 wx.updateTimelineShareData({ title: 我领到了 10 元红包你也来试试, link: window.location.href.split(#)[0], imgUrl: https://yourdomain.com/share-banner.jpg }); });6.2 服务端签名生成逻辑JavaGetMapping(/wechat/jssdk-sign) public ResponseEntityMapString, Object generateJsSdkSign(RequestParam String url) { // 1. 获取 jsapi_ticket需缓存有效期 2 小时 String jsapiTicket getJsapiTicket(); // 从 Redis 或本地缓存获取 // 2. 构造签名字符串jsapi_ticket、nonceStr、timestamp、url String nonceStr UUID.randomUUID().toString().replace(-, ); long timestamp System.currentTimeMillis() / 1000; String plainText jsapi_ticket jsapiTicket noncestr nonceStr timestamp timestamp url url; // 3. SHA1 签名 String signature DigestUtils.sha1Hex(plainText); MapString, Object result new HashMap(); result.put(appId, wx1234567890abcdef); result.put(timestamp, timestamp); result.put(nonceStr, nonceStr); result.put(signature, signature); return ResponseEntity.ok(result); } private String getJsapiTicket() { String cacheKey wechat_jsapi_ticket; String ticket redisTemplate.opsForValue().get(cacheKey); if (ticket ! null) return ticket; // 调用微信接口获取需 access_token String accessToken getAccessToken(); String ticketUrl https://api.weixin.qq.com/cgi-bin/ticket/getticket? access_token accessToken typejsapi; // ... 调用 HTTP Client 获取 JSON {errcode:0,errmsg:ok,ticket:xxx,expires_in:7200} // 缓存 1.5 小时避免到期前失效 redisTemplate.opsForValue().set(cacheKey, ticket, Duration.ofMinutes(90)); return ticket; }常见问题排查现象原因解决方案config:invalid signatureURL 与实际页面 URL 不一致如少了 query string 或多了 hash在浏览器控制台执行location.href.split(#)[0]复制结果传给后端config:invalid url domain当前域名未在公众号「JS 接口安全域名」中配置登录公众号后台 → 「公众号设置」→ 「功能设置」→ 「JS 接口安全域名」添加域名不带 http分享后标题/图片不生效wx.ready未触发或updateAppMessageShareData调用时机过早确保 DOM 加载完成后再调用或用document.addEventListener(DOMContentLoaded, ...)包裹微信 SDK 的本质不是一堆工具类而是四套独立协议的组合支付是金融级 HTTPS证书体系公众号是 OAuth2XML 消息流登录是授权码模式消息是加解密管道。把它们当成一个 ZIP 包去解压只会陷入依赖冲突和配置迷宫按协议边界切分、按能力单元验证才是落地的正路。本文还有配套的精品资源点击获取