小程序微信支付APIV3使用官方SDK教程

序:  

 公司业务涉及到打通微信支付,网上查了很多,发现要么太老,要么不可用,踩了无数坑之后终于搞定,只需要自己去商户后台申请之后替换配置,复制代码即可使用。

微信小程序支付本文使用【微信支付 APIv3 Java SDK(wechatpay-java) 】实现。
官方微信支付 APIv3 Java SDK GitHub - wechatpay-apiv3/wechatpay-java: 微信支付 APIv3 的官方 Java Library官网接口文档:开发指引_小程序支付|微信支付商户文档中心

该sdk无需进行繁琐的验签,httpClint等,就适合我这种懒人。

前置条件:
Java 1.8+。
成为微信支付商户。
商户 API 证书:指由商户申请的,包含证书序列号、商户的商户号、公司名称、公钥信息的证书。
商户 API 私钥:商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。
APIv3 密钥:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了 AES-256-GCM 加密。APIv3 密钥是加密时使用的对称密钥。

apiv3公钥id:

apiv3公钥证书

支付打通流程图

重点步骤说明:

步骤4: 用户下单发起支付,商户可通过JSAPI下单创建支付订单。

步骤9: 商户小程序内使用小程序调起支付API(wx.requestPayment)发起微信支付,详见小程序API文档 (opens new window)。

步骤16: 用户支付成功后,商户可接收到微信支付支付结果通知支付通知API。

步骤21: 商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。

具体实现步骤如下
1.前端弹框发起支付,调用【预支付订单/统一下单(/wxMiniappPay/createOrder)】接口取得预支付参数。
2.前端将上一步获得的参数,在小程序中调用支付API(wx.requestPayment)发起微信支付,用户输入支付密码后即可成功支付。
3.用户成功支付后,微信将通过【支付回调(/wxMiniappPay/payNotify)】接口推送支付成功的信息给后端,后端根据业务进行操作即可。
4.前端在用户输入密码支付后,调用【根据商户订单号查询订单(/wxMiniappPay/queryOrderByOutTradeNo)】接口,查看支付信息,按照自己的业务进行信息展示。
5.后端通过定时任务,调用【关闭订单(/wxMiniappPay/closeOrder)】接口,对未成功支付的订单进行关闭。
6.如果需要退款操作,前端调用【退款申请(/wxMiniappPay/refund)】接口,进行退款操作。
7.成功退款后,微信会通过【微信小程序退款回调】接口,推送退款状态信息,后端同步退款状态。
8.后端通过定时任务,调用【查询单笔退款(通过商户退款单号)(/wxMiniappPay/queryByOutRefundNo)】接口,同步退款状态。

微信小程序支付实现

pom.xml加入以下依赖 (注:sdk包版本尽量一致,0.2.12一下不支持apiv3)

		<!--微信支付-->
		<dependency>
			<groupId>com.github.wechatpay-apiv3</groupId>
			<artifactId>wechatpay-java</artifactId>
			<version>0.2.15</version>
		</dependency>

application.yml配置

证书路径如图

wx:
  miniapp:
    appid: wx7*******451 # 微信小程序appid
    secret: a4******0bacb86870 # 微信小程序密钥
    merchantId: 16******* # 商户号
    privateKeyPath: *****\apiclient_key.pem # 商户API私钥路径(测试环境)绝对路径
    merchantSerialNumber: 3**************** # 商户API证书序列号
    apiV3Key: *************** # 商户APIV3密钥
    payNotifyUrl:yuming/app/order/pay/CallBack # 支付通知地址(测试环境)
    refundNotifyUrl: yuming/wxMiniappPay/refundNotify # 退款通知地址(测试环境)
    publicKeyId: 'PUB_KEY_ID_*********' # 商户API公钥ID
    publicKeyPath: ********\pub_key.pem # 商户API公钥路径

微信小程序支付配置WxPayConfig 

package com.ruoyi.common.weixin;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * <p>
 * 微信小程序支付配置
 * </p>
 *
 * @author songfayuan
 * @date 2024/9/30 15:59
 */
@Data
@Component
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxPayConfig {
    /**
     * 微信小程序的 AppID
     */
    private String appid;

    /**
     * 微信小程序的密钥
     */
    private String secret;

    /**
     * 商户号
     */
    private String merchantId;

    /**
     * 商户API私钥路径
     */
    private String privateKeyPath;

    /**
     * 商户证书序列号
     */
    private String merchantSerialNumber;

    /**
     * 商户APIV3密钥
     */
    private String apiV3Key;

    /**
     * 支付通知地址
     */
    private String payNotifyUrl;

    /**
     * 退款通知地址
     */
    private String refundNotifyUrl;

    private String publicKeyId;

    private String publicKeyPath;
}

微信支付证书自动更新配置WxPayAutoCertificateConfig

注意:启动时,该配置类如果出现【无可用的平台证书,请在商户平台-API安全申请使用微信支付公钥。可查看指引】异常,GitHub - wechatpay-apiv3/wechatpay-java: 微信支付 APIv3 的官方 Java Library

可去查看微信官方SDK代码,注意这个类

package com.ruoyi.common.weixin;


import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.RSAPublicKeyConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * <p>
 * 微信支付证书自动更新配置
 * 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
 * </p>
 *
 * @author songfayuan
 * @date 2024/9/30 15:57
 */
@Slf4j
@Configuration
public class WxPayAutoCertificateConfig {
    @Resource
    private WxPayConfig wxPayConfig;

    /**
     * 初始化商户配置
     *
     * @return
     */
    @Bean
    public Config rsaAutoCertificateConfig() {
        // 这里把Config作为配置Bean是为了避免多次创建资源,一般项目运行的时候这些东西都确定了
        // 具体的参数改为申请的数据,可以通过读配置文件的形式获取
        Config config = new RSAPublicKeyConfig.Builder()
            .merchantId(wxPayConfig.getMerchantId())
            .privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
            .publicKeyFromPath(wxPayConfig.getPublicKeyPath())
            .publicKeyId(wxPayConfig.getPublicKeyId())
            .merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
            .apiV3Key(wxPayConfig.getApiV3Key())
            .build();


//        Config config = new RSAAutoCertificateConfig.Builder()
//                .merchantId(wxPayConfig.getMerchantId())
//                .privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
//                .merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
//                .apiV3Key(wxPayConfig.getApiV3Key())
//                .build();
        log.info("初始化微信支付商户配置完成...");
        return config;
    }
}

业务代码数据敏感,这里就不贴了,贴个相关的示例调用代码吧,根据自己业务集成一下

package com.wechat.pay.java.service.payments.jsapi;

import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByIdRequest;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.model.Transaction;

public class JsapiServiceExtensionExample {
  /** 商户号 */
  public static String merchantId = "190000****";

  /** 商户API私钥路径 */
  public static String privateKeyPath = "/Users/yourname/your/path/apiclient_key.pem";

  /** 商户证书序列号 */
  public static String merchantSerialNumber = "5157F09EFDC096DE15EBE81A47057A72********";

  /** 商户APIV3密钥 */
  public static String apiV3Key = "...";

  public static JsapiServiceExtension service;

  public static void main(String[] args) {
    // 初始化商户配置
    Config config =
        new RSAAutoCertificateConfig.Builder()
            .merchantId(merchantId)
            // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
            .privateKeyFromPath(privateKeyPath)
            .merchantSerialNumber(merchantSerialNumber)
            .apiV3Key(apiV3Key)
            .build();
    // 初始化服务
    service =
        new JsapiServiceExtension.Builder()
            .config(config)
            .signType("RSA") // 不填默认为RSA
            .build();
    try {
      // ... 调用接口
      PrepayWithRequestPaymentResponse response = prepayWithRequestPayment();
      System.out.println(response);
    } catch (HttpException e) { // 发送HTTP请求失败
      // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义
    } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
      // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
    } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
      // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义
    }
  }

  /** 关闭订单 */
  public static void closeOrder() {

    CloseOrderRequest request = new CloseOrderRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    service.closeOrder(request);
  }

  /** JSAPI支付下单,并返回JSAPI调起支付数据 */
  public static PrepayWithRequestPaymentResponse prepayWithRequestPayment() {
    PrepayRequest request = new PrepayRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    return service.prepayWithRequestPayment(request);
  }

  /** 微信支付订单号查询订单 */
  public static Transaction queryOrderById() {

    QueryOrderByIdRequest request = new QueryOrderByIdRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    return service.queryOrderById(request);
  }

  /** 商户订单号查询订单 */
  public static Transaction queryOrderByOutTradeNo() {

    QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    return service.queryOrderByOutTradeNo(request);
  }
}

相关工具类

package com.ruoyi.common.weixin;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

public class OrderNoGenerator {

    // 平台标识(WX-微信,ALI-支付宝)
    private static final String PLATFORM_PREFIX = "WX_REFUND_";

    // 时间戳格式(精确到毫秒)
    private static final SimpleDateFormat DATE_FORMAT =
        new SimpleDateFormat("yyyyMMddHHmmssSSS");
    private static final String ORDER_NO_PREFIX = "WP_"; // 微信预支付订单号前缀
    private static final int RANDOM_NUMBER_LENGTH = 4; // 随机数长度

    // 序列号(1000-9999循环)
    private static final AtomicInteger SEQ = new AtomicInteger(0);
    // 日期格式:两位年+月日+时分秒+两位毫秒(共14位)
    private static final DateTimeFormatter DATE_FMT =
        DateTimeFormatter.ofPattern("yyMMddHHmmssSS");
    private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai");

    /**
     * 生成微信预支付下单入参 orderNo
     *
     * @return 生成的唯一订单号
     */
    public static String generateOrderNo() {
        // 获取当前时间戳
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String timestamp = dateFormat.format(new Date());

        // 生成随机数
        Random random = new Random();
        StringBuilder randomNumber = new StringBuilder();
        for (int i = 0; i < RANDOM_NUMBER_LENGTH; i++) {
            randomNumber.append(random.nextInt(10));
        }

        // 组合前缀、时间戳和随机数
        return ORDER_NO_PREFIX + timestamp + randomNumber.toString();
    }

    /**
     * 生成全局唯一退款单号
     *
     * @param platform 支付平台标识(如:WX/ALI)
     */
    public static String generateOutRefundNo(String platform) {
        // 1. 平台标识(防多平台冲突)
        String prefix = platform.toUpperCase() + "_";

        // 2. 时间戳(17位)
        String timestamp = DATE_FORMAT.format(new Date());

        // 3. 随机数(3位防并发重复)
        String random = String.format("%03d",
            ThreadLocalRandom.current().nextInt(1000));

        return prefix + timestamp + random;
    }


    public static String generate() {
        LocalDateTime now = LocalDateTime.now(ZONE);
        String datePart = now.format(DATE_FMT);  // 示例:25030615304512(14位)
        // 序列号取模运算保证范围1000-9999
        int seq = SEQ.getAndIncrement() % 9000 + 1000;
        return datePart + String.format("%04d", seq);
    }


}

业务逻辑相对就简单了,自己结合实际业务调用,这个sdk集成了验签等,方便很多。

大佬们有指正的地方,欢迎评论区交流~

以下是微信小程序支付ApiV3版本java开发的完整案例: 1. 首先,你需要在微信商户平台上创建一个应用并获取到以下信息: - 商户号(mch_id) - 商户APIv3密钥(api_key) - 应用ID(appid) - 商户证书私钥文件(apiclient_key.pem) - 商户证书文件(apiclient_cert.pem) - 微信支付平台证书文件(weixinpay_cert.pem) 2. 接下来,你需要使用Java SDK进行开发,包括以下步骤: - 导入相关的Java SDK依赖包。 - 构建请求参数,包括HTTP请求头和请求体,其中请求体需要使用商户证书进行签名。 - 发送HTTP请求,并处理响应数据。 以下是一个简单的Java代码片段,演示如何使用Java SDK实现微信小程序支付ApiV3版本: ``` import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayConstants; import com.github.wxpay.sdk.WXPayUtil; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; public class WxPayApiV3Demo { private static final String API_KEY = "商户APIv3密钥"; private static final String MCH_ID = "商户号"; private static final String APP_ID = "应用ID"; private static final String API_BASE_URL = "https://api.mch.weixin.qq.com/"; private static final String PRIVATE_KEY_FILE = "apiclient_key.pem"; private static final String CERT_FILE = "apiclient_cert.pem"; private static final String WEIXINPAY_CERT_FILE = "weixinpay_cert.pem"; private static final String CHARSET = StandardCharsets.UTF_8.name(); private static final String API_PAY = "v3/pay/transactions/jsapi"; private static final String API_CERTIFICATE = "v3/certificates"; private static PrivateKey privateKey; private static X509Certificate certificate; private static X509Certificate weixinpayCertificate; static { try { privateKey = loadPrivateKey(new File(PRIVATE_KEY_FILE)); certificate = loadCertificate(new File(CERT_FILE)); weixinpayCertificate = loadCertificate(new File(WEIXINPAY_CERT_FILE)); } catch (Exception e) { throw new RuntimeException("Failed to load certificate.", e); } } public static void main(String[] args) throws Exception { // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", APP_ID); params.put("mchid", MCH_ID); params.put("description", "测试商品"); params.put("out_trade_no", "1234567890"); params.put("amount", "{\"total\":100,\"currency\":\"CNY\"}"); params.put("payer", "{\"openid\":\"用户openid\"}"); params.put("notify_url", "https://example.com/notify"); // 发送HTTP请求 String response = doPost(API_PAY, params); System.out.println(response); } private static String doPost(String api, Map<String, String> params) throws Exception { String url = API_BASE_URL + api; String timestamp = String.valueOf(System.currentTimeMillis() / 1000); // 构建请求头 Map<String, String> headers = new HashMap<>(); headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); headers.put(HttpHeaders.AUTHORIZATION, buildAuthorizationHeader(api, timestamp, params)); // 构建请求体 String requestBody = WXPayUtil.mapToXml(params); String signature = sign(requestBody, timestamp); String signedRequestBody = buildSignedRequestBody(requestBody, signature); // 发送HTTP请求 HttpPost httpPost = new HttpPost(url); headers.forEach(httpPost::addHeader); httpPost.setEntity(new StringEntity(signedRequestBody, CHARSET)); try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) { HttpEntity entity = httpResponse.getEntity(); return IOUtils.toString(entity.getContent(), CHARSET); } } private static String buildAuthorizationHeader(String api, String timestamp, Map<String, String> params) throws Exception { String nonceStr = WXPayUtil.generateUUID(); String message = buildMessage(api, timestamp, nonceStr, params); String signature = sign(message); return String.format("WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%s\",serial_no=\"%s\",signature=\"%s\"", MCH_ID, nonceStr, timestamp, weixinpayCertificate.getSerialNumber().toString(16), signature); } private static String buildMessage(String api, String timestamp, String nonceStr, Map<String, String> params) { StringBuilder sb = new StringBuilder(); sb.append("POST\n") .append(API_BASE_URL).append(api).append("\n") .append(buildQueryString(params)).append("\n") .append(HttpHeaders.CONTENT_TYPE).append(":").append(ContentType.APPLICATION_JSON.getMimeType()).append("\n") .append(HttpHeaders.ACCEPT).append(":").append(ContentType.APPLICATION_JSON.getMimeType()).append("\n") .append(HttpHeaders.AUTHORIZATION).append(":").append(buildAuthorizationHeader(api, timestamp, params)).append("\n") .append("Wechatpay-Timestamp:").append(timestamp).append("\n") .append("Wechatpay-Nonce:").append(nonceStr).append("\n") .append("Wechatpay-Serial:").append(weixinpayCertificate.getSerialNumber().toString(16)).append("\n"); return sb.toString(); } private static String buildQueryString(Map<String, String> params) { StringBuilder sb = new StringBuilder(); params.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .forEach(entry -> { if (sb.length() > 0) { sb.append("&"); } sb.append(entry.getKey()).append("=").append(entry.getValue()); }); return sb.toString(); } private static String sign(String message) throws Exception { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(message.getBytes(StandardCharsets.UTF_8)); return Base64.encodeBase64String(signature.sign()); } private static String sign(String requestBody, String timestamp) throws Exception { StringBuilder sb = new StringBuilder(); sb.append(timestamp).append("\n") .append(nonceStr()).append("\n") .append(requestBody).append("\n"); return sign(sb.toString()); } private static String buildSignedRequestBody(String requestBody, String signature) { return requestBody.replace("</xml>", String.format("<sign_type><![CDATA[SHA256]]></sign_type><sign><![CDATA[%s]]></sign></xml>", signature)); } private static String nonceStr() { return WXPayUtil.generateUUID(); } private static PrivateKey loadPrivateKey(File file) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { byte[] bytes = FileUtils.readFileToByteArray(file); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(spec); } private static X509Certificate loadCertificate(File file) throws IOException, CertificateException { byte[] bytes = FileUtils.readFileToByteArray(file); return WXPayUtil.loadCertificate(bytes); } } ``` 上述代码中,我们使用微信提供的Java SDK(https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient),它封装了HTTP请求和签名验证的相关逻辑,并提供了一些实用的工具类。在这个示例中,我们使用了WXPayUtil工具类来进行签名、验签和XML格式的转换。我们还使用了Apache HttpClient来发送HTTP请求。 需要注意的是,该示例中使用的是微信提供的Java SDK中的最新版本,如果你使用的是旧版本,可能会存在一些差异。 此外,该示例中只演示了如何实现支付接口,如果你需要实现其他接口,可以参考微信官方文档(https://wechatpay-api.gitbook.io/wechatpay-api-v3/)。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值