IJPay 微信支付 V3

1,519 阅读2分钟

按照惯例,记个笔记,网上也很多介绍v3和v2的区别,这里不做介绍,选择v3完全是因为restful风格和josn的数据格式,平时比较熟悉。

准备工作之证书准备

image.png

前三个api证书由微信提供详细请看:pay.weixin.qq.com/wiki/doc/ap…

第四个证书是由公钥和私钥生成的平台证书,后续有需要,以下是jsApiPay支付方式

<dependency>
    <groupId>com.github.javen205</groupId>
    <artifactId>IJPay-WxPay</artifactId>
    <version>2.8.0</version>
</dependency>
V3:
    appId: ****

    mchId: ****

    apiKey3: ****

    keyPath:/cert/apiclient_key.pem

    certPath: /cert/apiclient_cert.pem

    certP12Path: /cert/apiclient_cert.p12

    platformCertPath: /cert/platform_cert.pem

    domain: ***
    
@Data
@ToString
@Component
@ConfigurationProperties(prefix = "v3")
public class WxPayV3Properties {
    private String appId;
    private String mchId;
    private String apiKey3;
    private String keyPath;
    private String certPath;
    private String platformCertPath;
    private String domain;
}
private String getSerialNumber() {
    // 获取证书序列号
    X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(wxPayV3Properties.getCertPath()));
    return certificate.getSerialNumber().toString(16).toUpperCase();
}

生成平台证书

public void v3Get() {
    // 获取平台证书列表
    try {
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethod.GET,
                WxDomain.CHINA.toString(),
                WxApiType.GET_CERTIFICATES.toString(),
                wxPayV3Properties.getMchId(),
                getSerialNumber(),
                null,
                wxPayV3Properties.getKeyPath(),
                ""
        );
       /* String timestamp = response.getHeader("Wechatpay-Timestamp");
        String nonceStr = response.getHeader("Wechatpay-Nonce");
        String signature = response.getHeader("Wechatpay-Signature");*/
        String serialNumber = response.getHeader("Wechatpay-Serial");
        String body = response.getBody();
        int status = response.getStatus();
        log.info("serialNumber: {}", serialNumber);
        log.info("status: {}", status);
        log.info("body: {}", body);
        int isOk = 200;
        if (status == isOk) {
            JSONObject jsonObject = JSONUtil.parseObj(body);
            JSONArray dataArray = jsonObject.getJSONArray("data");
            // 默认认为只有一个平台证书
            JSONObject encryptObject = dataArray.getJSONObject(0);
            JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
            String associatedData = encryptCertificate.getStr("associated_data");
            String cipherText = encryptCertificate.getStr("ciphertext");
            String nonce = encryptCertificate.getStr("nonce");
            String serialNo = encryptObject.getStr("serial_no");
            final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, wxPayV3Properties.getPlatformCertPath());
            log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
        }
        boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Properties.getPlatformCertPath());
        log.info("verifySignature:" + verifySignature);
    } catch (Exception e) {
        e.printStackTrace();
    }

}
private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
    try {
        AesUtil aesUtil = new AesUtil(wxPayV3Properties.getApiKey3().getBytes(StandardCharsets.UTF_8));
        // 平台证书密文解密
        String publicKey = aesUtil.decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                cipherText
        );
        // 保存证书
        log.info("获取证书key:{},保存路径platformCert:{}", publicKey, certPath);
        //将生成的证书写入指定路径,文件名为:cert.pem
        FileOutputStream fos = new FileOutputStream(certPath);
        fos.write(publicKey.getBytes());
        fos.close();
        // 获取平台证书序列号
        X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
        return certificate.getSerialNumber().toString(16).toUpperCase();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

微信支付

image.png 微信支付正常流程是发起支付获取下单参数,由微信调起支付接口

public Map<String, String> createOrder() throws Exception {
    Map<String, String> map =new HashMap<>();
    String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
    String openId="";
    String total="1";
    //系统内唯一订单号(也可随机id)
    String outTradeNo="123456";
    Map<String,Object> model= requestWxPayParam(openId,total,outTradeNo, timeExpire);
    log.info("统一下单参数 {}", JSONUtil.toJsonStr(model));
    IJPayHttpResponse response = WxPayApi.v3(
            RequestMethod.POST,
            WxDomain.CHINA.toString(),
            WxApiType.JS_API_PAY.toString(),
            wxPayV3Properties.getMchId(),
            getSerialNumber(),
            null,
            wxPayV3Properties.getKeyPath(),
            JSONUtil.toJsonStr(model));
    log.info("统一下单响应 {}", response);
    if (response.getStatus() == 200) {
        boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Properties.getPlatformCertPath());
        log.info("verifySignature: {}", verifySignature);
        if (verifySignature) {
            String body = response.getBody();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            String prepayId = jsonObject.getStr("prepay_id");
            map = WxPayKit.jsApiCreateSign(wxPayV3Properties.getAppId(), prepayId,wxPayV3Properties.getKeyPath());
        }
    }
    return map;
}
private Map<String, Object> requestWxPayParam(String openId,String total,String outTradeNo, String timeExpire) {
    Map<String, Object> data = new HashMap<>();
    Map<String, String> user = new HashMap<>();
    user.put("openid", openId);
    Map<String, Object> fee = new HashMap<>();
    //分为单位
    fee.put("total", new BigDecimal(total).multiply(new BigDecimal(100)).longValue());
    data.put("appid", wxPayV3Properties.getAppId());
    data.put("mchid", wxPayV3Properties.getMchId());
    data.put("description", "测试");
    data.put("out_trade_no", outTradeNo);
    data.put("amount", fee);
    //默认2小时
    data.put("time_expire", timeExpire);
    //回调地址
    data.put("notify_url", wxPayV3Properties.getDomain().concat("/pay/callBackNotify"));
    data.put("payer", user);
    return data;
}
public void callBack(HttpServletRequest request, HttpServletResponse response) {
    log.info("======微信支付回调======");
    Map<String, String> map = new HashMap<>(12);
    try {
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");
        log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
        String result = HttpKit.readData(request);
        String platformCertPath =wxPayV3Properties.getPlatformCertPath();
        String mckKey=wxPayV3Properties.getApiKey3();
        //出于安全考虑,验证是否是微信回调
        String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,mckKey, platformCertPath);
        log.info("支付通知明文 {}", plainText);
        //逻辑处理
        
        //回复微信
        if (StrUtil.isNotEmpty(plainText)) {
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "SUCCESS");
        } else {
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "签名错误");
        }
        response.setHeader("Content-type", ContentType.JSON.toString());
        response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    } catch (Exception e) {
        log.error("微信回调失败:{}",e.getMessage());
    }
}

微信查单

public String queryOrderByNo(String outTradeNo) {
    try {
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), outTradeNo);
        url =url.concat("?mchid=").concat(wxPayV3Properties.getMchId());
        log.info("查询订单url:{}", url);
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethod.GET,
                WxDomain.CHINA.toString(),
                url,
                wxPayV3Properties.getMchId(),
                getSerialNumber(),
                null,
                wxPayV3Properties.getKeyPath(),
                ""
        );
        log.info("查询订单响应:{}", response);
        if (response.getStatus() == 200) {
            //平台证书
            boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Properties.getPlatformCertPath());
            log.info("verifySignature: {}", verifySignature);
            if (verifySignature) {
                String body = response.getBody();
                JSONObject jsonObject = JSONUtil.parseObj(body);
                JSONObject amountObject=JSONUtil.parseObj(jsonObject.get("amount"));
                log.info("订单金额结果:{}",amountObject.get("total"));
                log.info("订单响应结果:{}", jsonObject);
                return body;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

微信关单

public String closeOrderByNo(String outTradeNo) throws Exception {
    String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), outTradeNo);
    log.info("关闭订单url:{}", url);
    Map<String,Object> model=new HashMap<>();
    model.put("mchid",wxPayV3Properties.getMchId());
    IJPayHttpResponse response = WxPayApi.v3(
            RequestMethod.POST,
            WxDomain.CHINA.toString(),
            url,
            wxPayV3Properties.getMchId(),
            getSerialNumber(),
            null,
            wxPayV3Properties.getKeyPath(),
            JSONUtil.toJsonStr(model)
    );
    if (response.getStatus() == 204) {
        //平台证书
        boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Properties.getPlatformCertPath());
        log.info("verifySignature: {}", verifySignature);
        if (verifySignature) {
            return response.getBody();
        }
    }
    return null;
}

微信退款

public String refund() throws Exception {
        Map<String,Object> model=new HashMap<>();
        //交易单号(支付的订单号)
        model.put("out_trade_no","123456");
        model.put("out_refund_no",WxPayKit.generateStr());
        model.put("notify_url",wxPayV3Properties.getDomain().concat("/pay/callBackRefund"));
        Map<String, Object> fee = new HashMap<>();
        //退款(单位:分)
        fee.put("refund", 1);
        //订单总额(可发起多笔,最多50笔,详见微信官网)
        fee.put("total",1);
        fee.put("currency","CNY");
        model.put("amount",fee);
        log.info("微信订单退款参数 {}", JSONUtil.toJsonStr(model));
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethod.POST,
                WxDomain.CHINA.toString(),
                WxApiType.DOMESTIC_REFUNDS.toString(),
                wxPayV3Properties.getMchId(),
                getSerialNumber(),
                null,
                wxPayV3Properties.getKeyPath(),
                JSONUtil.toJsonStr(model)
        );
        log.info("微信订单退款响应 {}", response);
        if(response.getStatus()==200){
            boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Properties.getPlatformCertPath());
            log.info("verifySignature: {}", verifySignature);
            if (verifySignature) {
                return response.getBody();
            }
        }else{
            throw new ServiceException("订单退款响应失败");
        }
        return null;
}
public void callBackRefund(HttpServletRequest request, HttpServletResponse response) {
    log.info("收到微信退款回调");
    Map<String, String> map = new HashMap<>(12);
    try {
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");
        log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
        String result = HttpKit.readData(request);
        String platformCertPath =wxPayV3Properties.getPlatformCertPath();
        String mckKey=wxPayV3Properties.getApiKey3();
        String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,mckKey, platformCertPath);
        log.info("退款通知明文 {}", plainText);
        //逻辑处理
        
        //回复微信
        if (StrUtil.isNotEmpty(plainText)) {
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "SUCCESS");
        } else {
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "签名错误");
        }
        response.setHeader("Content-type", ContentType.JSON.toString());
        response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    } catch (Exception e) {
        log.error("微信回调失败:{}",e.getMessage());
    }
}

人不狠话不多,存个代码自己看