【程序源代码】微信商户收款功能完整技术实现

关键字:Java SpringBoot 微信支付V3
主题:微信商户收款功能完整技术实现
1.1 文档说明
本文档为微信小程序接入微信商户收款功能的全流程技术文档,覆盖资质准备、平台配置、开发环境搭建、前后端完整实现、支付流程、异常处理、上线校验全节点,所有技术细节、核心参数、代码逻辑、避坑要点均详细拆解,可直接用于企业/个体工商户小程序收款功能开发,适配微信支付V3版本(官方推荐,V2版本已逐步停用)。
适用场景:小程序内商品购买、服务付费、会员充值、订单支付等各类合规收款场景,个人主体小程序不支持微信支付,仅企业/个体工商户可接入。
1.2 核心规则强调
安全核心禁忌:微信小程序支付禁止在前端处理签名、密钥存储、预下单逻辑,所有核心支付接口必须由后端服务器完成,前端仅负责发起订单请求、接收参数拉起收银台,否则会触发微信安全风控,导致支付权限封禁、资金结算异常。
强制要求:后端必须部署公网HTTPS域名,HTTP域名无法通过微信支付校验,所有接口交互均需遵循HTTPS协议。
一、前置资质与必备条件
1.1 必备主体资质
1已认证微信小程序:仅限企业/个体工商户主体,完成微信公众平台实名认证,个人主体小程序无微信支付权限
1微信支付商户号:独立申请的微信支付商户平台账号,完成商户实名认证、结算账户绑定
1小程序与商户号绑定:同一主体下完成绑定,跨主体需额外申请关联授权
1后端服务器:具备公网访问能力,配置有效HTTPS证书,支持接口开发与异步通知接收
1.2 核心开发参数(提前归集)
开发前需提前整理以下参数,全程贯穿开发流程,避免频繁切换平台查找,参数明细如下:
|
参数名称 |
获取位置 |
用途说明 |
|
小程序AppID |
微信公众平台-小程序-开发管理-开发设置 |
小程序唯一标识,支付接口必填 |
|
商户号(mch_id) |
微信支付商户平台-账户信息 |
微信支付商户唯一标识 |
|
支付V3密钥(api_v3_key) |
商户平台-账户中心-API安全-设置V3密钥 |
支付签名、数据加密核心密钥,严格保密 |
|
商户证书序列号 |
商户平台-API安全-证书管理 |
V3接口签名校验 |
|
商户API证书(pem) |
商户平台-API安全-证书下载 |
退款、敏感接口调用,基础收款暂可备用 |
二、平台全流程配置(无配置无法开发)
2.1 开通微信支付商户号
1登录微信支付商户平台(https://pay.weixin.qq.com/),选择企业/个体工商户注册,提交营业执照、法人身份证、结算银行卡等资料
1完成商户实名认证与账户验证,等待审核通过(一般1-3个工作日)
1审核通过后,进入账户中心-API安全,设置V3密钥、下载商户证书,妥善保管所有密钥与证书,禁止泄露
2.2 小程序绑定商户号
1登录微信公众平台(https://mp.weixin.qq.com/),进入目标小程序后台
1路径:开发-开发管理-支付设置,点击“绑定商户号”,输入已开通的商户号,完成主体校验
1绑定成功后即时生效,无需额外审核,小程序自动获得支付权限
2.3 域名与支付授权配置
1小程序服务器域名配置:进入开发-开发设置-服务器域名,在request合法域名中添加后端HTTPS域名,保存后5分钟左右生效
1商户平台支付授权配置:登录商户平台-产品中心-开发配置,配置两项核心目录:
1支付授权目录:填写后端支付接口的根域名
1JSAPI支付授权目录:小程序支付专属目录,与后端接口域名保持一致
1校验域名生效:确保域名已备案、HTTPS证书有效,无证书过期、域名不匹配问题
三、开发环境搭建
3.1 推荐技术栈
1前端:微信小程序原生开发 / uniapp / Taro(本文以原生小程序为例)
1后端:Node.js/Java/Python/PHP(本文以Node.js为例,其他语言逻辑一致,仅SDK调用差异)
1协议:HTTPS(强制)
1支付版本:微信支付V3(官方主推,安全性更高,接口更规范)
3.3 后端环境准备(Java SpringBoot)
本文采用SpringBoot 2.7.x稳定版本适配开发,全程基于微信支付V3官方规范,不依赖杂乱第三方SDK,避免版本冲突与安全隐患,开发前提前搭建好SpringBoot基础项目,配置好Maven环境与MySQL数据库,用于存储订单数据。
3.2 前端开发工具
下载微信开发者工具,导入小程序项目,配置AppID,关闭“不校验合法域名”(开发测试可临时开启,上线必须关闭)。
3.3 后端依赖安装(Node.js)
初始化项目后,安装微信支付V3官方SDK与基础服务依赖,命令如下:
Maven核心依赖配置(截图版):下方为项目pom.xml所需的微信支付、Web、JSON解析核心依赖,直接对照截图复制配置即可,无需手动排版代码,复制到Word无格式问题。
四、完整支付业务流程
小程序支付为标准异步流程,共7个核心节点,每一步缺一不可,流程如下:
1用户在小程序端点击支付按钮,触发支付请求
1小程序调用后端创建订单接口,传递订单金额、商品描述、用户openid
1后端生成本地业务订单,存入数据库,调用微信支付统一下单API
1微信支付接口返回预支付ID(prepay_id)
1后端基于prepay_id完成V3签名,生成前端拉起收银台的参数,返回小程序
1小程序调用wx.requestPayment API,拉起微信收银台,用户输入密码完成支付
1微信服务器主动调用后端异步通知接口,推送支付结果,后端修改订单状态
核心要点:后端异步通知是订单支付成功的唯一依据,禁止以前端回调结果作为支付成功判断标准,避免前端伪造请求导致资损。
五、后端完整代码实现
本文采用SpringBoot 2.7.x版本实现后端支付逻辑,直接对接微信支付V3官方接口,不依赖第三方封装SDK,避免版本冲突,核心包含依赖引入、配置文件、配置类、下单接口、支付异步通知接口五大模块,代码可直接复制使用。
5.1 Maven核心依赖
以下为pom.xml核心依赖配置截图,包含SpringBoot Web、微信支付V3、JSON解析、配置处理器依赖,直接复制截图内文本到项目pom.xml即可,适配所有SpringBoot 2.7.x版本,无依赖冲突。
在项目pom.xml文件中添加微信支付V3、HTTP请求、加密相关依赖,用于接口调用、签名生成与数据解析:
|
xml |
5.2 配置文件参数配置
在application.yml中配置微信支付核心参数,商户证书提前放入resources/cert目录,以下为配置文件截图,严格按照截图内容填写,严禁硬编码密钥、证书信息。
在application.yml中配置微信支付核心参数,提前将商户证书(apiclient_key.pem)放入项目resources/cert目录下,严禁将密钥、证书硬编码到代码中:
|
yaml server: port: 8080 servlet: context-path: /api # 微信支付V3配置 wechat: pay: app-id: 小程序AppID mch-id: 微信支付商户号 api-v3-key: 微信支付V3密钥 serial-no: 商户证书序列号 private-key-path: classpath:cert/apiclient_key.pem notify-url: https://你的公网HTTPS域名/api/pay/notify# 支付结果异步通知地址 |
5.3 微信支付配置类
该配置类用于加载商户私钥、初始化微信支付V3 HTTP客户端,实现自动签名与证书加载,以下为完整代码截图,直接复制使用即可。
创建配置类读取yml参数,初始化微信支付V3请求客户端,实现证书加载、签名自动处理,后续接口直接注入使用:
|
java import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.FileInputStream; import java.io.InputStream; import java.security.PrivateKey; @Configuration public class WechatPayConfig { @Value(“${wechat.pay.app-id}”) private String appId; @Value(“${wechat.pay.mch-id}”) private String mchId; @Value(“${wechat.pay.api-v3-key}”) private String apiV3Key; @Value(“${wechat.pay.serial-no}”) private String serialNo; @Value(“${wechat.pay.private-key-path}”) private String privateKeyPath; /** * 加载商户私钥 */ @Bean public PrivateKey getPrivateKey() throws Exception { InputStream inputStream = this.getClass().getResourceAsStream(privateKeyPath.replace(“classpath:”, “”)); return PemUtil.loadPrivateKey(inputStream); } /** * 初始化微信支付V3 HTTP客户端 */ @Bean public CloseableHttpClient wechatPayHttpClient(PrivateKey privateKey) { WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withCredentials(new WechatPay2Credentials(mchId, new PrivateKeySigner(serialNo, privateKey))) .withValidator(new WechatPay2Validator((Verifier) null)); return builder.build(); } // getter方法省略,直接注入对应参数即可 } |
5.4 统一下单接口(核心)
核心下单接口,实现参数校验、本地订单创建、微信预下单、支付参数生成全逻辑,金额单位固定为分,订单号保证全局唯一,代码截图如下:
创建Controller接口,接收小程序前端请求,完成本地订单创建、调用微信统一下单接口、生成前端支付参数,金额单位统一为分,订单号需保证全局唯一:
|
java import com.alibaba.fastjson2.JSONObject; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.UUID; @RestController @RequestMapping(“/pay”) public class WechatPayController { @Autowired private CloseableHttpClient wechatPayHttpClient; @Value(“${wechat.pay.app-id}”) private String appId; @Value(“${wechat.pay.mch-id}”) private String mchId; @Value(“${wechat.pay.notify-url}”) private String notifyUrl; @Value(“${wechat.pay.api-v3-key}”) private String apiV3Key; /** * 小程序支付统一下单接口 * @param params 前端传入参数:openid, totalFee(金额分), body(商品描述) */ @PostMapping(“/createOrder”) public MapcreateOrder(@RequestBody JSONObject params) { MapresultMap = new HashMap<>(); try { // 1. 获取前端参数并校验 String openid = params.getString(“openid”); Integer totalFee = params.getInteger(“totalFee”); String body = params.getString(“body”); if (openid == null || totalFee == null || totalFee < 1 || body == null) { resultMap.put(“code”, 400); resultMap.put(“msg”, “参数缺失或金额不合法”); return resultMap; } // 生成唯一商户订单号 String outTradeNo = “ORDER” + System.currentTimeMillis() + UUID.randomUUID().toString().replace(“-“, “”).substring(0, 8); // 2. 此处添加本地订单入库逻辑(存入MySQL,状态默认为0:未支付) // orderService.saveOrder(outTradeNo, totalFee, openid, body); // 3. 组装微信统一下单请求参数 JSONObject requestParam = new JSONObject(); requestParam.put(“appid”, appId); requestParam.put(“mchid”, mchId); requestParam.put(“description”, body); requestParam.put(“out_trade_no”, outTradeNo); requestParam.put(“notify_url”, notifyUrl); // 金额参数 JSONObject amountObj = new JSONObject(); amountObj.put(“total”, totalFee); amountObj.put(“currency”, “CNY”); requestParam.put(“amount”, amountObj); // 用户openid JSONObject payerObj = new JSONObject(); payerObj.put(“openid”, openid); requestParam.put(“payer”, payerObj); // 4. 调用微信支付V3统一下单接口 HttpPost httpPost = new HttpPost(“https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi”); httpPost.addHeader(“Content-Type”, “application/json”); httpPost.addHeader(“Accept”, “application/json”); httpPost.setEntity(new StringEntity(requestParam.toJSONString(), StandardCharsets.UTF_8)); CloseableHttpResponse response = wechatPayHttpClient.execute(httpPost); String responseStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); JSONObject responseObj = JSONObject.parseObject(responseStr); // 5. 获取预支付ID,生成前端支付参数 String prepayId = responseObj.getString(“prepay_id”); if (prepayId == null) { resultMap.put(“code”, 500); resultMap.put(“msg”, “微信下单失败:” + responseObj.getString(“message”)); return resultMap; } // 生成小程序拉起支付所需参数 MappayParams = buildPayParams(prepayId); resultMap.put(“code”, 200); resultMap.put(“msg”, “下单成功”); resultMap.put(“data”, payParams); } catch (Exception e) { e.printStackTrace(); resultMap.put(“code”, 500); resultMap.put(“msg”, “服务异常,下单失败”); } return resultMap; } /** * 生成小程序前端支付参数(自动签名) */ private MapbuildPayParams(String prepayId) throws Exception { MappayParams = new HashMap<>(); long timeStamp = System.currentTimeMillis() / 1000; String nonceStr = UUID.randomUUID().toString().replace(“-“, “”); String packageStr = “prepay_id=” + prepayId; // 小程序支付必填参数 payParams.put(“appId”, appId); payParams.put(“timeStamp”, String.valueOf(timeStamp)); payParams.put(“nonceStr”, nonceStr); payParams.put(“package”, packageStr); payParams.put(“signType”, “RSA”); // 生成签名(微信支付V3签名逻辑,此处省略具体签名实现,直接使用SDK自动处理) return payParams; } } |
5.5 支付结果异步通知接口
微信支付结果主动推送接口,是订单支付成功的唯一判断依据,必须保证公网可访问,处理完成后按微信规范返回响应,避免重复通知,代码截图如下:
微信支付结果主动推送接口,是订单支付成功的唯一判断依据,必须保证公网可访问,处理完成后需按照微信规范返回响应,避免重复通知:
|
java import com.alibaba.fastjson2.JSONObject; import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping(“/pay”) public class WechatPayNotifyController { @Value(“${wechat.pay.api-v3-key}”) private String apiV3Key; /** * 微信支付异步通知接口 */ @PostMapping(“/notify”) public MappayNotify(HttpServletRequest request, @RequestBody String body) { MapresultMap = new HashMap<>(); try { // 1. 解析微信通知参数 JSONObject notifyObj = JSONObject.parseObject(body); String resourceType = notifyObj.getString(“resource_type”); JSONObject resource = notifyObj.getJSONObject(“resource”); String ciphertext = resource.getString(“ciphertext”); String nonce = resource.getString(“nonce”); String associatedData = resource.getString(“associated_data”); // 2. AES解密通知数据(V3密钥解密) AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)); String decryptStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext); JSONObject decryptObj = JSONObject.parseObject(decryptStr); // 3. 获取订单核心信息 String outTradeNo = decryptObj.getString(“out_trade_no”); String tradeState = decryptObj.getString(“trade_state”); String transactionId = decryptObj.getString(“transaction_id”); // 4. 支付成功处理 if (“SUCCESS”.equals(tradeState)) { // 更新本地订单状态:改为1-已支付,记录微信订单号、支付时间 // orderService.updateOrderStatus(outTradeNo, 1, transactionId); System.out.println(“订单” + outTradeNo + “支付成功,微信订单号:” + transactionId); } // 5. 返回成功响应给微信,停止重复通知 resultMap.put(“code”, “SUCCESS”); resultMap.put(“message”, “成功”); } catch (Exception e) { e.printStackTrace(); // 处理失败,返回失败,微信会重新推送通知 resultMap.put(“code”, “FAIL”); resultMap.put(“message”, “处理失败”); } return resultMap; } } |
六、前端小程序适配代码
前端仅需修改后端接口地址,其余逻辑不变,以下为小程序支付页面精简代码截图,直接复制到微信开发者工具即可运行:
前端小程序代码无需大幅改动,仅需将原有请求后端接口地址,替换为Java后端接口地址:https://你的域名/api/pay/createOrder,其余逻辑(获取openid、拉起支付)完全沿用,代码如下(精简版,直接复制):
|
javascript // pages/pay/pay.js const app = getApp(); Page({ data: { totalFee: 100, orderBody: ‘小程序商品购买’ }, // 发起支付 toPay() { const openid = app.globalData.openid; if (!openid) { wx.showToast({ title: ‘请先授权登录’, icon: ‘none’ }); return; } wx.showLoading({ title: ‘下单中…’ }); wx.request({ url: ‘https://你的公网HTTPS域名/api/pay/createOrder’, method: ‘POST’, data: { openid: openid, totalFee: this.data.totalFee, body: this.data.orderBody }, success: (res) => { wx.hideLoading(); if (res.data.code === 200) { // 拉起微信支付 wx.requestPayment({ …res.data.data, success: () => wx.showToast({ title: ‘支付成功’ }), fail: () => wx.showToast({ title: ‘支付取消’, icon: ‘none’ }) }) } else { wx.showToast({ title: res.data.msg, icon: ‘none’ }) } }, fail: () => { wx.hideLoading(); wx.showToast({ title: ‘网络异常’, icon: ‘none’ }) } }) } }) |
七、Java开发常见注意事项
1证书路径问题:商户证书需放入resources目录,打包时确保证书一同打包,避免线上环境找不到证书
1跨域配置:SpringBoot项目需添加跨域配置,允许小程序域名访问
1签名错误:核对V3密钥、证书序列号、私钥是否匹配,时间戳需采用秒级而非毫秒级
1异步通知白名单:服务器防火墙放行微信支付通知IP,避免通知被拦截
1事务控制:订单创建与支付下单需添加事务,防止订单重复创建
八、异常处理与上线校验
8.1 常见异常排查
1下单报错“域名未授权”:检查小程序后台合法域名、商户平台支付授权目录是否配置HTTPS域名
1支付报错“签名验证失败”:重新核对商户证书、V3密钥、序列号,确保参数完全一致
1收不到异步通知:检查通知地址公网可访问,无内网限制,接口返回格式符合微信规范
8.2 上线前校验步骤
1关闭微信开发者工具“不校验合法域名”,测试接口连通性
1测试0.01元小额支付,验证下单、支付、订单状态更新全流程
1校验商户平台结算账户、费率配置,确保资金正常结算
1提交小程序审核,审核通过后正式发布
需要源码的关注公众号 回复:添加微信


————————
夜雨聆风