时间: 2025-09-22 【学无止境】 阅读量:共2人围观
简介 在微信浏览器中原生拉取微信支付,通常指的是通过 微信JS-SDK 或 微信内嵌浏览器提供的JSAPI 来调起支付界面。这是实现微信H5支付(在微信内部浏览器的支付)的标准方式。
实现原理
流程分为前后端协作:
前端:在微信浏览器中引入JS-SDK,准备调起支付。
后端:生成支付所需的参数(最关键的是生成签名)。
前端:获取后端生成的参数,调用 wx.chooseWXPay 或 WeixinJSBridge.invoke 方法调起支付。
方法一:使用微信JS-SDK(官方推荐)
这是目前更主流和推荐的方式。
步骤 1:引入JS-SDK
在需要支付的页面引入JS-SDK脚本:
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <!-- 或使用更新版本 -->
步骤 2:后端生成支付参数(java示例)
1. 添加 Maven 依赖
首先,在 pom.xml 中添加必要的依赖:
<dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- XML 处理 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> </dependency> <!-- HTTP 客户端 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!-- 其他你可能已经有的依赖 --> </dependencies>
2. 配置微信支付参数
在 application.yml 或 application.properties 中配置:
# application.yml wechat: pay: app-id: wx1234567890abcdef # 公众号或小程序的APPID mch-id: 1230000000 # 商户号 mch-key: your_mch_key_32_characters_long # 商户API密钥 notify-url: https://yourdomain.com/api/wxpay/notify # 支付结果回调地址 unified-order-url: https://api.mch.weixin.qq.com/pay/unifiedorder # 统一下单接口
创建配置类 WechatPayConfig.java:
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "wechat.pay") public class WechatPayConfig { private String appId; private String mchId; private String mchKey; private String notifyUrl; private String unifiedOrderUrl; // Getter 和 Setter 方法 public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getMchId() { return mchId; } public void setMchId(String mchId) { this.mchId = mchId; } public String getMchKey() { return mchKey; } public void setMchKey(String mchKey) { this.mchKey = mchKey; } public String getNotifyUrl() { return notifyUrl; } public void setNotifyUrl(String notifyUrl) { this.notifyUrl = notifyUrl; } public String getUnifiedOrderUrl() { return unifiedOrderUrl; } public void setUnifiedOrderUrl(String unifiedOrderUrl) { this.unifiedOrderUrl = unifiedOrderUrl; } }
3. 创建请求和响应DTO
创建统一订单请求DTO UnifiedOrderRequest.java:
import com.fasterxml.jackson.annotation.JsonProperty; public class UnifiedOrderRequest { @JsonProperty("out_trade_no") private String outTradeNo; // 商户订单号 @JsonProperty("total_fee") private Integer totalFee; // 订单金额(分) private String body; // 商品描述 private String openid; // 用户openid // Getter 和 Setter public String getOutTradeNo() { return outTradeNo; } public void setOutTradeNo(String outTradeNo) { this.outTradeNo = outTradeNo; } public Integer getTotalFee() { return totalFee; } public void setTotalFee(Integer totalFee) { this.totalFee = totalFee; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } }
创建支付参数响应DTO PaymentParamsResponse.java:
import com.fasterxml.jackson.annotation.JsonProperty; public class PaymentParamsResponse { @JsonProperty("appId") private String appId; @JsonProperty("timeStamp") private String timeStamp; @JsonProperty("nonceStr") private String nonceStr; @JsonProperty("package") private String packageValue; @JsonProperty("signType") private String signType; @JsonProperty("paySign") private String paySign; // Getter 和 Setter public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimeStamp() { return timeStamp; } public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getPackageValue() { return packageValue; } public void setPackageValue(String packageValue) { this.packageValue = packageValue; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } public String getPaySign() { return paySign; } public void setPaySign(String paySign) { this.paySign = paySign; } }
4. 核心工具类 - 签名和HTTP请求
创建微信支付工具类 WechatPayUtil.java:
import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.xml.sax.InputSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.StringReader; import java.io.StringWriter; import java.security.MessageDigest; import java.util.*; @Component public class WechatPayUtil { /** * 生成MD5签名 */ public String generateSign(Map<String, String> params, String key) { try { // 按字典序排序参数 List<String> keys = new ArrayList<>(params.keySet()); Collections.sort(keys); StringBuilder stringA = new StringBuilder(); for (String k : keys) { String v = params.get(k); if (v != null && !v.isEmpty() && !k.equals("sign")) { stringA.append(k).append("=").append(v).append("&"); } } stringA.append("key=").append(key); MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(stringA.toString().getBytes("UTF-8")); StringBuilder sign = new StringBuilder(); for (byte b : digest) { sign.append(String.format("%02x", b & 0xff)); } return sign.toString().toUpperCase(); } catch (Exception e) { throw new RuntimeException("生成签名失败", e); } } /** * Map转换为XML字符串 */ public String mapToXml(Map<String, String> params) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.newDocument(); org.w3c.dom.Element root = document.createElement("xml"); document.appendChild(root); for (Map.Entry<String, String> entry : params.entrySet()) { org.w3c.dom.Element element = document.createElement(entry.getKey()); element.appendChild(document.createTextNode(entry.getValue())); root.appendChild(element); } TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(document), new StreamResult(writer)); return writer.toString(); } catch (Exception e) { throw new RuntimeException("Map转XML失败", e); } } /** * XML字符串转换为Map */ public Map<String, String> xmlToMap(String xml) { try { Map<String, String> data = new HashMap<>(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new InputSource(new StringReader(xml))); document.getDocumentElement().normalize(); org.w3c.dom.NodeList nodeList = document.getDocumentElement().getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { org.w3c.dom.Node node = nodeList.item(i); if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { data.put(node.getNodeName(), node.getTextContent()); } } return data; } catch (Exception e) { throw new RuntimeException("XML转Map失败", e); } } /** * 生成随机字符串 */ public String generateNonceStr() { return UUID.randomUUID().toString().replace("-", "").substring(0, 32); } }
5. 支付服务类
创建支付服务 WechatPayService.java:
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.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; @Service public class WechatPayService { @Autowired private WechatPayConfig wechatPayConfig; @Autowired private WechatPayUtil wechatPayUtil; /** * 获取支付参数 */ public PaymentParamsResponse getPaymentParams(UnifiedOrderRequest request) { try { // 1. 调用统一下单接口 Map<String, String> unifiedOrderResult = unifiedOrder(request); if (!"SUCCESS".equals(unifiedOrderResult.get("return_code")) || !"SUCCESS".equals(unifiedOrderResult.get("result_code"))) { throw new RuntimeException("统一下单失败: " + unifiedOrderResult.get("return_msg")); } String prepayId = unifiedOrderResult.get("prepay_id"); // 2. 构造前端支付参数 return buildPaymentParams(prepayId); } catch (Exception e) { throw new RuntimeException("获取支付参数失败", e); } } /** * 调用统一下单接口 */ private Map<String, String> unifiedOrder(UnifiedOrderRequest request) { try { Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", wechatPayUtil.generateNonceStr()); params.put("body", request.getBody()); params.put("out_trade_no", request.getOutTradeNo()); params.put("total_fee", request.getTotalFee().toString()); params.put("spbill_create_ip", "127.0.0.1"); // 可根据实际获取用户IP params.put("notify_url", wechatPayConfig.getNotifyUrl()); params.put("trade_type", "JSAPI"); params.put("openid", request.getOpenid()); // 生成签名 String sign = wechatPayUtil.generateSign(params, wechatPayConfig.getMchKey()); params.put("sign", sign); // 转换为XML String xmlData = wechatPayUtil.mapToXml(params); // 发送HTTP请求 try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost httpPost = new HttpPost(wechatPayConfig.getUnifiedOrderUrl()); httpPost.setEntity(new StringEntity(xmlData, "UTF-8")); httpPost.setHeader("Content-Type", "application/xml"); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseXml = EntityUtils.toString(response.getEntity(), "UTF-8"); return wechatPayUtil.xmlToMap(responseXml); } } } catch (Exception e) { throw new RuntimeException("统一下单接口调用失败", e); } } /** * 构造前端支付参数 */ private PaymentParamsResponse buildPaymentParams(String prepayId) { String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); String nonceStr = wechatPayUtil.generateNonceStr(); String packageValue = "prepay_id=" + prepayId; Map<String, String> signParams = new HashMap<>(); signParams.put("appId", wechatPayConfig.getAppId()); signParams.put("timeStamp", timeStamp); signParams.put("nonceStr", nonceStr); signParams.put("package", packageValue); signParams.put("signType", "MD5"); String paySign = wechatPayUtil.generateSign(signParams, wechatPayConfig.getMchKey()); PaymentParamsResponse response = new PaymentParamsResponse(); response.setAppId(wechatPayConfig.getAppId()); response.setTimeStamp(timeStamp); response.setNonceStr(nonceStr); response.setPackageValue(packageValue); response.setSignType("MD5"); response.setPaySign(paySign); return response; } }
6. 控制器类
创建REST控制器 WechatPayController.java:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/wxpay") @CrossOrigin(origins = "*") // 根据实际情况配置跨域 public class WechatPayController { @Autowired private WechatPayService wechatPayService; /** * 获取支付参数接口 */ @PostMapping("/get-payment-params") public ApiResponse<PaymentParamsResponse> getPaymentParams(@RequestBody UnifiedOrderRequest request) { try { PaymentParamsResponse paymentParams = wechatPayService.getPaymentParams(request); return ApiResponse.success(paymentParams); } catch (Exception e) { return ApiResponse.error(e.getMessage()); } } /** * 支付结果回调接口(异步通知) */ @PostMapping("/notify") public String paymentNotify(@RequestBody String notifyData) { try { Map<String, String> notifyMap = wechatPayUtil.xmlToMap(notifyData); // 验证签名 Map<String, String> signMap = new HashMap<>(notifyMap); String sign = signMap.remove("sign"); String calculatedSign = wechatPayUtil.generateSign(signMap, wechatPayConfig.getMchKey()); if (!calculatedSign.equals(sign)) { return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>"; } // 处理业务逻辑 if ("SUCCESS".equals(notifyMap.get("return_code"))) { String outTradeNo = notifyMap.get("out_trade_no"); String transactionId = notifyMap.get("transaction_id"); // TODO: 更新订单状态为已支付 // orderService.updateOrderStatus(outTradeNo, OrderStatus.PAID, transactionId); return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; } else { return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[业务失败]]></return_msg></xml>"; } } catch (Exception e) { return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理失败]]></return_msg></xml>"; } } } // 统一的API响应格式 class ApiResponse<T> { private int code; private String message; private T data; public static <T> ApiResponse<T> success(T data) { ApiResponse<T> response = new ApiResponse<>(); response.code = 0; response.message = "success"; response.data = data; return response; } public static <T> ApiResponse<T> error(String message) { ApiResponse<T> response = new ApiResponse<>(); response.code = -1; response.message = message; return response; } // Getter 和 Setter public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
7. 使用示例
前端调用示例:
// 前端调用后端接口获取支付参数 async function getPaymentParams() { const requestData = { out_trade_no: 'ORDER_' + Date.now(), // 生成订单号 total_fee: 100, // 1元 = 100分 body: '测试商品', openid: '用户的openid' // 必须从微信授权获取 }; try { const response = await fetch('/api/wxpay/get-payment-params', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }); const result = await response.json(); if (result.code === 0) { // 调用微信支付 callWxPay(result.data); } else { alert('获取支付参数失败: ' + result.message); } } catch (error) { console.error('请求失败:', error); } }
步骤 3:前端调用支付(JavaScript)
前端从你的后端接口获取参数,并调用JS-SDK的支付方法。
// 假设你已经从后端获取到了支付参数 payParams // payParams 结构: { appId, timeStamp, nonceStr, package, signType, paySign } // 首先需要配置JS-SDK(通常需要先通过后端接口获取jsapi_ticket并进行配置,这里省略) // 直接调用支付 function callWxPay(payParams) { if (typeof WeixinJSBridge === "undefined") { alert("请在微信客户端中打开"); return; } WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId": payParams.appId, "timeStamp": payParams.timeStamp, "nonceStr": payParams.nonceStr, "package": payParams.package, "signType": payParams.signType, "paySign": payParams.paySign }, function(res) { // 支付成功或失败后的回调 if (res.err_msg == "get_brand_wcpay_request:ok") { alert("支付成功!"); // 跳转到成功页面 } else { alert("支付失败: " + res.err_msg); // 处理失败逻辑 } } ); } // 或者使用官方推荐的写法(需要先config) wx.ready(function() { // 假设点击按钮触发支付 document.getElementById('pay-button').onclick = function() { wx.chooseWXPay({ timestamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: payParams.signType, paySign: payParams.paySign, success: function (res) { // 支付成功后的回调 if (res.errMsg === "chooseWXPay:ok") { alert("支付成功!"); } }, fail: function(err) { console.error("支付调用失败", err); } }); }; });
方法二:使用更底层的 WeixinJSBridge(传统方式)
这种方式不需要引入额外的JS-SDK,直接使用微信内置的对象。
// 前端JavaScript function onBridgeReady(payParams) { WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId": payParams.appId, "timeStamp": payParams.timeStamp, "nonceStr": payParams.nonceStr, "package": payParams.package, "signType": payParams.signType, "paySign": payParams.paySign }, function(res) { if (res.err_msg == "get_brand_wcpay_request:ok") { // 支付成功 alert('支付成功!'); } else { // 支付失败 alert('支付失败: ' + res.err_msg); } } ); } // 调用支付 function callPayment(payParams) { if (typeof WeixinJSBridge == "undefined") { if( document.addEventListener ) { document.addEventListener('WeixinJSBridgeReady', function() { onBridgeReady(payParams); }, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', function() { onBridgeReady(payParams); }); document.attachEvent('onWeixinJSBridgeReady', function() { onBridgeReady(payParams); }); } } else { onBridgeReady(payParams); } }
关键注意事项
OpenID是必须的:JSAPI支付必须提供用户的OpenID,这意味着你的用户需要先进行微信授权登录。
签名算法:前后端生成签名时,参数顺序、空值处理、key拼接必须严格按照微信要求,否则会报“签名错误”。
金额单位:total_fee 单位是分,而且是整数(如1元 = 100)。
异步通知(Notify):支付成功后,微信服务器会异步通知你提供的 notify_url,你需要在后端验证通知并处理业务逻辑(如更新订单状态)。
调试:在微信开发者工具或微信浏览器中,开启调试模式(地址栏输入 debugx5.qq.com)可以查看详细的错误信息。
建议优先使用 方法一(JS-SDK),这是微信官方当前主推的方式,兼容性和未来维护性更好。