【Java】实现微信V3支付-JSAPI支付


前言

对接微信新版本的微信V3支付,本文章只对接JSAPI支付的相关代码,其他的方式都差不多;


提示:以下是本篇文章正文内容,下面案例可供参考

一、pom文件导入

        <!--微信V3支付-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.3.0</version>
        </dependency>

二、相关工具类

1.WxPayOutModel

封装的实体相关信息,可以自行修改代码如下(示例):

/**
 * @author 孤巷.
 * @description 微信支付参数模型-POST
 * @date 2023/8/1 下午 3:45
 */
@Data
public class WxPayOutModel {
    
    

    /**
     * 微信apiV3密钥
     */
    private String apiV3Key;

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

    /**
     * 微信API证书序列号
     */
    private String serialNo;

    /**
     * 微信请求接口地址-也就是你要调用的微信接口地址
     */
    private String url;

    /**
     * 微信请求接口入参
     */
    private String value;

    private String method;

    private String body;

    /**
     * 机构编码
     */
    private String orgName;

}

2.WxPayUtil

代码如下(示例):

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.whxx.base.core.constant.WeChatPayConstant;
import com.whxx.base.core.model.WxPayOutModel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
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.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
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.*;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
@Slf4j
public class WxPayUtil {
    
    


    private KeyStore store;
    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final Random RANDOM = new SecureRandom();
    private final Object lock = new Object();


    /**
     * 初始化微信支付配置参数
     *
     * @return HttpClient
     */
    public HttpClient iniWeChatV3Configuration(WxPayOutModel wxPayOutModel) {
    
    
        //获取商户密钥
        PrivateKey privateKey = getPrivateKey(null);
        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(wxPayOutModel.getSerialNo(), privateKey);
        //用来实现省份认证
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(wxPayOutModel.getMchId(), privateKeySigner);
        // 获取证书管理器实例
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                wxPayOutModel.getApiV3Key().getBytes(StandardCharsets.UTF_8)
        );
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(wxPayOutModel.getMchId(), wxPayOutModel.getSerialNo(), getPrivateKey(null))
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }

    /**
     * Post请求 调用微信支付接口
     *
     * @param wxPayOutModel 接口入参
     * @return JSON
     */
    public JSONObject httpPostException(WxPayOutModel wxPayOutModel) {
    
    
        HttpClient httpClient = iniWeChatV3Configuration(wxPayOutModel);
        HttpPost httpPost = new HttpPost(wxPayOutModel.getUrl());
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        String bodyAsString = null;
        try {
    
    
            objectMapper.writeValue(bos, JSON.parseObject(wxPayOutModel.getValue()));
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
            bodyAsString = EntityUtils.toString(response.getEntity());
        } catch (IOException e) {
    
    
            log.error("微信支付异常 == {}", e.getMessage());
            e.printStackTrace();
        }
        return JSON.parseObject(bodyAsString);
    }

    /**
     * 获取证书私钥-把微信证书私钥放在项目resources文件夹下即可;
     * @param filename 私钥证书地址
     * @return 私钥
     */
    private PrivateKey getPrivateKey(String filename){
    
    
        try {
    
    
            InputStream systemResourceAsStream = this.getClass().getResourceAsStream("/apiclient_key.pem");
            return PemUtil.loadPrivateKey(systemResourceAsStream);
        } catch (Exception e) {
    
    
            throw new RuntimeException("私钥文件不存在", e);
        }
    }


    @SneakyThrows
    public String awakenPaySign(String appid, long timestamp, String nonceStr, String packages) {
    
    
        String signatureStr = Stream.of(appid, String.valueOf(timestamp), nonceStr, packages)
                .collect(Collectors.joining("\n", "", "\n"));
        log.info("签名的拼接参数 == {}",signatureStr);
        Signature sign = Signature.getInstance("SHA256withRSA");
        log.info("signatureStr == {}", signatureStr);
        sign.initSign(getPrivateKey(null));
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

3.WeChatPayConstant

就是用到的常量,你也可以把他写死;代码如下(示例):

/**
 * @author 孤巷.
 * @description 微信API接口常量
 * @date 2023/8/1 下午 3:27
 */
public class WeChatPayConstant {
    
    

    /**
     * ISV模式jsapi支付
     */
    public static final String ISV_JSAPI_URL = "v3/pay/partner/transactions/jsapi";
    /**
     * ISV模式Native支付
     */
    public static final String ISV_NATIVE_URL = "v3/pay/partner/transactions/native";
    /**
     * JSAPI支付
     */
    public static final String JSAPI_URL = "v3/pay/transactions/jsapi";
    /**
     * Native支付
     */
    public static final String NATIVE_URL = "v3/pay/transactions/native";

/**
     * 币种:CNY人民币
     */
    public static final String CNY = "CNY";

    /**
     * 退款币种
     */
    public static final String CURRENCY = "currency";
    /**
     * 退款金额
     */
    public static final String REFUND = "refund";
    /**
     * 订单总金额
     */
    public static final String TOTAL = "total";
    /**
     * 商户订单号
     */
    public static final String OUT_TRADE_NO = "out_trade_no";
    /**
     * 商户退款订单号
     */
    public static final String OUT_REFUND_NO = "out_refund_no";
    /**
     * 退款原因
     */
    public static final String REASON = "reason";

    /**
     * 唯一用户标识
     */
    public static final String OPENID = "openid";

    /**
     * 服务商应用ID
     */
    public static final String ISV_APPID = "sp_appid";
    /**
     * 服务商户号
     */
    public static final String ISV_MCHID = "sp_mchid";
    /**
     * 子商户应用ID
     */
    public static final String SUB_APPID = "sub_appid";
    /**
     * 子商户号
     */
    public static final String SUB_MCHID = "sub_mchid";
    /**
     * 直连商户的商户号,由微信支付生成并下发
     */
    public static final String WX_MCH_ID = "mchid";
    public static final String WX_APP_ID = "appid";

    /**
     * 商品描述
     */
    public static final String DESCRIPTION = "description";
    /**
     * 通知地址
     */
    public static final String NOTIFY_URL = "notify_url";
    /**
     * 订单失效时间
     */
    public static final String TIME_EXPIRE = "time_expire";
    /**
     * 支付者信息
     */
    public static final String PAYER = "payer";
    /**
     * 用户在子商户appid下的唯一标识
     */
    public static final String SUB_OPENID = "sub_openid";
    /**
     * 预支付会话标识
     */
    public static final String PREPAY_ID = "prepay_id";
    /**
     * 平台应用编码
     */
    public static final String APPID = "appId";
    /**
     * 时间类型
     */
    public static final String TIMESTAMP = "timeStamp";
    public static final String NONCESTR = "nonceStr";
    /**
     * 包
     */
    public static final String PACKAGE = "package";
    public static final String PREPAY_ID_EQUAL = "prepay_id=";
    /**
     * 签名类型
     */
    public static final String SIGNTYPE = "signType";
    /**
     * 加密方式RSA
     */
    public static final String RSA = "RSA";
    public static final String PAYSIGN = "paySign";

    public static final String FIELD_SIGN = "sign";

    public enum SignType {
    
    
        MD5, HMACSHA256
    }
        public static final String SUCCESS = "SUCCESS";
    public static final String PROCESSING = "PROCESSING";
    public static final String WECHAT_REFUND_CODE = "return_code";
    public static final String RETURN_MSG = "return_msg";
    public static final String RESULT_CODE = "result_code";
    public static final String ERROR_CODE_DES = "err_code_des";
    public static final String MICRO_PAY_SUCCESS = "MICROPAY";
    public static final String TRADE_TYPE = "trade_type";
    public static final String CLOSE = "CLOSE";
    /**
     * 微信支付订单号
     */
    public static final String TRANSACTION_ID = "transaction_id";
    /**
     * 退款成功时间/支付完成时间
     */
    public static final String SUCCESS_TIME = "success_time";
    /**
     * 金额信息
     */
    public static final String AMOUNT = "amount";
    /**
     * 交易状态
     */
    public static final String TRADE_STATE = "trade_state";
    /**
     * 微信签名校验参数
     */
    public static final String WECHATPAY_NONCE = "Wechatpay-Nonce";
    public static final String WECHATPAY_TIMESTAMP = "Wechatpay-Timestamp";
    public static final String WECHATPAY_SIGNATURE = "Wechatpay-Signature";
    public static final String SHA256WITHRSA = "SHA256withRSA";
    /**
     * 状态码
     */
    public static final String CODE = "code";
    public static final String FAIL = "FAIL";
    public static final String MESSAGE = "message";
    public static final String SUCCESS_MSG = "操作成功";
    public static final String FAIL_MSG = "操作失败";
}

二、相关业务代码

ResultUtil我就不多介绍了,参考我的统一出入参文章即可;代码如下(示例):


    /**
     * 微信JSAPI支付-调起预付单窗口-V3版本接口
     * @return 成功则返回前端所需调用预付单窗口参数
     */
    public JSONObject jsApi(UnifyPayOrder payOrder, String loginHospitalId, String loginSoftId, String orgName) {
    
    
        //封装JSAPI微信入参
        WxPayOutModel wxPayOutModel = builderWxPayOutModel(payOrder,loginHospitalId,loginSoftId,orgName);
        log.info("微信JSAPI支付-封装入参 == {}",wxPayOutModel);
        //调用微信JSAPI接口,获取回参
        WxPayUtil wxPayUtil = new WxPayUtil();
        JSONObject prePayIdResult = wxPayUtil.httpPostException(wxPayOutModel);
        if(prePayIdResult.isEmpty()){
    
    
            return ResultUtil.fail("微信请求失败:"+prePayIdResult);
        }
        if(prePayIdResult.getString(WeChatPayConstant.PREPAY_ID) == null){
    
    
            throw new WeChatException("微信获取prePayId失败:"+prePayIdResult);
        }
        String prePayId = prePayIdResult.getString(WeChatPayConstant.PREPAY_ID);
        JSONObject result = new JSONObject();
        result.put(WeChatPayConstant.APPID, configRedisService
.getConfigStringValue(loginHospitalId,loginSoftId,PayConfigConstant.GZH_APPID));
        result.put(WeChatPayConstant.TIMESTAMP, System.currentTimeMillis() / 1000);
        result.put(WeChatPayConstant.NONCESTR, WxPayUtil.generateNonceStr());
        result.put(WeChatPayConstant.PACKAGE, WeChatPayConstant.PREPAY_ID_EQUAL + prePayId);
        result.put(WeChatPayConstant.SIGNTYPE, WeChatPayConstant.RSA);
        String sign = wxPayUtil.awakenPaySign(result.getString(WeChatPayConstant.APPID), result.getLong(WeChatPayConstant.TIMESTAMP),result.getString(WeChatPayConstant.NONCESTR),result.getString(WeChatPayConstant.PACKAGE));
        result.put(WeChatPayConstant.PAYSIGN, sign);
        return ResultUtil.success(result);
    }


    /**
     * 微信JSAPI请求入参封装
     * @param payOrder 充值订单实体类
     * @return WxPayOutModel
     */
    private WxPayOutModel builderWxPayOutModel(UnifyPayOrder payOrder,String loginHospitalId,String loginSoftId,String orgName) {
    
    
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        String url;
        //这个就是获取系统配置,判断是不是服务商模式
        boolean isIsv = configRedisService.getConfigBoolValue(loginHospitalId,loginSoftId,PayConfigConstant.IS_ISV);
        if (isIsv) {
    
    
            //服务商模式
            rootNode.put(WeChatPayConstant.ISV_APPID, configRedisService
                    .getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.ISV_APPID));
            rootNode.put(WeChatPayConstant.ISV_MCHID, configRedisService
                    .getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.ISV_MCHID));
            rootNode.put(WeChatPayConstant.SUB_APPID, configRedisService
                    .getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.GZH_APPID));
            rootNode.put(WeChatPayConstant.SUB_MCHID, configRedisService
                    .getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.MCH_ID));
            //下面实际就是根据支付渠道编码判断下具体是哪种支付,如果只有JSAPI判断可以直接去掉就可以
            //jsapi
            if (payOrder.getPayChannelCode().equals(WeChatPayConstant.JS_API_CODE)) {
    
    
                rootNode.putObject(WeChatPayConstant.PAYER).put(WeChatPayConstant.SUB_OPENID, payOrder.getOpenId());
                url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
                        ,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.ISV_JSAPI_URL;
            //Native
            } else {
    
    
                url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
                        ,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.ISV_NATIVE_URL;
            }
        } else {
    
    
            rootNode.put(WeChatPayConstant.WX_APP_ID
                    , configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.GZH_APPID));
            rootNode.put(WeChatPayConstant.WX_MCH_ID
                    , configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.MCH_ID));
            //jsapi
            if (payOrder.getPayChannelCode().equals(WeChatPayConstant.JS_API_CODE)) {
    
    
                rootNode.putObject(WeChatPayConstant.PAYER).put(WeChatPayConstant.OPENID, payOrder.getOpenId());
                url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
                        ,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.JSAPI_URL;
            //Native
            } else {
    
    
                url = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
                        ,PayConfigConstant.NGINX_FORWARD_URL)+WeChatPayConstant.NATIVE_URL;
            }
        }
        rootNode.put(WeChatPayConstant.TIME_EXPIRE, getExpiredTime());
        rootNode.put(WeChatPayConstant.DESCRIPTION, createPayOrderSubject(payOrder.getCardNo()));
        rootNode.put(WeChatPayConstant.OUT_TRADE_NO, payOrder.getPlatformNo());
        String notifyUrl = configRedisService.getConfigStringValue(loginHospitalId,loginSoftId
                , PayConfigConstant.RECHARGE_RETURN_URL);
        rootNode.put(WeChatPayConstant.NOTIFY_URL, getNotifyUrl(notifyUrl, loginHospitalId,loginSoftId));
        rootNode.putObject(WeChatPayConstant.AMOUNT)
                .put(WeChatPayConstant.TOTAL, BigDecimalUtil.yuanToFen(payOrder.getMoney()));
        //调用封装函数获取微信返回数据
        return buildWxPayOutModel(orgName,url,loginHospitalId,loginSoftId,rootNode,isIsv);
    }


    /**
     * 封装调用微信支付接口入参
     *
     * @param url          微信请求地址
     * @param rootNode     微信请求入参
     * @return weChatOutApiModel
     */
    private WxPayOutModel buildWxPayOutModel(String orgName,String url,String loginHospitalId,String loginSoftId, Object rootNode,boolean isIsv) {
    
    
        WxPayOutModel wxPayOutModel = new WxPayOutModel();
        wxPayOutModel.setApiV3Key(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId,PayConfigConstant.TRADE_KEY));
        wxPayOutModel.setCertAddress(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId,PayConfigConstant.WECHAT_REFUND_FILE_URL));
        if (isIsv) {
    
    
            //服务商模式则使用服务商的商户id
            wxPayOutModel.setMchId(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.ISV_MCHID));
        } else {
    
    
            wxPayOutModel.setMchId(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.MCH_ID));
        }
        wxPayOutModel.setValue(String.valueOf(rootNode));
        wxPayOutModel.setUrl(url);
        wxPayOutModel.setSerialNo(configRedisService.getConfigStringValue(loginHospitalId,loginSoftId, PayConfigConstant.WECHAT_CERT_SERIAL_NO));
        wxPayOutModel.setOrgName(orgName);
        return wxPayOutModel;
    }


总结

有相关疑问欢迎大家下方评论,我会第一时间回复!!

猜你喜欢

转载自blog.csdn.net/qq_42666609/article/details/132432380