SpringBoot对接微信支付之JSAPI

分享SpringBoot整合微信公众号支付项目,对接微信JSAPI支付类型遇到的问题和过程封装的工具类,目前已正常使用,有问题大家评论区互动哈,有需要源码的可以私信我。

1、创建SpringBoot项目

自行创建简单SpringBootDemo

2、映入微信支付maven依赖

        <!-- Mybatis-plus 依赖配置 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <!-- 微信支付api v3 -->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>
        <!-- 引入hutool工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.2.4</version>
        </dependency>

3、在bootstrap.yam映入配置信息

# 微信支付相关
wxPay:
  appId: 微信支付APPID
  ## jsApi支付相关
  jsApi:
    ## 商户账号
    mchId: 商户账号
    ## 商户API证书的证书序列号
    mchSerialNo: 商户API证书的证书序列号
    ## 商户API私钥
    apiV3Key: 商户API私钥
    ## 回调地址
    notifyUrl: 回调地址

4、封装JSAPI需要的工具类(我这边集成:jsapi下单、jsapi查单逻辑)

WxPayJsApiHttpUtil:jsapi接口工具类

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.ycg.pay.common.constant.WxPayJsApiConstant;
import com.ycg.pay.common.constant.WxPayJsApiHeaderConstant;
import com.ycg.pay.common.constant.WxPayJsApiOtherConstant;
import com.ycg.pay.model.bo.WxPayJsApiAmountBO;
import com.ycg.pay.model.bo.WxPayJsApiOrderBO;
import com.ycg.pay.model.bo.WxPayPayerBO;
import com.ycg.pay.model.vo.WxPayJsApiPreVO;
import lombok.RequiredArgsConstructor;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;

/**
 * @ClassName WxPayJsApiCreateSignUtil
 * @Description JSAPI工具类
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月21日 0021 上午 08:58:36
 * @Version 1.0
 */
@Component
@RefreshScope
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WxPayJsApiHttpUtil {

    private static Logger logger = LoggerFactory.getLogger(WxPayJsApiHttpUtil.class);
    private final WxPayJsApiSignUtil wxPayJsApiSignUtil;
    /**
     * appId
     **/
    @Value("${wxPay.appId}")
    String appId;
    /**
     * mchId
     **/
    @Value("${wxPay.jsApi.mchId}")
    String mchId;
    /**
     * mchSerialNo
     **/
    @Value("${wxPay.jsApi.mchSerialNo}")
    String mchSerialNo;
    /**
     * apiV3Key
     **/
    @Value("${wxPay.jsApi.apiV3Key}")
    String apiV3Key;
    /**
     * notifyUrl
     **/
    @Value("${wxPay.jsApi.notifyUrl}")
    String notifyUrl;

    /**
     * 操作 - 微信JS_API下单
     *
     * @return com.ycg.pay.model.vo.WxPayJsApiPreVO
     * @Author 俞春旺
     * @Date 下午 04:38:03 2023年4月22日 0022
     **/
    public WxPayJsApiPreVO jsApiV3() throws Exception {
        //请求URL
        HttpPost httpPost = new HttpPost(WxPayJsApiConstant.JS_API_URL);
        String out_trade_no = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN);
        // 请求body参数
        WxPayJsApiAmountBO amount = new WxPayJsApiAmountBO();
        amount.setTotal(1);
        amount.setCurrency("CNY");

        WxPayPayerBO payer = new WxPayPayerBO();
        payer.setOpenid("opneid");

        WxPayJsApiOrderBO request = new WxPayJsApiOrderBO();
        request.setMchid(mchId);
        request.setOut_trade_no(out_trade_no);
        request.setAttach("order_no_20230423");
        request.setAppid(appId);
        request.setDescription("练视频推送套餐");
        request.setNotify_url(notifyUrl);
        request.setAmount(amount);
        request.setPayer(payer);

        StringEntity entity = new StringEntity(JSONUtil.toJsonStr(request), "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader(WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT, WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT_VAL);

        // 构建 - 请求信息
        CloseableHttpClient closeableHttpClient = initCloseableHttpClientV3();
        // 操作 - 完成请求
        CloseableHttpResponse response = closeableHttpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println(bodyAsString);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } finally {
            response.close();
            closeableHttpClient.close();
        }
        // 构造 - 响应信息
        WxPayJsApiPreVO result = convertWxPayJsApiPreInfo((String) JSONUtil.parseObj(bodyAsString).get(WxPayJsApiOtherConstant.HTTP_OTHER_PREPAY_ID));
        System.out.println("result -> " + JSONUtil.toJsonStr(result));
        return result;
    }

    /**
     * 获取 - jsApi加密串及前端换起JSAPI支付界面
     *
     * @param prepayId
     * @return com.ycg.pay.model.vo.WxPayJsApiPreVO
     * @Author 俞春旺
     * @Date 下午 01:42:42 2023年4月21日 0021
     **/
    protected WxPayJsApiPreVO convertWxPayJsApiPreInfo(String prepayId) throws Exception {
        logger.info("[开始]预支付信息-prepayId : {}", prepayId);
        // 构建 - 相关参数
        String time = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String packageStr = StrUtil.format(WxPayJsApiConstant.SIGN_PACKAGE_STR, prepayId);
        // 构建 - 签名信息
        ArrayList<String> list = new ArrayList<>();
        list.add(appId);
        list.add(time);
        list.add(nonceStr);
        list.add(packageStr);

        // 操作 - 签名
        String packageSign = wxPayJsApiSignUtil.signV3(WxPayJsApiSignUtil.buildSignMessage(list).getBytes());

        // 构建 - 响应信息
        WxPayJsApiPreVO result = new WxPayJsApiPreVO();
        result.setWxAppId(appId);
        result.setWxTimeStamp(time);
        result.setWxNonceStr(nonceStr);
        result.setWxPackage(packageStr);
        result.setWxSignType(WxPayJsApiConstant.SIGN_TYPE);
        result.setWxPaySign(packageSign);
        logger.info("[结束]预支付信息-WxPayJsApiPreVO : {}", JSONUtil.toJsonStr(result));
        return result;
    }

    /**
     * 查看 - JSAPI微信支付订单号查询
     *
     * @param transaction_id 微信支付订单号
     * @return com.alibaba.fastjson.JSONObject
     * @Author 俞春旺
     * @Date 下午 04:24:44 2023年4月22日 0022
     **/
    public JSONObject queryJsApiOrderInfoV3(String transaction_id) throws HttpCodeException, GeneralSecurityException, NotFoundException, IOException, URISyntaxException {
        String url = StrUtil.format(WxPayJsApiConstant.TRANSACTIONS_ORDER_QUERY_URL, transaction_id, mchId);
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader(WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT, WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT_VAL);

        CloseableHttpClient httpClient = initCloseableHttpClientV3();
        CloseableHttpResponse response = httpClient.execute(httpGet);

        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println("bodyAsString:" + bodyAsString);
        return JSONUtil.parseObj(bodyAsString);
    }

    /**
     * 查询 - 实时获取resources实时新增的文件目录
     *
     * @param
     * @return java.lang.String
     * @Author 俞春旺
     * @Date 下午 07:29:28 2023年3月30日 0030
     **/
    protected static String getResourcesPath() {
        // 这里需要注意的是ApplicationHome是属于SpringBoot的类
        // 获取项目下resources/static/img路径
        ApplicationHome applicationHome = new ApplicationHome(WxPayJsApiHttpUtil.class);

        // 保存目录位置根据项目需求可随意更改 - win和linux目录不一样
        String path = applicationHome.getDir().getParentFile().getParentFile().getAbsolutePath() + "/src/main/resources/pay/";
        System.out.println("path:" + path);
        return path;
    }

    /**
     * 构造 - 通用CloseableHttpClient(定时更新平台证书功能)
     *
     * @return org.apache.http.impl.client.CloseableHttpClient
     * @Author 俞春旺
     * @Date 下午 04:31:47 2023年4月22日 0022
     **/
    protected CloseableHttpClient initCloseableHttpClientV3() throws IOException, HttpCodeException, GeneralSecurityException, NotFoundException {
        // 获取 - 秘钥证书信息
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(WxPayJsApiHttpUtil.getResourcesPath() + "apiclient_key.pem"));
        // 获取 - 证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();

        // 操作 - 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
                new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
        // ... 若有多个商户号,可继续调用putMerchant添加商户信息
        // 操作 - 从证书管理器中获取verifier
        Verifier verifier = certificatesManager.getVerifier(mchId);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));

        // 构造通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }
}

WxPayJsApiNotifyUtil:支付完成回调工具类


import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.ycg.pay.common.constant.WxPayJsApiNotifyConstant;
import com.ycg.pay.common.constant.WxPayJsApiResponseConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @ClassName WxPayJsApiNotifyUtil
 * @Description 回调通知
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月21日 0021 上午 08:58:36
 * @Version 1.0
 */
@Component
@RefreshScope
public class WxPayJsApiNotifyUtil {

    private static Logger logger = LoggerFactory.getLogger(WxPayJsApiNotifyUtil.class);
    /**
     * 商户API私钥
     **/
    @Value("${wxPay.jsApi.apiV3Key}")
    String apiV3Key;

    /**
     * 操作 - 微信支付回调状态处理逻辑
     * @param request 请求入参
     * @param response 请求响应
     * @return java.lang.String
     * @Author 俞春旺
     * @Date 下午 05:44:08 2023年4月22日 0022
    **/
    public String notifyV3(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 构建 - 微信支付回调通知body数据
        String body = getVerifyNotifyBody(request);
        // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
        String plainText = verifyNotify(body, apiV3Key);

        // 构建 - 微信支付回调通知map数据
        Map<String, String> map = new LinkedHashMap<>();
        if (StrUtil.isNotEmpty(plainText)) {
            response.setStatus(WxPayJsApiResponseConstant.RESPONSE_SUCCESS);
            map.put(WxPayJsApiResponseConstant.RESPONSE_CODE, WxPayJsApiResponseConstant.RESPONSE_SUCCESS_MESSAGE);
            map.put(WxPayJsApiResponseConstant.RESPONSE_MESSAGE, WxPayJsApiResponseConstant.RESPONSE_SUCCESS_MESSAGE);
            logger.info("微信支付回调成功,处理成功 " + map);
        } else {
            response.setStatus(WxPayJsApiResponseConstant.RESPONSE_ERROR);
            map.put(WxPayJsApiResponseConstant.RESPONSE_CODE, WxPayJsApiResponseConstant.RESPONSE_ERROR_MESSAGE);
            map.put(WxPayJsApiResponseConstant.RESPONSE_MESSAGE, "签名错误");
            logger.info("微信支付回调成功,处理失败 " + map);
        }

        // 构建 - 响应信息
        response.setHeader("Content-type", ContentType.JSON.toString());
        response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
        String out_trade_no = (String) JSONUtil.parseObj(plainText).get("out_trade_no");
        return out_trade_no;
    }

    /**
     * 获取 - 微信支付回调通知请求头body内容
     * @param request HttpServletRequest
     * @return java.lang.String
     * @Author 俞春旺
     * @Date 上午 10:11:18 2023年4月23日 0023
    **/
    public static String getVerifyNotifyBody(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * v3 支付异步通知验证签名
     *
     * @param body 异步通知密文
     * @param key  api 密钥
     * @return 异步通知明文
     * @throws Exception 异常信息
     */
    public static String verifyNotify(String body, String key) throws Exception {
        // 获取 - 请求body相关加密信息
        JSONObject resultObject = JSONUtil.parseObj(body);
        JSONObject resource = resultObject.getJSONObject(WxPayJsApiNotifyConstant.HTTP_NOTIFY_RESOURCE);
        String cipherText = resource.getStr(WxPayJsApiNotifyConstant.HTTP_NOTIFY_CIPHER_TEXT);
        String nonceStr = resource.getStr(WxPayJsApiNotifyConstant.HTTP_NOTIFY_NONCE);
        String associatedData = resource.getStr(WxPayJsApiNotifyConstant.HTTP_NOTIFY_ASSOCIATED_DATA);

        // 构造 - 解密工具类
        AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));

        // 操作 - 密文解密
        String result = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonceStr.getBytes(StandardCharsets.UTF_8), cipherText);
        logger.info("微信支付成功,解密回调信息 - result : {}",result);
        return result;
    }
}

WxPayJsApiSignUtil:支付签名工具类

import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Signer;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.ycg.pay.common.constant.WxPayJsApiConstant;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;

/**
 * @ClassName WxPayJsApiSignUtil
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 04:39:33
 * @Version 1.0
 */
@Component
@RefreshScope
public class WxPayJsApiSignUtil {

    /**
     * mchSerialNo
     **/
    @Value("${wxPay.jsApi.mchSerialNo}")
    String mchSerialNo;

    /**
     * 操作 - 数据加密
     *
     * @param message 消息信息
     * @return java.lang.String
     * @Author 俞春旺
     * @Date 下午 01:24:56 2023年4月21日 0021
     **/
    public String signV3(byte[] message) throws IOException {
        // 构建 - 签名工具类
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, getPrivateKey(WxPayJsApiHttpUtil.getResourcesPath() + WxPayJsApiConstant.SIGN_API_CLIENT_KEY));
        return privateKeySigner.sign(message).getSign();
    }

    /**
     * 获取私钥。
     * 这是个静态方法,可以直接用类名调用
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     * <p>
     * 完全不需要修改,注意此方法也是去掉了头部和尾部,注意文件路径名
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        return PemUtil.loadPrivateKey(content);
    }

    /**
     * 转换 - 加密数据
     *
     * @param signMessage
     * @return java.lang.String
     * @Author 俞春旺
     * @Date 下午 01:19:45 2023年4月21日 0021
     **/
    public static String buildSignMessage(ArrayList<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }
}

common.constant:常量代码信息

/**
 * @ClassName WxPayJsApiConstant
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 05:01:15
 * @Version 1.0
 */
public interface WxPayJsApiConstant {
    // 请求地址
    /**
     * 微信JS_API下单地址
     */
    String JS_API_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
    /**
     * 微信支付订单号查询
     */
    String TRANSACTIONS_ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/{}?mchid={}";

    // 签名相关
    /**
     * PACKAGE_STR
     */
    String SIGN_PACKAGE_STR = "prepay_id={}";
    /**
     * SIGN_TYPE
     */
    String SIGN_TYPE = "RSA";
    /**
     * SIGN_API_CLIENT_KEY
     */
    String SIGN_API_CLIENT_KEY = "apiclient_key.pem";

}

/**
 * @ClassName WxPayJsApiConstant
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 05:01:15
 * @Version 1.0
 */
public interface WxPayJsApiHeaderConstant {
    /**
     * 请求相关_头部_ACCEPT
     */
    String HTTP_HEADER_ACCEPT = "Accept";
    /**
     * 请求相关_头部_ACCEPT_VAL
     */
    String HTTP_HEADER_ACCEPT_VAL = "application/json";

    /**
     * 请求相关_头部_ACCEPT
     */
    String HTTP_OTHER_PREPAY_ID = "prepay_id";
}

/**
 * @ClassName WxPayJsApiConstant
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 05:01:15
 * @Version 1.0
 */
public interface WxPayJsApiNotifyConstant {
    /**
     * 回调通知常量-resource
     */
    String HTTP_NOTIFY_RESOURCE = "resource";
    /**
     * 回调通知常量-cipher-text
     */
    String HTTP_NOTIFY_CIPHER_TEXT = "ciphertext";

    /**
     * 回调通知常量-nonce
     */
    String HTTP_NOTIFY_NONCE = "nonce";

    /**
     * 回调通知常量-associated_data
     */
    String HTTP_NOTIFY_ASSOCIATED_DATA = "associated_data";
}

/**
 * @ClassName WxPayJsApiConstant
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 05:01:15
 * @Version 1.0
 */
public interface WxPayJsApiOtherConstant {
    /**
     * 请求相关_头部_ACCEPT
     */
    String HTTP_OTHER_PREPAY_ID = "prepay_id";
}

/**
 * @ClassName WxPayJsApiResponseConstant
 * @Description 响应常量信息
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 05:01:15
 * @Version 1.0
 */
public interface WxPayJsApiResponseConstant {
    /**
     * 响应code
     */
    String RESPONSE_CODE = "code";
    /**
     * 响应成功-200
     */
    Integer RESPONSE_SUCCESS = 200;
    /**
     * 响应成功消息
     */
    String RESPONSE_SUCCESS_MESSAGE = "SUCCESS";
    /**
     * 响应失败-200
     */
    Integer RESPONSE_ERROR = 500;
    /**
     * 响应失败消息
     */
    String RESPONSE_ERROR_MESSAGE = "ERROR";
    /**
     * 响应message
     */
    String RESPONSE_MESSAGE = "message";


}

model包底下的类

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.math.BigDecimal;

/**
 * @ClassName WxPayAmountBO
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月21日 0021 上午 08:56:49
 * @Version 1.0
 */
@Data
public class WxPayJsApiAmountBO {

    @ApiModelProperty("总金额:订单总金额,单位为分。")
    private Integer total;

    @ApiModelProperty("货币类型:CNY:人民币,境内商户号仅支持人民币。[长度:1~16位]")
    private String currency;

}


import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @ClassName OperationJsApiOrderBO
 * @Description JS_API下单
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 05:46:17
 * @Version 1.0
 */
@Data
public class WxPayJsApiOrderBO {

    @ApiModelProperty("[必填:是]直连商户号:直连商户的商户号,由微信支付生成并下发[长度:1~32位]")
    private String mchid;

    @ApiModelProperty("[必填:是]商户订单号:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯(1217752501201407033233368018)[长度:6~32位]")
    private String out_trade_no;

    @ApiModelProperty("[必填:是]应用ID:由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID[长度:1~32位]")
    private String appid;

    @ApiModelProperty("[必填:是]商品描述:Image形象店-深圳腾大-QQ公仔[长度:1~127位]")
    private String description;

    @ApiModelProperty("[必填:否]附加数据:附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用,实际情况下只有支付完成状态才会返回该字段")
    private String attach;

    @ApiModelProperty("[必填:是]通知地址:异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http[长度:1~256位]")
    private String notify_url;

    @ApiModelProperty("[必填:是]订单金额:订单金额信息")
    private WxPayJsApiAmountBO amount;

    @ApiModelProperty("[必填:是]支付者:支付者信息")
    private WxPayPayerBO payer;

}

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @ClassName WxPayPayerBO
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月22日 0022 下午 05:48:37
 * @Version 1.0
 */
@Data
public class WxPayPayerBO {

    @ApiModelProperty("微信openId[长度:1~128位]")
    private String openid;
}


import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @ClassName WxPayJsApiPreVO
 * @Description JsApi与支付响应信息,唤起前端支付信息
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月21日 0021 下午 01:33:56
 * @Version 1.0
 */
@Data
public class WxPayJsApiPreVO {

    @ApiModelProperty("应用ID:商户申请的公众号对应的appId")
    private String wxAppId;

    @ApiModelProperty("时间戳")
    private String wxTimeStamp;

    @ApiModelProperty("随机字符串")
    private String wxNonceStr;

    @ApiModelProperty("订单详情扩展字符串")
    private String wxPackage;

    @ApiModelProperty("签名方式")
    private String wxSignType;

    @ApiModelProperty("签名")
    private String wxPaySign;

}

5、controller测试代码

import cn.hutool.json.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.ycg.common.core.domain.R;
import com.ycg.pay.utils.jsapi.WxPayJsApiHttpUtil;
import com.ycg.pay.model.vo.WxPayJsApiPreVO;
import com.ycg.pay.utils.jsapi.WxPayJsApiNotifyUtil;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;

/**
 * @ClassName JsApiDemoTestController
 * @Description TODO
 * @Author 俞春旺
 * @Company RBGT
 * @Date 2023年4月21日 0021 下午 02:08:42
 * @Version 1.0
 */
@Api(tags = "WEB端 ->访客通行记录相关接口")
@RestController
@RequestMapping("/jsapi")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@CrossOrigin(origins = "*")
public class JsApiDemoTestController {

    private final WxPayJsApiHttpUtil wxPayJsApiHttpUtil;
    private final WxPayJsApiNotifyUtil wxPayJsApiNotifyUtil;

    @GetMapping("/test")
    public R<WxPayJsApiPreVO> test() throws Exception {
        return R.ok(wxPayJsApiHttpUtil.jsApiV3());
    }

    @GetMapping("/queryJsApiOrderInfo")
    public R<JSONObject> queryJsApiOrderInfo(@RequestParam("transactionId") String transactionId) throws URISyntaxException, GeneralSecurityException, IOException, NotFoundException, HttpCodeException {
        return R.ok(wxPayJsApiHttpUtil.queryJsApiOrderInfoV3(transactionId));
    }

    @PostMapping("/notify")
    public void wxPayResult(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("微信公众号支付异步消息");
        String out_trade_no = wxPayJsApiNotifyUtil.notifyV3(request, response);
        System.out.println("out_trade_no:" + out_trade_no);
    }
}

中途遇到的问题和大家分享一下:

(1)在对接JSAPI时应认真阅读官方文档,要多看几遍,然后在开始整理自己SDK的DEMO,确保在调试过程中,正常发起支付,且微信官方将支付完成信息正常回调通知到你的平台;

(2)唤起支付的页面域名,必须要在商户应用中心做好配置(必须是http/https,不允许本地,可以结合花生壳内网穿透调试,实在不行就打包线上调试),不然会提示:该域名尚未注册;

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
首先,你需要在微信公众平台申请开通JSAPI支付,并获取到商户号、密钥、证书等信息。 接着,在Spring Boot项目中添加微信支付SDK的依赖,比如: ```xml <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-pay</artifactId> <version>3.6.0</version> </dependency> ``` 然后,创建一个配置类来配置微信支付相关的参数,比如: ```java @Configuration public class WxPayConfig { @Value("${wxpay.appid}") private String appId; @Value("${wxpay.mchid}") private String mchId; @Value("${wxpay.key}") private String key; @Value("${wxpay.certPath}") private String certPath; @Bean public WxPayService wxPayService() throws Exception { WxPayConfig payConfig = new WxPayConfig(); payConfig.setAppId(appId); payConfig.setMchId(mchId); payConfig.setMchKey(key); payConfig.setKeyPath(certPath); return new WxPayServiceImpl(payConfig); } } ``` 其中,`appId`、`mchId`、`key`和`certPath`是申请支付时所获取到的信息。然后,通过`WxPayServiceImpl`创建一个`WxPayService`的实例,用于后续的支付操作。 接下来,编写控制器处理支付请求,比如: ```java @RestController @RequestMapping("/wxpay") public class WxPayController { @Autowired private WxPayService wxPayService; @PostMapping("/unifiedorder") public Map<String, String> unifiedOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException { WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request); Map<String, String> resultMap = new HashMap<>(); resultMap.put("appId", result.getAppid()); resultMap.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); resultMap.put("nonceStr", result.getNonceStr()); resultMap.put("package", "prepay_id=" + result.getPrepayId()); resultMap.put("signType", "MD5"); resultMap.put("paySign", wxPayService.createSign(resultMap)); return resultMap; } } ``` 其中,`WxPayUnifiedOrderRequest`是支付请求参数,包括订单号、金额、回调地址等信息。`wxPayService.unifiedOrder(request)`方法返回的是支付下单结果,包括预支付ID等信息。最后,将这些信息组装成JSAPI支付所需的数据格式,返回给前端即可。 注意,在进行支付之前,需要先通过微信公众平台获取用户的openid,然后将其作为支付请求参数的一个字段传递给微信支付。另外,JSAPI支付还需要在页面上引入微信JSAPI的SDK,同时配置好微信公众平台的授权域名等信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值