微信直连商户公众号 JSAPI 支付,详细教程+源码

简介: JSAPI 支付用于微信公众号内的网页调起微信收银台,常见于在公众号菜单、文章页或 H5 活动页中完成支付。该方式依赖微信内置浏览器环境,非微信浏览器无法调起。

大家好,我是小悟。

适用场景与入口:JSAPI 支付用于微信公众号内的网页调起微信收银台,常见于在公众号菜单、文章页或 H5 活动页中完成支付。该方式依赖微信内置浏览器环境,非微信浏览器无法调起。

身份标识:下单前必须获取用户在当前公众号下的OpenID,通常通过 OAuth2 授权码 code → access_token → openid 流程获得。

下单与预支付:商户后端调用微信统一下单 API,传入必要参数(如 appid、mchid、out_trade_no、total、body、notify_url、openid、trade_type=JSAPI),微信返回prepay_id。

前端调起:前端使用微信 JSSDKchooseWXPay 调起支付,传入由后端生成的支付参数与签名(appId、timeStamp、nonceStr、package=prepay_id=…、signType、paySign)。

结果处理:前端仅用于展示结果;商户以微信服务器的异步通知(notify)为准更新订单状态,并做好幂等。

域名与目录:需在公众号后台配置网页授权域名,在商户平台配置支付授权目录,否则会报“当前页面的 URL 未注册”等错误。

安全要点:全程使用 HTTPS,参数签名校验、金额一致性校验、通知去重与状态机控制。

以上要点与流程为微信公众号内网页支付的标准实践,适用于公众号场景的 JSAPI 调起与结果处理。

后端 Spring Boot 实现(Java)

说明:以下示例采用微信支付v3 版 API + SDK,包含配置、统一下单、回调验签与通知回执,便于快速落地。

  1. 1、Maven 依赖(pom.xml)
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 微信支付 v3 Java SDK -->
    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-java</artifactId>
        <version>0.4.12</version>
    </dependency>
    <!-- JSON 处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

说明:使用官方维护的 wechatpay-java SDK,简化签名、验签与 HTTP 调用,适配 v3 接口与证书自动更新。

2、配置文件(application.yml)

wechat:
  appId: wx8888888888888888
  mchId: 1900000001
  merchantSerialNumber: 444C3F6B5A7D8E9F0A1B2C3D4E5F6A7B8C9D0E1F
  privateKeyPath: classpath:cert/merchant/apiclient_key.pem
  apiV3Key: 7788990011223344556677889900112233445566
  notifyUrl: https://yourdomain.com/api/wxpay/notify

说明:配置包含公众号 appId商户号 mchId商户证书序列号商户私钥路径APIv3 密钥支付回调地址。其中 APIv3 密钥用于回调内容解密与验签。

3、配置类与 SDK 初始化

package com.example.wxpay.config;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
@Configuration
@ConfigurationProperties(prefix = "wechat")
public class WechatPayConfig {
    private String appId;
    private String mchId;
    private String merchantSerialNumber;
    private String privateKeyPath;
    private String apiV3Key;
    private String notifyUrl;
    @Bean
    public JsapiService jsapiService() throws Exception {
        // 读取商户私钥(PKCS#8 PEM)
        InputStream keyInputStream = getClass().getClassLoader().getResourceAsStream(privateKeyPath);
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(keyInputStream);
        RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(mchId)
                .privateKeyFromPath(privateKeyPath)
                .merchantSerialNumber(merchantSerialNumber)
                .apiV3Key(apiV3Key.getBytes(StandardCharsets.UTF_8))
                .build();
        return new JsapiService.Builder().config(config).build();
    }
    // getter/setter 省略
}

说明:通过 RSAAutoCertificateConfig 自动管理平台证书并完成请求签名,是微信支付 v3 推荐的接入方式。

4、统一下单与生成前端调起参数

package com.example.wxpay.service;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.nativepay.model.Amount as NativeAmount;
import com.wechat.pay.java.service.payments.nativepay.model.Transaction;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.UUID;
@Service
public class WxPayService {
    private final JsapiService jsapiService;
    private final String appId;
    private final String notifyUrl;
    public WxPayService(JsapiService jsapiService,
                        @Value("${wechat.appId}") String appId,
                        @Value("${wechat.notifyUrl}") String notifyUrl) {
        this.jsapiService = jsapiService;
        this.appId = appId;
        this.notifyUrl = notifyUrl;
    }
    // 金额单位:分 -> 元(微信统一下单以“分”为单位)
    private long fenToYuan(Long fen) {
        return fen == null ? 0L : fen / 100L;
    }
    public PrepayResponse jsapiPrepay(String openid, Long totalFee, String body, String outTradeNo) throws Exception {
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(fenToYuan(totalFee)); // 微信金额单位:分
        request.setAmount(amount);
        request.setAppid(appId);
        request.setMchid(mchId);
        request.setOutTradeNo(outTradeNo);
        request.setNonceStr(UUID.randomUUID().toString().replace("-", ""));
        request.setBody(body);
        request.setNotifyUrl(notifyUrl);
        request.setPayer(new com.wechat.pay.java.service.payments.jsapi.model.Payer().setOpenid(openid));
        return jsapiService.prepay(request);
    }
}

说明:调用 JsapiService.prepay 获取 prepay_id,后续用于前端调起支付。注意金额单位为,openid 为必填。

5、回调控制器(验签 + 解密 + 回执)

ackage com.example.wxpay.controller;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.NotificationRequest;
import com.wechat.pay.java.core.notification.NotificationResponse;
import com.wechat.pay.java.service.payments.nativepay.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@RestController
@RequestMapping("/api/wxpay")
public class WxPayNotifyController {
    private final NotificationParser notificationParser;
    private final ObjectMapper objectMapper = new ObjectMapper();
    public WxPayNotifyController(RSAAutoCertificateConfig config) {
        this.notificationParser = new NotificationParser(config);
    }
    @PostMapping(value = "/notify", consumes = "application/json", produces = "application/json")
    public ResponseEntity<byte[]> handleNotify(HttpServletRequest request) {
        try {
            // 1) 解析并验签,自动解密 resource.data
            NotificationRequest req = notificationParser.parse(request.getInputStream());
            String resourceType = req.getResourceType();
            String plaintext = new String(req.getPlaintext(), StandardCharsets.UTF_8);
            String serialNo = req.getWechatpaySerial();
            String signature = req.getWechatpaySignature();
            String nonce = req.getWechatpayNonce();
            // 2) 根据资源类型处理(此处以 JSAPI 为例,实际可能为 TRANSACTION.TRANSACTION 或 JSAPI.PREPAYMENT_ID 等)
            if ("TRANSACTION.TRANSACTION".equals(resourceType)) {
                Transaction txn = objectMapper.readValue(plaintext, Transaction.class);
                String tradeState = txn.getTradeState();
                String outTradeNo = txn.getOutTradeNo();
                log.info("支付回调 trade_state={}, out_trade_no={}", tradeState, outTradeNo);
                // TODO: 校验商户订单号、金额一致性、幂等处理、更新订单状态
                // 3) 返回成功回执(必须)
                return ResponseEntity.ok().body("{\"code\":\"SUCCESS\",\"message\":\"成功\"}".getBytes(StandardCharsets.UTF_8));
            } else {
                log.warn("暂不支持的回调类型: {}", resourceType);
                return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body("{\"code\":\"FAIL\",\"message\":\"不支持的回调类型\"}".getBytes(StandardCharsets.UTF_8));
            }
        } catch (Exception e) {
            log.error("处理微信回调异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("{\"code\":\"FAIL\",\"message\":\"处理失败\"}".getBytes(StandardCharsets.UTF_8));
        }
    }
}

说明:使用 NotificationParser 自动完成验签与解密,业务侧仅需按资源类型解析并处理。回调必须返回200 与成功 JSON,否则微信会重试。

前端 UniApp 实现(Vue 2⁄3 通用思路)

说明:前端在公众号内页面调用 JSSDK 的 chooseWXPay,参数由后端生成并签名。注意使用 HTTPS微信授权域名,并在支付回调中仅作页面跳转,以服务器异步通知为准更新订单。

1、安装并引入 JSSDK(NPM)

npm install jweixin-module --save

说明:使用官方推荐的 jweixin-module 在小程序/公众号 H5 中调用微信 JSAPI。

2、获取 OpenID(示例:OAuth2 简化流程)

// utils/auth.js
export function getWxCode(callback) {
  const appid = 'wx8888888888888888';
  const redirectUri = encodeURIComponent('https://yourdomain.com/pages/pay/index');
  const scope = 'snsapi_base'; // 静默授权,仅获取 openid
  const state = 'STATE';
  const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
  // 若当前页面无 code,则跳转授权
  if (!getLocationSearch('code')) {
    window.location.href = url;
  } else {
    const code = getLocationSearch('code');
    // 将 code 发送给后端换取 openid(示例)
    // axios.post('/api/wxpay/auth', { code }).then(res => callback(res.data.openid));
    callback('MOCK_OPENID'); // 仅演示,实际请请求后端
  }
}
function getLocationSearch(key) {
  const reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)');
  const r = window.location.search.substr(1).match(reg);
  return r ? decodeURIComponent(r[2]) : null;
}

说明:通过 snsapi_base 静默授权获取 openid,用于统一下单必填字段。生产环境请在后端完成换取与校验。

3、调起支付页面(Vue 示例)

<template>
  <view class="pay-page">
    <button type="primary" @click="handlePay">微信支付</button>
  </view>
</template>
<script>
import jweixin from 'jweixin-module';
import { getWxCode } from '@/utils/auth';
export default {
  data() {
    return {
      payParams: null, // { appId, timeStamp, nonceStr, package: 'prepay_id=...', signType, paySign }
    };
  },
  async onLoad() {
    await getWxCode((openid) => {
      this.createOrder(openid);
    });
  },
  methods: {
    async createOrder(openid) {
      try {
        const res = await uni.request({
          url: 'https://yourdomain.com/api/wxpay/jsapi-prepay',
          method: 'POST',
          data: { openid, totalFee: 100, body: '测试商品', outTradeNo: 'ORDER_' + Date.now() },
        });
        this.payParams = res.data.data; // 后端返回的支付参数
        this.initWxConfig();
      } catch (err) {
        uni.showToast({ title: '下单失败', icon: 'none' });
      }
    },
    initWxConfig() {
      jweixin.config({
        debug: false,
        appId: this.payParams.appId,
        timestamp: parseInt(this.payParams.timeStamp, 10),
        nonceStr: this.payParams.nonceStr,
        signature: this.payParams.paySign,
        jsApiList: ['chooseWXPay'],
      });
      jweixin.ready(() => {
        // 可选:检测接口可用性
        jweixin.checkJsApi({
          jsApiList: ['chooseWXPay'],
        });
      });
      jweixin.error((err) => {
        console.error('JSSDK 配置失败', err);
        uni.showToast({ title: '支付初始化失败', icon: 'none' });
      });
    },
    pay() {
      if (!this.payParams) return;
      uni.showLoading({ title: '调起支付中' });
      jweixin.chooseWXPay({
        timestamp: parseInt(this.payParams.timeStamp, 10), // 注意:JSSDK 字段名为小写 timeStamp
        nonceStr: this.payParams.nonceStr,
        package: this.payParams.package,                 // 形如:prepay_id=wx123456...
        signType: this.payParams.signType,               // 通常为 MD5
        paySign: this.payParams.paySign,
        success: (res) => {
          uni.hideLoading();
          uni.showToast({ title: '支付成功', icon: 'success' });
          // 注意:前端成功回调并非最终结果,需以服务器异步通知为准
          uni.redirectTo({ url: '/pages/pay/result?status=success' });
        },
        cancel: () => {
          uni.hideLoading();
          uni.showToast({ title: '支付取消', icon: 'none' });
        },
        fail: (err) => {
          uni.hideLoading();
          console.error('支付失败', err);
          uni.showToast({ title: '支付失败', icon: 'none' });
        },
      });
    },
  },
};
</script>

说明:前端通过 chooseWXPay 调起支付,参数中的 package=prepay_id=…paySign 由后端提供。注意 timeStamp 在 JSSDK 中为小写,与后端统一下单的 timeStamp(大写)命名不同。支付结果以服务器异步通知为准,前端回调仅用于页面提示与跳转。

部署与安全要点

域名与目录:在公众号后台配置网页授权域名,在商户平台配置支付授权目录,页面 URL 必须与配置完全匹配,否则无法调起支付。

HTTPS 与证书:全链路使用 HTTPS;商户 API 证书妥善保管,SDK 已支持自动更新平台证书,无需重启。

金额与签名:金额统一用;签名使用微信指定算法(v3 为 RSA-SHA256),严格按字典序与规则拼接。

幂等与去重:以微信异步通知为准更新订单,做好幂等(如按商户订单号去重、状态机校验)。

以上为公众号支付的关键注意事项,能有效避免“未注册 URL”“签名错误”“无法调起”等常见问题。

微信直连商户公众号 JSAPI 支付,详细教程+源码.jpg

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

相关文章
|
3月前
|
负载均衡 容灾 JavaScript
Nginx反向代理容灾备份(手把手教你搭建高可用Web服务)
本文介绍如何通过Nginx反向代理实现容灾备份与高可用架构。利用upstream模块配置主备服务器,结合健康检查与自动故障转移,确保主服务宕机时无缝切换至备用服务器。图文详解参数设置、配置步骤及测试方法,并提供Keepalived、HTTPS等进阶优化建议,助小白快速搭建稳定可靠的Web系统。
|
监控 Java 应用服务中间件
谈谈你对spring boot 3.0的理解
1. Java 版本要求:Spring Boot 3.0 要求使用 Java 17 或更高版本,这可能会对一些仍在使用旧版 Java 的项目造成兼容性问题。需要确保项目使用的 Java 版本符合要求,并考虑是否需要升级 JDK 版本。 2. 底层依赖项迁移:Spring Boot 3.0 将所有底层依赖项从 Java EE 迁移到了 Jakarta EE API,基于 Jakarta EE 9 并尽可能地兼容 Jakarta EE 10。这可能会对一些使用了 Java EE 的应用造成影响,需要进行相应的修改和调整。 3. 插件和库的支持:尽管 Spring Boot 3.0 支持更多的插件和
1935 0
|
3月前
|
数据采集 人工智能 运维
为什么你跟AI说话它总是听不懂?12000星项目揭秘答案
想让AI真正听懂你的话?别再靠“感觉”编程!从Vibe Coding到上下文工程,用三份说明书(项目规矩、需求详情、执行清单)系统化提升AI输出质量。老金实测:前期多花30分钟,后期省下2小时返工。附开源知识库+GitHub高星项目解读,助你打造靠谱AI搭档。
|
JavaScript 内存技术
node与npm版本对应关系以及使用nvm管理node版本
node与npm版本对应关系以及使用nvm管理node版本
8838 0
|
3月前
|
人工智能 自然语言处理 Shell
🦞 如何在 OpenClaw (Clawdbot/Moltbot) 配置阿里云百炼 API
本教程指导用户在开源AI助手Clawdbot中集成阿里云百炼API,涵盖安装Clawdbot、获取百炼API Key、配置环境变量与模型参数、验证调用等完整流程,支持Qwen3-max thinking (Qwen3-Max-2026-01-23)/Qwen - Plus等主流模型,助力本地化智能自动化。
77575 201
🦞 如何在 OpenClaw (Clawdbot/Moltbot) 配置阿里云百炼 API
|
3月前
|
人工智能 安全 API
谷歌亮剑“魔猫”:一场针对中国跨境短信钓鱼团伙的法律围剿
2025年,谷歌起诉“魔猫”团伙,揭露其利用Google Voice与“Darcula”工具包实施大规模短信钓鱼。此案标志科技巨头从技术防御转向法律反制,凸显云服务滥用与AI犯罪升级的严峻挑战,推动全球协作构建数字安全防线。
488 0
|
3月前
|
人工智能 运维 监控
安全事件别再靠人熬了:从报警到修复,一条自动化编排的命
安全事件别再靠人熬了:从报警到修复,一条自动化编排的命
133 10
|
5月前
|
机器学习/深度学习 人工智能 前端开发
终端里的 AI 编程助手:OpenCode 使用指南
OpenCode 是开源的终端 AI 编码助手,支持 Claude、GPT-4 等模型,可在命令行完成代码编写、Bug 修复、项目重构。提供原生终端界面和上下文感知能力,适合全栈开发者和终端用户使用。
44147 11