SpringBoot集成微信支付JSAPIV3保姆教程

这篇具有很好参考价值的文章主要介绍了SpringBoot集成微信支付JSAPIV3保姆教程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

最近为一个公众号h5商城接入了微信支付功能,查找资料过程中踩了很多坑,以此文章记录一下和大家分享

前期准备

公众号认证

微信支付功能需要开通企业号并进行资质认证,费用一年300,且需企业营业执照等信息,对公账户打款验证

登录微信公众平台https://mp.weixin.qq.com/,创建服务号

如果已有服务号扫码登录后点击公众号头像选择认证详情菜单

商户开通

点击公众号左侧微信支付菜单,选择右侧关联商户按钮,如果没有商户按指引申请

参数获取

公众号参数

点击左侧基本配置菜单,记录右侧的应用ID(appid

商户参数

点击公众号左侧微信支付菜单,滑动到已关联商户号,点击查看按钮

进入商户后,选择产品中心,左侧开发配置,记录商户号(mchId

进入商户后,选择账户中心,左侧API安全,按照指引获取APIV3密钥(apiV3Key),API证书的序列号(merchantSerialNumber)和私钥文件apiclient_key.pem

参数配置

外网映射

在微信支付本地调试时需要用到外网映射工具,这里推荐NATAPP:https://natapp.cn/(非广)

一个月带备案域名的映射隧道12元,我们需要两个,一个映射公众号菜单页面,一个映射后端接口

公众号参数

进入公众点击左侧自定义菜单,右侧点击添加菜单,输入外网映射后的菜单地址

如果你是新手,需要进行网页授权认证获取用户openid,那你还需要进行网页授权域名的设置

点左侧接口权限菜单,修改右侧的网页授权用户信息获取

进入后设置JS接口安全域名,会需要将一个txt认证文件放置到你的静态页面目录,参照指引即可

商户参数

进入商户后,选择产品中心,左侧我的产品,进入JSAPI支付

点击产品设置,在支付配置模块,添加支付授权目录(后端接口和前端网页都添加)

支付对接

参数声明

wechartpay:
  # 公众号id
  appId: xxx
  # 公众号中微信支付绑定的商户的商户号
  mchId: xxxx
  # 商户apiV3Keyz密钥
  apiV3Key: xxxx
  #商户证书序列号
  merchantSerialNumber: xxxx
  # 支付回调地址
  v3PayNotifyUrl: http://xxxxxx/wechatpay/pay_notify
  # 退款回调地址
  v3BackNotifyUrl: http://xxxxx/wechatpay/back_notify
	@Value("${wechartpay.appId}")
    private String appId;
    @Value("${wechartpay.mchId}")
    private String mchId;
    @Value("${wechartpay.apiV3Key}")
    private String apiV3Key;
    @Value("${wechartpay.merchantSerialNumber}")
    private String merchantSerialNumber;
    @Value("${wechartpay.v3PayNotifyUrl}")
    private String v3PayNotifyUrl;
    @Value("${wechartpay.v3BackNotifyUrl}")
    private String v3BackNotifyUrl;

	public static RSAAutoCertificateConfig config = null;
    public static JsapiServiceExtension service = null;
    public static RefundService backService = null;

	private void initPayConfig() {
        initConfig();
        // 构建service
        if (service == null) {
            service = new JsapiServiceExtension.Builder().config(config).build();
        }
    }

    private void initBackConfig() {
        initConfig();
        // 构建service
        if (backService == null) {
            backService = new RefundService.Builder().config(config).build();
        }
    }

    private void initConfig() {
        String filePath = getFilePath("apiclient_key.pem");
        if (config == null) {
            config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(mchId)
                    .privateKeyFromPath(filePath)
                    .merchantSerialNumber(merchantSerialNumber)
                    .apiV3Key(apiV3Key)
                    .build();
        }
    }

    public RSAAutoCertificateConfig getConfig() {
        initConfig();
        return config;
    }

    public static String getFilePath(String classFilePath) {
        String filePath = "";
        try {
            String templateFilePath = "tempfiles/classpathfile/";
            File tempDir = new File(templateFilePath);
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }
            String[] filePathList = classFilePath.split("/");
            String checkFilePath = "tempfiles/classpathfile";
            for (String item : filePathList) {
                checkFilePath += "/" + item;
            }
            File tempFile = new File(checkFilePath);
            if (tempFile.exists()) {
                filePath = checkFilePath;
            } else {
                //解析
                ClassPathResource classPathResource = new ClassPathResource(classFilePath);
                InputStream inputStream = classPathResource.getInputStream();
                checkFilePath = "tempfiles/classpathfile";
                for (int i = 0; i < filePathList.length; i++) {
                    checkFilePath += "/" + filePathList[i];
                    if (i == filePathList.length - 1) {
                        //文件
                        File file = new File(checkFilePath);
                        if (!file.exists()) {
                            FileUtils.copyInputStreamToFile(inputStream, file);
                        }
                    } else {
                        //目录
                        tempDir = new File(checkFilePath);
                        if (!tempDir.exists()) {
                            tempDir.mkdirs();
                        }
                    }
                }
                inputStream.close();
                filePath = checkFilePath;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return filePath;
    }

将apiclient_key.pem私钥文件拷贝到resources文件夹根目录

Maven引用

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.11</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.8.0</version>
        </dependency>

用户授权

为了测试流程的完整性,这里简单描述下如何通过网页授权获取用户的openid,几个参数定义如下

var appid = "xxxx";
var appsecret = "xxxx";
redirect_uri = encodeURIComponent("http://xxxx/xxx.html");
response_type = "code";
scope = "snsapi_userinfo";
  • 发起授权请求

    function getCodeUrl() {
        var url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + redirect_uri + "&response_type=" + response_type + "&scope=" + scope + "#wechat_redirect";
        return url
    }
    

    跳转到开始授权页面,会跳转到redirect_uri这个页面,url参数携带授权code

  • 用户同意授权

    function getAuthUrl(code) {
        var url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + appsecret + "&code=" + code + "&grant_type=authorization_code";
        return url
    }
    

    根据code生成正式授权url,需要用户手动点击同意,使用get方式请求该url成功后会返回openid

    var url = getAuthUrl(code);
    $.get(url, function (data, status) {
       var result = JSON.parse(data)
       if (!result.errcode) {
           var openid = result.openid;
        }
    });
    

不使用用户授权流程也能简单的获取到用户openid进行测试,如果该用户关注了公众号,选择公众号左侧的用户管理菜单,点击用户跳转到与该用户的聊天界面,url参数中的tofakeid就是用户的openid

支付准备

根据用户的openid,订单号,订单金额,订单说明四个参数进行支付前的参数准备,会返回如下参数

公众号ID(appId)

时间戳(timeStamp)

随机串(nonceStr)

打包值(packageVal)

微信签名方式(signType)

微信签名(paySign)

这里的orderID指业务中生成的订单号,最大32位,由数字和字母组成,支付金额最终要转转换成已分为单位

    @PostMapping("/prepay")
    public Object prepay(@RequestBody Map<String, Object> params) throws Exception {
        String openId = "xxxx";
        String orderID = String.valueOf(params.get("orderID"));
        BigDecimal payAmount = new BigDecimal(String.valueOf(params.get("payAmount")));
        String payDes = "支付测试";
        return paySDK.getPreparePayInfo(openId,orderID,payAmount,payDes);
    }
	//支付前的准备参数,供前端调用
    public PrepayWithRequestPaymentResponse getPreparePayInfo(String openid, String orderID, BigDecimal payAmount, String payDes) {
        initPayConfig();
        //元转换为分
        Integer amountInteger = (payAmount.multiply(new BigDecimal(100))).intValue();
        //组装预约支付的实体
        // request.setXxx(val)设置所需参数,具体参数可见Request定义
        PrepayRequest request = new PrepayRequest();
        //计算金额
        Amount amount = new Amount();
        amount.setTotal(amountInteger);
        amount.setCurrency("CNY");
        request.setAmount(amount);
        //公众号appId
        request.setAppid(appId);
        //商户号
        request.setMchid(mchId);
        //支付者信息
        Payer payer = new Payer();
        payer.setOpenid(openid);
        request.setPayer(payer);
        //描述
        request.setDescription(payDes);
        //微信回调地址,需要是https://开头的,必须外网可以正常访问
        //本地测试可以使用内网穿透工具,网上很多的
        request.setNotifyUrl(v3PayNotifyUrl);
        //订单号
        request.setOutTradeNo(orderID);
        // 加密
        PrepayWithRequestPaymentResponse payment = service.prepayWithRequestPayment(request);
        //默认加密类型为RSA
        payment.setSignType("MD5");
        payment.setAppId(appId);
        return payment;
    }

支付拉起

在微信环境调用支付准备接口获取参数后,使用WeixinJSBridge.invoke方法发起微信支付

function submitWeChatPay(orderID,payAmount,callback) {
    if (typeof WeixinJSBridge != "undefined") {
        var param = {
            orderID:orderID,
            payAmount:payAmount
        }
        httpPost(JSON.stringify(param),"http://xxxx/wechatpay/prepay",function (data, status) {
            var param = {
                "appId": data.appId,    
                "timeStamp": data.timeStamp,  
                "nonceStr": data.nonceStr,    
                "package": data.packageVal,
                "signType": data.signType,    
                "paySign": data.paySign
            }
            WeixinJSBridge.invoke(
                'getBrandWCPayRequest', param,callback);
        })
    } else {
        alert("非微信环境")
    }
}

支付回调

支付回调地址是在支付准备阶段传递的,在用户付款完成后会自动调用该接口,传递支付订单的相关信息

    @PostMapping("/pay_notify")
    public void pay_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取报文
        String body = getRequestBody(request);
        //随机串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        //微信传递过来的签名
        String signature = request.getHeader("Wechatpay-Signature");
        //证书序列号(微信平台)
        String serialNo = request.getHeader("Wechatpay-Serial");
        //时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        InputStream is = null;
        try {
            is = request.getInputStream();
            // 构造 RequestParam
            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonceStr)
                    .signature(signature)
                    .timestamp(timestamp)
                    .body(body)
                    .build();
            // 如果已经初始化了 RSAAutoCertificateConfig,可以直接使用  config
            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(paySDK.getConfig());
            // 验签、解密并转换成 Transaction
            Transaction transaction = parser.parse(requestParam, Transaction.class);
            //记录日志信息
            Transaction.TradeStateEnum state = transaction.getTradeState();
            String orderNo = transaction.getOutTradeNo();
            System.out.println("订单号:" + orderNo);
            if (state == Transaction.TradeStateEnum.SUCCESS) {
                System.out.println("支付成功");
                //TODO------
                //根据自己的需求处理相应的业务逻辑,异步

                //通知微信回调成功
                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
            } else {
                System.out.println("微信回调失败,JsapiPayController.payNotify.transaction:" + transaction.toString());
                //通知微信回调失败
                response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            is.close();
        }
    }

订单查询

除了支付回调的异步通知,我们还需要通过定时任务主动去查询支付信息来保证业务订单支付状态的正确

    @PostMapping("/pay_check")
    public Object pay_check(@RequestBody Map<String, Object> params) throws Exception {
        String orderID = String.valueOf(params.get("orderID"));
        com.wechat.pay.java.service.payments.model.Transaction transaction = paySDK.getPayOrderInfo(orderID);
        com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum state = transaction.getTradeState();
        if (state == com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.SUCCESS) {
            return Result.okResult().add("obj", transaction);
        } else {
            return Result.errorResult().add("obj", transaction);
        }
    }
    //获取订单支付结果信息
    public com.wechat.pay.java.service.payments.model.Transaction getPayOrderInfo(String orderID) {
        initPayConfig();
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setMchid(mchId);
        request.setOutTradeNo(orderID);
        com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(request);
        return transaction;
    }

退款申请

退款申请需要业务订单号和微信支付号,所以我们这里先通过查询订单信息得到transactionId,你也可以冗余记录在表中

退款支持全款退款和部分退款,部分退款对应的场景就是同一个订单买了多个商品,只退款了其中一个

这里只发起退款申请,具体的退款处理进度通知由退款回调完成

    @PostMapping("/back")
    public Object back(@RequestBody Map<String, Object> params) throws Exception {
        String orderID = String.valueOf(params.get("orderID"));
        String backID = String.valueOf(params.get("backID"));
        BigDecimal backAmount = new BigDecimal(String.valueOf(params.get("backAmount")));
        paySDK.applyRefund(orderID,backID,backAmount);
        return Result.okResult();
    }
    //申请退款
    public void applyRefund(String orderID, String backID,BigDecimal backAmount) {
        initPayConfig();
        initBackConfig();
        QueryOrderByOutTradeNoRequest payRequest = new QueryOrderByOutTradeNoRequest();
        payRequest.setMchid(mchId);
        payRequest.setOutTradeNo(orderID);
        com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(payRequest);


        CreateRequest request = new CreateRequest();
        request.setTransactionId(transaction.getTransactionId());
        request.setNotifyUrl(v3BackNotifyUrl);
        request.setOutTradeNo(transaction.getOutTradeNo());
        request.setOutRefundNo(backID);
        request.setReason("测试退款");
        AmountReq amountReq = new AmountReq();
        amountReq.setCurrency(transaction.getAmount().getCurrency());
        amountReq.setTotal(Long.parseLong((transaction.getAmount().getTotal().toString())));
        amountReq.setRefund( (backAmount.multiply(new BigDecimal(100))).longValue());
        request.setAmount(amountReq);
        backService.create(request);
    }

退款回调

退款回调在申请退款后自动调用该接口,由于退款需要一定的处理时间,所以回调通知一般显示的状态为处理中(PROCESSING)可以在此回调更新订单退款的处理状态

    @PostMapping("/back_notify")
    public void back_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取报文
        String body = getRequestBody(request);
        //随机串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        //微信传递过来的签名
        String signature = request.getHeader("Wechatpay-Signature");
        //证书序列号(微信平台)
        String serialNo = request.getHeader("Wechatpay-Serial");
        //时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        InputStream is = null;
        try {
            is = request.getInputStream();
            // 构造 RequestParam
            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonceStr)
                    .signature(signature)
                    .timestamp(timestamp)
                    .body(body)
                    .build();
            // 如果已经初始化了 RSAAutoCertificateConfig,可以直接使用  config
            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(paySDK.getConfig());
            // 验签、解密并转换成 Transaction
            Refund refund = parser.parse(requestParam, Refund.class);
            //记录日志信息
            Status state = refund.getStatus();
            String orderID = refund.getOutTradeNo();
            String backID = refund.getOutRefundNo();
            System.out.println("订单ID:" + orderID);
            System.out.println("退款ID:" + backID);
            if (state == Status.PROCESSING) {
                //TODO------
                //根据自己的需求处理相应的业务逻辑,异步

                //通知微信回调成功
                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
                System.out.println("退款处理中");
            } else if (state == Status.SUCCESS) {
                //TODO------
                //根据自己的需求处理相应的业务逻辑,异步

                //通知微信回调成功
                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
                System.out.println("退款完成");
            } else {
                System.out.println("微信回调失败,JsapiPayController.Refund:" + state.toString());
                //通知微信回调失败
                response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            is.close();
        }
    }

退款查询

除了退款回调的异步通知,我们还需要通过定时任务主动去查询退款信息来保证业务订单退款状态的正确文章来源地址https://www.toymoban.com/news/detail-706418.html

 @PostMapping("/back_check")
    public Object back_check(@RequestBody Map<String, Object> params) throws Exception {
        String backID = String.valueOf(params.get("backID"));
        Refund refund = paySDK.getRefundOrderInfo(backID);
        if (refund.getStatus() == Status.SUCCESS) {
            return Result.okResult().add("obj", refund);
        }if (refund.getStatus() == Status.PROCESSING) {
            return Result.okResult().setCode(2).setMsg("退款处理中").add("obj", refund);
        } else {
            return Result.errorResult().add("obj", refund);
        }
    }
    //获取订单退款结果信息
    public Refund getRefundOrderInfo(String backID){
        initBackConfig();
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.setOutRefundNo(backID);
        return backService.queryByOutRefundNo(request);
    }

到了这里,关于SpringBoot集成微信支付JSAPIV3保姆教程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot集成沙箱支付

    支付宝沙箱支付( Alipay Sandbox Payment )是支付宝提供的一个模拟支付环境,用于开发和测试支付宝支付功能的开发者工具。在真实的支付宝环境中进行支付开发和测试可能涉及真实资金和真实用户账户,而沙箱环境则提供了一个安全、隔离的环境,使开发者能够模拟支付过程

    2024年02月03日
    浏览(42)
  • JAVA 微信APPV3支付(保姆级)

    这里以APP支付和退款为例 创建WechatPaymentService.java   如果更换支付类型如:APP、二维码支付、扫码支付、JSAPI支付 请看以下示例二维码支付代码  HttpPost httpPost = new HttpPost(\\\"这里更换\\\") 整体不变只需要微信返回正确状态码内处理即可 如以下返回为 二维码支付参数 你学废了

    2024年02月04日
    浏览(20)
  • 搭建自动发卡网站搭建教程(独角数卡)保姆级教程,支付 + 图文

    程序是开源的独角数卡 我搭建了一个这样的 一个在线销售虚拟产品的平台。你应该见过这样的发卡平台。一些虚拟产品,如软件、激活码和会员可以放在上面出售。我在这里使用的发卡项目是一个开源的单字符数字卡。界面和功能非常符合我的口味。此外,我可以访问这里

    2024年02月04日
    浏览(451)
  • 小程序微信支付V3版本Java集成

    相较于之前的微信支付API,主要区别是: 遵循统一的REST的设计风格 使用JSON作为数据交互的格式,不再使用XML 使用基于非对称密钥的SHA256-RSA的数字签名算法,不再使用MD5或HMAC-SHA256 不再要求携带HTTPS客户端证书(仅需携带证书序列号) 使用AES-256-GCM,对回调中的关键信息进

    2024年02月11日
    浏览(59)
  • 【24年2月最新教程】国内如何使用虚拟卡订阅midjourney?midjourney银行卡支付教程?(保姆级图文教程)

    Midjourney 是一款备受欢迎的人工智能生成图像工具,它可以通过输入文字描述,自动生成精美的图像。与许多其他图像生成工具不同,Midjourney 不需要安装任何软件,也不受个人电脑性能的限制,因为它运行在云端服务器上。要使用 Midjourney,只需拥有一个 Discord 帐号并加入相

    2024年02月22日
    浏览(45)
  • 第五节、项目支付功能实战-证书获取、微信支付集成初始化配置、sdk统一下单、api安全源码解读

    本节首先会讲解商户证书、私钥、微信平台证书的获取、APIv3密钥的生成。然后将我们微信支付需要的参数配置信息初始化出来,为后面的业务代码使用。结合微信平台证书下载案例和微信统一下单api来讲解请求和响应都做了什么操作。上一节中我们提到的那些证书又是如何

    2024年02月03日
    浏览(43)
  • Jenkins集成SonarQube保姆级教程

    Jenkins是自动化部署平台,一个粗眉大眼的糙汉子! SonarQube是代码扫描平台,一个眉目清秀的小女子! 有一天,上天交给我一个任务,去撮合撮合他们! 我抬头看了看天, 不,天花板。 这事儿有戏! 不废话,开干! jenkins和SonarQube的搭建过程就忽略了,不知道的可以看:

    2024年02月15日
    浏览(39)
  • 微服务集成RabbitMq保姆级教程

     本文通过简单的示例代码和说明,让读者能够了解微服务如何集成RabbitMq 之前的教程 https://www.cnblogs.com/leafstar/p/17641358.html 在这里我将介绍Centos中通过docker进行安装RabbitMq   1.首先你已经有一台可以使用的虚拟机(教程很多)   2.yum install docker -y    3.拉取docker镜像   4.开启

    2024年02月12日
    浏览(38)
  • SpringBoot 整合微信小程序微信支付V3 jsapi (支付、退款)

    最近的一个微信小程序项目里有用到微信支付,网上找的资料都是特别乱,看起来特别懵,结合了好多文章的内容,终于做了出来,可能我的这个博文看起来也是特别乱,但是是可以直接C走简单改一改就可以用的。(支付成功回调,和退款回调因为昨天刚在阿里申请的域名还

    2024年04月25日
    浏览(46)
  • 【微信支付】springboot-java接入微信支付-JSAPI支付/查单/退款/发送红包(三)---退款

    微信支付开发文档:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/create.html 退款与查单的请求头类似,但是查单是GET请求,所以在构造签名的时候相对简单些,但是退款请求中有请求参数,在构造签名时,需要将请求体添加到请求头参数中。 1、构造请求参数 查看微信支付开

    2024年01月19日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包