乐于分享
好东西不私藏

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

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

关键字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                  org.springframework.bootspring-boot-starter-webcom.github.wechatpay-apiv3wechatpay-apache-httpclient0.4.6com.alibabafastjson22.0.32org.springframework.bootspring-boot-configuration-processortrue

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提交小程序审核,审核通过后正式发布

需要源码的关注公众号  回复:添加微信


———————————————
【你的每一份打赏就是最真诚的鼓励】