文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java 实现微信支付详细教程

2023-08-19 15:15

关注

摘要:最近的一个项目中涉及到了支付业务,其中用到了微信支付和支付宝支付,在做的过程中也遇到些问题,所以现在总结梳理一下,分享给有需要的人,也为自己以后回顾留个思路。

一、微信支付接入准备工作:

首先,微信支付,只支持企业用户,个人用户是不能接入微信支付的,所以要想接入微信支付,首先需要有微信公众号,这个的企业才能申请。有了微信公众号,就能申请微信支付的相关内容,所以在准备开始写代码之前需要先把下面的这些参数申请好:公众账号ID、微信支付商户号、API密钥、AppSecret是APPID对应的接口密码、回调地址(回调必须保证外网能访问到此地址)、发起请求的电脑IP

有了上面提到的这些参数,那我们就可以接入微信支付了,下面我来看下微信支付的官方文档(https://pay.weixin.qq.com/wiki/doc/api/index.html)、访问该地址可以看到有多种支付方式可以选择,我们这里选择扫码支付的方式(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1)

这里我们选择模式二,下面看下模式二的时序图,如下图:

模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。

在这里插入图片描述

            <dependency>            <groupId>com.github.wechatpay-apiv3groupId>            <artifactId>wechatpay-apache-httpclientartifactId>            <version>0.3.0version>        dependency>                <dependency>            <groupId>com.google.code.gsongroupId>            <artifactId>gsonartifactId>            <version>2.8.6version>        dependency>        <dependency>            <groupId>org.apache.httpcomponentsgroupId>            <artifactId>httpclientartifactId>            <version>4.5.12version>        dependency>                <dependency>            <groupId>com.google.zxinggroupId>            <artifactId>coreartifactId>            <version>3.3.3version>        dependency>                <dependency>            <groupId>com.google.zxinggroupId>            <artifactId>javaseartifactId>            <version>3.3.3version>        dependency>
# 微信支付相关参数wxpay:  # 商户号  mch-id: xxxxxxx  # 商户API证书序列号  mch-serial-no: xxxxxxxxxx  # 商户私钥文件  # 注意:该文件放在项目根目录下  private-key-path: ./apiclient_key.pem  # APIv3密钥  api-v3-key: xxxxxxxx  # APPID  appid: xxxxxxc27e0e7cxxx  # 微信服务器地址  domain: https://api.mch.weixin.qq.com  # 接收结果通知地址  # 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置  notify-domain: https://c7c1-240e-3b5-3015-be0-1bc-9bed-fca4-d09b.ngrok.io

1.Controller层

     @ApiOperation(value = "native 微信支付下单 返回Image")    @GetMapping("/native")    public BaseRes<String> nativePay(@RequestParam("packageId") Integer packageId) {        return wxPayService.nativePay(packageId);    }      @ApiOperation(value = "JSAPI微信支付下单")    @GetMapping("/jsapi")    public BaseRes<String> jsapiPay(@RequestParam("packageId") Integer packageId,@RequestParam("openId") String openId) {        return wxPayService.jsapiPay(packageId,openId);    }

注意:packageId是套餐Id,可根据情况修改

2.Service层

   BaseRes<String> nativePay(Integer packageId);      BaseRes<String> jsapiPay(Integer packageId, String openId);

3.实现层

        @Transactional(rollbackFor = Exception.class)    @Override    @SneakyThrows    public BaseRes<String> nativePay(Integer packageId){        log.info("发起Navicat支付请求");        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));        CloseableHttpResponse response = wxPayExecute(packageId, null, 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 IOException("request failed");            }            Gson gson = new Gson();            //响应结果            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);            //二维码            String codeUrl = resultMap.get("code_url");            return new BaseRes<>(codeUrl,ServiceCode.SUCCESS);            //生成二维码//            WxPayUtil.makeQRCode(codeUrl);        } finally {            response.close();        }    }      @Override    @SneakyThrows    public BaseRes<String> jsapiPay(Integer packageId, String openId) {        log.info("发起Navicat支付请求");        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PAY.getType()));        CloseableHttpResponse response = wxPayExecute(packageId, openId, 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("JSAPI下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);                throw new IOException("request failed");            }            Gson gson = new Gson();            //响应结果            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);            String prepayId = resultMap.get("prepay_id");            return new BaseRes<>(prepayId,ServiceCode.SUCCESS);        } finally {            response.close();        }    } // 封装统一下单方法  private CloseableHttpResponse wxPayExecute(Integer packageId,String openId,HttpPost httpPost) throws IOException {        // 获取套餐金额 还有相关信息        ChatPackage chatPackage = chatPackageMapper.selectById(packageId);        if (null == chatPackage) {            throw new NingException(ServiceCode.FAILED);        }        BigDecimal amount = chatPackage.getAmount();        if (null == amount || amount.equals(BigDecimal.ZERO)) {            throw new NingException(ServiceCode.SUCCESS);        }// 从登录信息中获取用户信息        TokenUser loginUserInfo = CommUtils.getLoginUserInfo();        Integer userId = loginUserInfo.getUserId();        // 请求body参数        Gson gson = new Gson();        Map<String,Object> paramsMap = new HashMap<>();        paramsMap.put("appid", wxPayConfig.getAppid());        paramsMap.put("mchid", wxPayConfig.getMchId());        paramsMap.put("description", chatPackage.getName());        paramsMap.put("out_trade_no", WxPayUtil.generateOrderNumber(userId,packageId)); //订单号        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxApiType.NATIVE_NOTIFY.getType()));        Map<String,Object> amountMap = new HashMap<>();        //由单位:元 转换为单位:分,并由Bigdecimal转换为整型        BigDecimal total = amount.multiply(new BigDecimal(100));        amountMap.put("total", total.intValue());        amountMap.put("currency", "CNY");        paramsMap.put("amount", amountMap);// 判断是Navicat下单还是JSAPI下单 JSAPI需要传OPENID        if (StringUtils.isNotBlank(openId)) {            Map<String,Object> payerMap = new HashMap<>();            payerMap.put("openid",openId);            paramsMap.put("payer",payerMap);        }        JSONObject attachJson = new JSONObject();        attachJson.put("packageId",packageId);        attachJson.put("userId",userId);        attachJson.put("total",total);        paramsMap.put("attach",attachJson.toJSONString());        //将参数转换成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");        //完成签名并执行请求        return wxPayClient.execute(httpPost);    }

1.Controller层

       @ApiOperation(value = "支付通知", notes = "支付通知")    @PostMapping("/pay/notify")    @ClientAuthControl    public WxRes nativeNotify() {        return wxPayService.nativeNotify();    }    

2.Service层

    WxRes nativeNotify();

3.实现层

@Resource    private Verifier verifier;        private final ReentrantLock lock = new ReentrantLock();    @Override    @SneakyThrows    @Transactional    public WxRes nativeNotify() {        HttpServletRequest request = CommUtils.getRequest();        HttpServletResponse response = CommUtils.getResponse();        Gson gson = new Gson();        try {            //处理通知参数            String body = WxPayUtil.readData(request);            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);            String requestId = (String) bodyMap.get("id");            //签名的验证            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest                    = new WechatPay2ValidatorForRequest(verifier, requestId, body);            if (wechatPay2ValidatorForRequest.validate(request)) {                throw new RuntimeException();            }            log.info("通知验签成功");            //处理订单            processOrder(bodyMap);            return new WxRes("SUCCESS","成功");        } catch (Exception e) {            e.printStackTrace();            response.setStatus(500);            return new WxRes("FAIL","成功");        }    }       @Transactional    @SneakyThrows    public void processOrder(Map<String, Object> bodyMap){        log.info("处理订单");        //解密报文        String plainText = decryptFromResource(bodyMap);        //将明文转换成map        Gson gson = new Gson();        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);        String orderNo = (String) plainTextMap.get("out_trade_no");        String attach = (String) plainTextMap.get("attach");        JSONObject attachJson = JSONObject.parseObject(attach);        Integer packageId = attachJson.getInteger("packageId");        Integer userId = attachJson.getInteger("userId");        Integer total = attachJson.getInteger("total");                //尝试获取锁:        // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放        if (lock.tryLock()) {            try {                log.info("plainText={}",plainText);                //处理重复的通知                //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。                String orderStatus = orderService.getOrderStatus(orderNo);                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {                    return;                }// TODO 修改订单状态、添加支付记录等  // 通知前端用户 已完成支付                messageSocketHandle.sendMessageByUserID(userId,new TextMessage("PaySuccess"));            } finally {                //要主动释放锁                lock.unlock();            }        }    }    @SneakyThrows    private String decryptFromResource(Map<String, Object> bodyMap) {        log.info("密文解密");        //通知数据        Map<String, String> resourceMap = (Map) bodyMap.get("resource");        //数据密文        String ciphertext = resourceMap.get("ciphertext");        //随机串        String nonce = resourceMap.get("nonce");        //附加数据        String associatedData = resourceMap.get("associated_data");        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));        //数据明文        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),                nonce.getBytes(StandardCharsets.UTF_8),                ciphertext);        return plainText;    }

1.WxPayUtil工具类

@Slf4jpublic class WxPayUtil {    private static final Random random = new Random();// 生成订单号    public static String generateOrderNumber(int userId, int packageId) {        // 获取当前时间戳        long timestamp = System.currentTimeMillis();        // 生成6位随机数        int randomNum = random.nextInt(900000) + 100000;        // 组装订单号        return String.format("%d%d%d%d", timestamp, randomNum, userId, packageId);    }        public static void makeQRCode(String url){        HttpServletResponse response = CommUtils.getResponse();        //通过支付链接生成二维码        HashMap<EncodeHintType, Object> hints = new HashMap<>();        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);        hints.put(EncodeHintType.MARGIN, 2);        try {            BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 200, 200, hints);            MatrixToImageWriter.writeToStream(bitMatrix, "PNG", response.getOutputStream());            System.out.println("创建二维码完成");        } catch (Exception e) {            e.printStackTrace();        }    }        public static String readData(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();                }            }        }    }}

2.微信支付配置类

@Configuration@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点@Data //使用set方法将wxpay节点中的值填充到当前类的属性中@Slf4jpublic class WxPayConfig {    // 商户号    private String mchId;    // 商户API证书序列号    private String mchSerialNo;    // 商户私钥文件    private String privateKeyPath;    // APIv3密钥    private String apiV3Key;    // APPID    private String appid;    // 微信服务器地址    private String domain;    // 接收结果通知地址    private String notifyDomain;        private PrivateKey getPrivateKey(String filename) {        try {            return PemUtil.loadPrivateKey(new FileInputStream(filename));        } catch (FileNotFoundException e) {            throw new RuntimeException("私钥文件不存在", e);        }    }        @Bean    public ScheduledUpdateCertificatesVerifier getVerifier() {        log.info("获取签名验证器");        //获取商户私钥        PrivateKey privateKey = getPrivateKey(privateKeyPath);        //私钥签名对象        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);        //身份认证对象        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);        // 使用定时更新的签名验证器,不需要传入证书        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(                wechatPay2Credentials,                apiV3Key.getBytes(StandardCharsets.UTF_8));        return verifier;    }        @Bean(name = "wxPayClient")    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {        log.info("获取httpClient");        //获取商户私钥        PrivateKey privateKey = getPrivateKey(privateKeyPath);        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()                .withMerchant(mchId, mchSerialNo, privateKey)                .withValidator(new WechatPay2Validator(verifier));        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新        CloseableHttpClient httpClient = builder.build();        return httpClient;    }        @Bean(name = "wxPayNoSignClient")    public CloseableHttpClient getWxPayNoSignClient() {        //获取商户私钥        PrivateKey privateKey = getPrivateKey(privateKeyPath);        //用于构造HttpClient        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()                //设置商户信息                .withMerchant(mchId, mchSerialNo, privateKey)                //无需进行签名验证、通过withValidator((response) -> true)实现                .withValidator((response) -> true);        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新        CloseableHttpClient httpClient = builder.build();        log.info("== getWxPayNoSignClient END ==");        return httpClient;    }}

3.微信支付枚举类

@AllArgsConstructor@Getterpublic enum WxApiType {        NATIVE_PAY("/v3/pay/transactions/native"),        JSAPI_PAY("/v3/pay/transactions/jsapi"),        ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),        CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),        NATIVE_NOTIFY("/client/order/pay/notify");        private final String type;}

4.签名验证类

@Slf4jpublic class WechatPay2ValidatorForRequest {        protected static final long RESPONSE_EXPIRED_MINUTES = 5;    protected final Verifier verifier;    protected final String requestId;    protected final String body;    public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {        this.verifier = verifier;        this.requestId = requestId;        this.body = body;    }    protected static IllegalArgumentException parameterError(String message, Object... args) {        message = String.format(message, args);        return new IllegalArgumentException("parameter error: " + message);    }    protected static IllegalArgumentException verifyFail(String message, Object... args) {        message = String.format(message, args);        return new IllegalArgumentException("signature verify fail: " + message);    }    public final boolean validate(HttpServletRequest request) throws IOException {        try {            //处理请求参数            validateParameters(request);            //构造验签名串            String message = buildMessage(request);            String serial = request.getHeader(WECHAT_PAY_SERIAL);            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);            //验签            if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {                throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",                        serial, message, signature, requestId);            }        } catch (IllegalArgumentException e) {            log.error(e.getMessage());            return false;        }        return true;    }    protected final void validateParameters(HttpServletRequest request) {        // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};        String header = null;        for (String headerName : headers) {            header = request.getHeader(headerName);            if (header == null) {                throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);            }        }        //判断请求是否过期        String timestampStr = header;        try {            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));            // 拒绝过期请求            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {                throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);            }        } catch (DateTimeException | NumberFormatException e) {            throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);        }    }    protected final String buildMessage(HttpServletRequest request) throws IOException {        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);        String nonce = request.getHeader(WECHAT_PAY_NONCE);        return timestamp + "\n"                + nonce + "\n"                + body + "\n";    }    protected final String getResponseBody(CloseableHttpResponse response) throws IOException {        HttpEntity entity = response.getEntity();        return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";    }}

OK,齐活~

来源地址:https://blog.csdn.net/weixin_45444807/article/details/131673713

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯