杰瑞科技汇

Java app微信支付接口如何集成?

  1. 准备工作:注册商户、获取关键参数。
  2. 核心概念:APIv3、APIv3密钥、签名、证书。
  3. 开发流程:从下单到查询、退款的全过程。
  4. 代码示例:使用 Java 和 httpclient/OkHttp 进行签名和请求。
  5. 常见问题与最佳实践

准备工作

在写任何代码之前,你必须在微信支付商户平台完成以下配置:

  1. 注册微信支付商户

  2. 获取 API 密钥(APIv3 Key)

    • 登录商户平台 -> 账户中心 -> API安全
    • APIv3密钥 栏目,设置并获取你的密钥,这个密钥至关重要,用于生成和验证签名,请务必妥善保管
  3. 申请产品并添加支付方式

    • 产品中心 -> 我的产品 中,确保你已经开通了 “JSAPI 支付”“Native 支付” 等你需要的支付产品。
    • JSAPI 支付通常需要用户的 openid,而 Native 支付则生成一个二维码。
  4. 获取商户证书

    • API安全 -> 证书管理 中,下载你的 商户API证书(一个 .p12 文件)。
    • 你还需要一个 API证书序列号,这个在证书管理页面可以找到。
    • 注意:证书是双向验证的关键,微信服务器会验证你的身份,你也需要验证微信服务器的身份。

核心概念

APIv3

微信支付官方推荐的API版本,相比旧版APIv2,它有诸多优点:

  • 更安全:强制使用 HTTPS,签名算法更严谨(使用 HMAC-SHA256),支持双向证书认证。
  • 更规范:所有请求和响应都使用 JSON 格式,数据结构清晰。
  • 更易用:提供了官方的各语言版本 SDK,但底层原理理解起来也不复杂。

APIv3 Key

一个32位的字符串,用于对请求体进行签名,以及在接收微信回调时验证签名,它不用于数据加密,仅用于签名。

签名

微信支付为了保证请求的完整性和防篡改,要求每个请求都必须带有签名,签名过程如下:

  1. 拼接:HTTP方法 + URL路径 + 请求时间戳 + 请求随机串 + 请求体
  2. 加密:使用你的 APIv3 Key 对上述拼接的字符串进行 HMAC-SHA256 加密。
  3. 编码:将加密后的结果进行 Base64 编码,得到最终的签名串。

证书

  • 商户证书:用于向微信服务器证明“我是我”,在调用某些敏感接口或接收回调时需要。
  • 平台证书:微信服务器的证书,用于解密微信回调通知中的敏感数据(如支付金额),你需要从微信提供的下载地址获取最新的平台证书。

开发流程(以 JSAPI 支付为例)

JSAPI 支付适用于在微信内置浏览器或小程序中发起支付,最终由用户使用微信支付完成。

统一下单

这是支付流程的第一步,你的服务器需要调用微信的统一下单接口,创建一个预支付交易单。

请求参数准备

  • appid: 你的应用ID(公众号或小程序的AppID)。
  • mchid: 你的商户号。
  • description: 商品描述。
  • out_trade_no: 你的系统内部订单号,必须唯一。
  • notify_url: 支付结果接收异步通知的URL。
  • amount: 订单金额,单位为
  • payer: 支付者信息,对于JSAPI支付,必须包含 openid

发起请求

  • URL: https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
  • 方法: POST
  • 请求头: 必须包含 Authorization (签名信息) 和 Content-Type: application/json
  • 请求体: 上述参数的 JSON 对象。

处理响应

  • 如果下单成功,微信会返回一个预支付交易ID prepay_id
  • 你的服务器需要使用这个 prepay_id 和其他参数,按照微信规定的格式,生成一个最终传递给前端(小程序/H5)的支付参数包。

生成前端支付参数

下单成功后,你需要将以下参数返回给前端:

  • appId: 你的AppID。
  • timeStamp: 当前时间戳(秒)。
  • nonceStr: 随机字符串。
  • package: 格式为 prepay_id=xxxx 的字符串。
  • signType: 签名算法,固定为 RSAMD5,根据你的配置来,通常用 RSA

前端拿到这些参数后,调用微信的 wx.requestPayment 方法即可拉起支付。

处理支付结果通知

支付完成后,微信服务器会主动向你在 notify_url 中设置的地址发送一个 POST 请求,通知支付结果。

验证签名

  • 接收到请求后,第一步必须是验证签名,确保请求确实来自微信。
  • 签名信息在请求头 Wechatpay-TimestampWechatpay-NonceWechatpay-Signature 中。
  • 验证逻辑与发起请求时类似,但数据源是请求头和请求体。

解密回调数据

  • 回调的请求体是加密的,解密需要用到 APIv3 Key平台证书
  • 解密步骤:
    1. 从请求头 Wechatpay-Serial 中获取微信平台证书序列号。
    2. 根据序列号找到对应的平台证书(你需要提前下载并缓存)。
    3. 使用证书中的公钥,对请求体中的 resource 字段进行解密(AES-256-GCM 算法)。
    4. 解密后得到 JSON 格式的支付结果数据。

业务处理

  • 验证签名和解密成功后,解析支付结果数据(如 out_trade_no, transaction_id, trade_state 等)。
  • 根据支付状态(如 SUCCESS)更新你系统中的订单状态。
  • 必须向微信服务器返回一个应答,格式为 XML<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>,微信服务器收到这个应答后,才停止重发通知。

查询订单

由于网络等原因,你可能会收不到异步通知,提供一个主动查询订单状态的接口是必要的。

  • 调用 https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no} 接口。
  • 需要提供你的 out_trade_nomchid

退款

如果需要退款,调用退款接口。

  • URL: https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
  • 同样需要签名,并传入 out_trade_no, out_refund_no, amount 等参数。

Java 代码示例

下面是一个简化的 Java 示例,展示如何生成签名并发起统一下单请求,这里使用 httpclientcommons-codec

添加 Maven 依赖

<dependencies>
    <!-- httpclient for sending requests -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    <!-- commons codec for base64 and sha256 -->
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
    </dependency>
    <!-- For json processing -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.0</version>
    </dependency>
</dependencies>

签名工具类

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.UUID;
public class WeChatPayUtil {
    // 你的配置
    private static final String MCHID = "your_mch_id";
    private static final String API_V3_KEY = "your_api_v3_key";
    private static final String APPID = "your_app_id";
    private static final String CERT_PATH = "path/to/your/apiclient_cert.p12"; // 商户证书路径
    private static final String CERT_PASSWORD = "your_p12_password"; // p12文件密码,默认是商户号
    /**
     * 生成签名
     * @param method HTTP方法 (GET, POST等)
     * @param url 请求路径 (不包含域名和查询参数)
     * @param timestamp 时间戳
     * @param nonceStr 随机串
     * @param body 请求体
     * @return 签名字符串
     * @throws Exception
     */
    public static String generateSignature(String method, String url, String timestamp, String nonceStr, String body) throws Exception {
        // 1. 拼接字符串
        String message = method + "\n" +
                url + "\n" +
                timestamp + "\n" +
                nonceStr + "\n" +
                body + "\n";
        // 2. 使用APIv3 Key进行HMAC-SHA256加密
        byte[] digest = DigestUtils.hmacSha256(message.getBytes(StandardCharsets.UTF_8), API_V3_KEY.getBytes(StandardCharsets.UTF_8));
        // 3. Base64编码
        return Base64.encodeBase64String(digest);
    }
    /**
     * 创建带签名的HTTP请求头
     * @param url 请求URL
     * @param body 请求体JSON
     * @return 包含Authorization的请求头
     * @throws Exception
     */
    public static String createAuthorizationHeader(String url, String body) throws Exception {
        String method = "POST";
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String signature = generateSignature(method, url, timestamp, nonceStr, body);
        // 组装Authorization头
        return "WECHATPAY2-SHA256-RSA2048 " +
                "mchid=\"" + MCHID + "\"," +
                "serial_no=\"your_mch_cert_serial_number\"," + // 你的商户证书序列号
                "timestamp=\"" + timestamp + "\"," +
                "nonce_str=\"" + nonceStr + "\"," +
                "signature=\"" + signature + "\"";
    }
    /**
     * 发起统一下单请求
     * @param orderInfo 订单信息对象
     * @return 微信响应
     * @throws Exception
     */
    public static String createOrder(WeChatPayOrderRequest orderInfo) throws Exception {
        String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
        String jsonBody = new ObjectMapper().writeValueAsString(orderInfo);
        // 创建HTTP客户端 (使用商户证书进行双向认证)
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream instream = new FileInputStream(new File(CERT_PATH))) {
            keyStore.load(instream, CERT_PASSWORD.toCharArray());
        }
        SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, CERT_PASSWORD.toCharArray())
                .build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslContext, new String[] {"TLSv1.2"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        try (CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build()) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setHeader("Authorization", createAuthorizationHeader(url, jsonBody));
            httpPost.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
            try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
                HttpEntity entity = response.getEntity();
                String responseString = EntityUtils.toString(entity, StandardCharsets.UTF_8);
                EntityUtils.consume(entity); // 确保实体被完全消费
                return responseString;
            }
        }
    }
    // 示例订单请求对象
    public static class WeChatPayOrderRequest {
        private String appid;
        private String mchid;
        private String description;
        private String out_trade_no;
        private String notify_url;
        private Amount amount;
        private Payer payer;
        // Getters and Setters...
        public static class Amount {
            private int total; // 单位:分
            // Getters and Setters...
        }
        public static class Payer {
            private String openid;
            // Getters and Setters...
        }
    }
    public static void main(String[] args) {
        try {
            WeChatPayOrderRequest order = new WeChatPayOrderRequest();
            order.setAppid(APPID);
            order.setMchid(MCHID);
            order.setDescription("测试商品");
            order.setOut_trade_no("ORDER_" + System.currentTimeMillis());
            order.setNotify_url("https://your-domain.com/notify");
            WeChatPayOrderRequest.Amount amount = new WeChatPayOrderRequest.Amount();
            amount.setTotal(1); // 1元 = 100分
            order.setAmount(amount);
            WeChatPayOrderRequest.Payer payer = new WeChatPayOrderRequest.Payer();
            payer.setOpenid("user_openid_here"); // 替换为用户的openid
            order.setPayer(payer);
            String response = createOrder(order);
            System.out.println("下单响应: " + response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意

  • 上述代码是一个简化示例,实际项目中你需要更完善的错误处理、日志记录和对象映射(如使用 Lombok 或手动编写 Getters/Setters)。
  • 平台证书的下载和缓存逻辑也需要额外实现,用于解密回调。

最佳实践与建议

  1. 使用官方 SDK:虽然理解原理很重要,但在生产环境中,强烈建议使用微信支付官方提供的 Java SDK,它封装了签名、证书、加解密等所有复杂细节,能让你更专注于业务逻辑,官方 SDK 的 GitHub 地址:wechatpay-java

  2. 异步通知是关键:务必正确实现和验证支付回调,这是保证你系统数据一致性的最重要一环。先验证签名,再解密数据,最后处理业务并返回成功应答

  3. 幂等性设计:对于下单、退款等操作,你的业务接口必须是幂等的,即使用户重复请求,也应该返回同一个结果,避免重复创建订单或重复退款,可以使用 out_trade_noout_refund_no 作为幂等键。

  4. 日志记录:详细记录与微信支付交互的请求和响应日志,包括签名、请求体、响应内容等,这对于排查问题至关重要。

  5. 环境隔离:微信支付有沙箱环境和正式环境,开发时务必使用沙箱环境进行测试,避免产生真实交易,切换环境时,只需要修改请求的 URL(沙箱 URL: https://api.mch.weixin.qq.com/sandboxnew/...)和 API Key/证书即可。

  6. 金额单位:微信支付 API 中,金额单位统一为 ,在代码中务必注意转换,避免因单位错误导致严重的资金问题。

希望这份详细的指南能帮助你顺利集成微信支付!

分享:
扫描分享到社交APP
上一篇
下一篇