栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

Java对接第三方支付渠道之微信支付APIV3版本

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java对接第三方支付渠道之微信支付APIV3版本

提示:微信支付APIV3版本对接流程梳理,目前微信支付提供APIV3和APIV2两个版本,简而言之,V3版本的安全性比V2更高。

Java对接第三方支付渠道之微信支付APIV3版本
  • 一、接入指引
    • 1.获取商户号
    • 2.获取AppID
    • 3.申请商户证书
    • 4.获取微信的证书
    • 5.获取APIv3秘钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
  • 二、导入依赖
  • 三、书写配置类
  • 四、书写工具类
    • 1.订单状态枚举类
    • 2.支付类型枚举类
    • 3.微信支付相关地址枚举类
  • 五、调用相关支付接口
    • 1.流程图
    • 2.发起支付
    • 3.支付通知
    • 4.查询订单
    • 5.取消支付
  • 总结


一、接入指引

是为了获取:标识商户身份的信息、商户的证书和私钥、微信支付的证书、微信支付API的URL

1.获取商户号

微信商户平台:https://pay.weixin.qq.com/ 步骤:申请成为商户 => 提交资料 => 签署协议 => 获取商户号

2.获取AppID

微信公众平台:https://mp.weixin.qq.com/ 步骤:注册服务号 => 服务号认证 => 获取APPID => 绑定商户号

3.申请商户证书

步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 申请API证书 包括商户证书和商户私钥

4.获取微信的证书

可以预先下载,也可以通过编程的方式获取
我这里是直接下载在本地的

5.获取APIv3秘钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)

步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 设置APIv3密钥


二、导入依赖
        
        
            com.github.wechatpay-apiv3
            wechatpay-apache-httpclient
            0.4.5
        

三、书写配置类

这里导入支付秘钥的时候,我直接选择的是导入秘钥字符串,没有选择导入秘钥文件

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    
    private String mchId;

    
    private String mchSerialNo;

    
    private String privateKeyString;

    
    private String apiV3Key;

    
    private String appid;

    
    private String domain;

    
    private String notifyDomain;

    
    private PrivateKey getPrivateKey(String privateKeyString){

        log.info("开始获取私钥");

        try {
            //PrivateKey privateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKeyString));
            PrivateKey privateKey = PemUtil.loadPrivateKey(
            					new ByteArrayInputStream(privateKeyString.getBytes("utf-8")));
            log.info("获取私钥成功");
            return privateKey;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            log.error("获取私钥文件失败", e);
            throw new ServiceErrorException(ResultCode.ERROR, "私钥不存在");
        }

    }

    
    @Bean
    public Verifier getVerifier(){

        log.info("开始获取签名验证器");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyString);

        //微信证书校验器
        Verifier verifier=null;

        try{
            //获取证书管理器实例
            CertificatesManager certificatesManager=CertificatesManager.getInstance();

            //私钥签名对象(加密)
            PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

            //身份认证对象(解密)
            WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

            //向证书管理器增加需要自动更新平台证书的商户信息(默认时间间隔:24小时)
            certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));

            //从证书管理器中获取verifier
            verifier=certificatesManager.getVerifier(mchId);
        }
        catch(Exception e){
            log.error("获取签名验证器失败", e);
            throw new ServiceErrorException(ResultCode.ERROR, "获取签名验证器失败");
        }
        log.info("获取签名验证器成功");
        return verifier;
    }


    
    @Bean
    public HttpClient httpClientWithSign(){

        log.info("开始获取httpClientWithSign");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyString);

        //微信证书校验器
        Verifier verifier=getVerifier();

        WechatPayHttpClientBuilder builder=WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId,mchSerialNo,privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        CloseableHttpClient httpClient=builder.build();

        log.info("获取httpClientWithSign结束");
        return httpClient;
    }

    
    @Bean
    public HttpClient httpClientWithNoSign(){

        log.info("开始获取httpClientWithNoSign");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyString);

        //用于构造HttpClient
        WechatPayHttpClientBuilder builder=WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId,mchSerialNo,privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator(response->true);
        CloseableHttpClient httpClient=builder.build();

        log.info("获取httpClientWithNoSign结束");
        return httpClient;
    }

}


四、书写工具类 1.订单状态枚举类
@AllArgsConstructor
@Getter
public enum OrderStatus {

    
    NOTPAY("未支付"),

    
    SUCCESS("支付成功"),

    
    CLOSED("超时已关闭"),

    
    CANCEL("用户已取消"),
    
    
    private final String type;
}

2.支付类型枚举类

之所以书写这个枚举类,是因为我们在对接第三方支付的时候,一般会同时对接微信支付和支付包支付两种方式。

@AllArgsConstructor
@Getter
public enum PayType {

    
    WXPAY("微信"),

    
    ALIPAY("支付宝");

    
    private final String type;
}
3.微信支付相关地址枚举类
package com.ruiya.commons.enums.wxpay;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum WxApiType {

	
	NATIVE_PAY("/v3/pay/transactions/native"),

	
	ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),

	
	CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),

	
	private final String type;

}

五、调用相关支付接口 1.流程图

2.发起支付

https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml


    @Override
    public String nativePay(String orderNo, String uid) throws Exception{

        //根据orderNo获取orderInfo
        OrderInfo orderInfo = orderInfoService.getOne(new LambdaQueryWrapper()
                .eq(OrderInfo::getOrderNo, orderNo)
                .eq(OrderInfo::getUid, uid));

        if (Objects.isNull(orderInfo)){
            throw new ServiceErrorException(ResultCode.ERROR, "发起支付请求失败 - 订单不存在");
        }

        String codeUrl = orderInfo.getCodeUrl();
        String orderStatus = orderInfo.getOrderStatus();
        //支付二维码不为空 &&订单的支付状态为支付中
        if(!StringUtils.isEmpty(codeUrl) && OrderStatus.NOTPAY.getType().equals(orderStatus)){
            log.info("订单已存在,二维码已保存");
            //返回二维码
            return codeUrl;
        }

        //更新订单的支付类型
        orderInfoService.updatePayTypeByOrderNo(orderInfo.getOrderNo(), PayType.WXPAY);

        //调用统一下单API
        HttpPost httpPost=new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));

        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", orderInfo.getTitle());
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

        Map amountMap = new HashMap();

        BigDecimal payMoney = orderInfo.getTotalFee();
        BigDecimal total = payMoney.multiply(new BigDecimal("100"));
        total = total.setScale(0,BigDecimal.ROUND_UP);

        //支付金额单位:分
        amountMap.put("total", total.intValue());
        amountMap.put("currency", "CNY");

        paramsMap.put("amount", amountMap);

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数:" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = (CloseableHttpResponse) httpClientWithSign.execute(httpPost);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码

            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功,无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
                throw new ServiceErrorException(ResultCode.ERROR, "发起支付请求失败 - Native下单失败:" + bodyAsString);
            }

            //响应结果
            Map resultMap = gson.fromJson(bodyAsString, HashMap.class);
            //获取二维码
            codeUrl = resultMap.get("code_url");
            //保存支付二维码
            orderInfoService.saveCodeUrl(orderNo, codeUrl);

            return codeUrl;
        }finally {
            response.close();
        }
    }

3.支付通知

https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml

 
    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response){

        log.info("收到微信回调");

        Gson gson = new Gson();
        Map map = new HashMap<>();//应答对象

        try {

            //处理通知参数
            String body = HttpUtils.readData(request);
            Map bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String)bodyMap.get("id");
            log.info("支付通知的id ===> {}", requestId);
            
            //签名的验证
            String serialNumber = request.getHeader("Wechatpay-Serial");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String signature = request.getHeader("Wechatpay-Signature");// 请求头Wechatpay-Signature

            // 构造微信请求体
            NotificationRequest wxRequest = new NotificationRequest.Builder().withSerialNumber(serialNumber)
                    .withNonce(nonce)
                    .withTimestamp(timestamp)
                    .withSignature(signature)
                    .withBody(body)
                    .build();

            NotificationHandler handler = new
                    NotificationHandler(wxPayConfig.getVerifier(), wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));

            Notification notification = null;
            try {
                // 验签和解析请求体
                notification = handler.parse(wxRequest);
                Assert.assertNotNull(notification);
            } catch (Exception e) {
                log.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return JSON.toJSONString(map);
            }
            log.info("通知验签成功");
            // 从notification中获取解密报文,并解析为HashMap
            String plainText = notification.getDecryptData();
            log.info("解密报文:{}",plainText);

            //支付成功后 - 处理订单
            wxPayService.processOrder(plainText);
            
            //成功应答
            response.setStatus(200);
            return gson.toJson(map);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("失败应答");
            //失败应答
            response.setStatus(500);
            map.put("code", "FALL");
            map.put("message", "失败");
            return gson.toJson(map);
        }
    }


 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processOrder(String plainText) {

        log.info("用户支付成功 - 开始处理订单!");

        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        //订单号
        String orderNo = (String)plainTextMap.get("out_trade_no");

         
        //尝试获取锁:
        // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
        if(lock.tryLock()){
            try {
                //1.处理重复的通知
                //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if(!OrderStatus.NOTPAY.getType().equals(orderStatus)){
                    log.info("重复订单");
                    return;
                }
                //2.更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);
                //3.记录支付日志
                paymentInfoService.createPaymentInfo(plainText);

                //根据订单号获取订单信息
                OrderInfo orderInfo = orderInfoService.getUidByOrderInfo(orderNo);
                String title = orderInfo.getTitle();
                String uid = orderInfo.getUid();

                //4.保存购买课程成功的消息提示
                noticeService.saveBuyNotice(title,uid);

                //5.移除延时队列中的订单信息
                log.info("延时队列中移除订单信息");
                orderDelayQueue.removeToOrderDelayQueue(orderNo);

                //6.发送支付成功的消息给前端用户
                String msg = uid+"|"+title;
                webSocket.sendToUser(msg);
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }
        log.info("用户支付成功 - 处理订单结束!");
    }
4.查询订单

https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean queryOrder(String orderNo) throws Exception{

        log.info("查单接口调用 ===> {}", orderNo);

        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());

        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = (CloseableHttpResponse) httpClientWithSign.execute(httpGet);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);

                Gson gson = new Gson();
                Map resultMap = gson.fromJson(bodyAsString, HashMap.class);

                //获取微信支付端的订单状态
                String tradeState = resultMap.get("trade_state");

                //判断订单状态
                if(WxTradeState.SUCCESS.getType().equals(tradeState)){

                    log.warn("查询订单已支付 ===> {}", orderNo);

                    //如果确认订单已支付则更新本地订单状态
                    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                    //记录支付日志
                    paymentInfoService.createPaymentInfo(bodyAsString);

                    //根据订单号获取订单信息
                    OrderInfo orderInfo = orderInfoService.getUidByOrderInfo(orderNo);
                    String title = orderInfo.getTitle();
                    String uid = orderInfo.getUid();

                    //保存购买课程成功的消息提示
                    noticeService.saveBuyNotice(title,uid);

                    //移除延时队列中的订单信息
                    log.info("延时队列中移除订单信息");
                    orderDelayQueue.removeToOrderDelayQueue(orderNo);

                    //发送支付成功的消息给前端用户
                    String msg = uid+"|"+title;
                    webSocket.sendToUser(msg);

                    return true;
                }
                if(WxTradeState.NOTPAY.getType().equals(tradeState)){
                    log.info("微信支付 - 支付未完成 - 请扫码支付");
                    return false;
                }
            } else if (statusCode == 204) { //处理成功,无返回Body
                log.info("成功");
                return false;
            } else {
                log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
                throw new ServiceErrorException(ResultCode.ERROR, "微信支付 - 查询订单接口调用失败");
            }
        } finally {
            response.close();
        }
        return false;
    }

5.取消支付

https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml

 
    @Override
    public void closeOrder(String orderNo){

        log.info("关单接口的调用,订单号 ===> {}", orderNo);

        //创建远程请求对象
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url);
        HttpPost httpPost = new HttpPost(url);

        //组装json请求体
        Gson gson = new Gson();
        Map paramsMap = new HashMap<>();
        paramsMap.put("mchid", wxPayConfig.getMchId());
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}", jsonParams);

        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        try {
            //完成签名并执行请求
            CloseableHttpResponse response = (CloseableHttpResponse) httpClientWithSign.execute(httpPost);
            try {
                int statusCode = response.getStatusLine().getStatusCode();//响应状态码
                if (statusCode == 200) { //处理成功
                    log.info("用户取消订单成功 - 200");
                } else if (statusCode == 204) { //处理成功,无返回Body
                    log.info("用户取消订单成功 - 204");
                } else {
                    log.info("用户取消订单失败,响应码 = " + statusCode);
                    throw new ServiceErrorException(ResultCode.ERROR, "用户取消订单失败");
                }
            } finally {
                response.close();
            }
        } catch (IOException e) {
            log.error("调用微信支付关闭订单接口失败 - {}", e);
            e.printStackTrace();
            throw new ServiceErrorException(ResultCode.ERROR, "调用微信支付关闭订单接口失败");
        }
    }
总结

我这边的JDK版本是jdk1.8.0_333,如果有开发者在读取秘钥的时候获取不到系统路径,获取秘钥文件是没存地址以及提示读取了非法长度的字符串等报错提示,证明当前jdk的版本过低,需要升级jdk的版本
以上就是就是全部内容,本文仅仅简单介绍了微信支付的部分API的使用,还有退款以及账单等相关操作后续使用到了会继续更新。

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1032993.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号