SpringMVC集成支付宝(沙箱环境)扫码支付

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fighting_sxw/article/details/84957230

第零步、支付流程
在这里插入图片描述
这是支付宝给出的扫码支付的流程,我将上述过程片面的概括为以下几步
1、商户系统根据订单信息向支付宝的服务器请求生成支付用的二维码
2、支付宝的服务器收到请求后,按照用户的请求,生成相关信息(包含二维码连接)返回给商户
3、商户接收到二维码信息后,生成支付用的二维码提供给用户
4、用户通过手机扫码,向支付宝服务器发起支付请求
5、支付成功后,支付宝服务器将支付的信息返回给商户。
6、商户可以调用支付宝提供的查询接口,查询交易状态。

第一步、环境搭建
沙箱环境的配置请参考:https://blog.csdn.net/fighting_sxw/article/details/84874134
本文使用Idea下Maven搭建的SpringMVC环境,根据官方demo,需要引入如下jar包,除了红框中的两个,其他都可以通过maven引入
在这里插入图片描述

第二步、扫码支付逻辑
根据支付宝的扫码支付逻辑,首先是商户系统向支付宝发起请求并生成二维码
在支付宝提供的demo已经封装好了示例方法:test_trade_precreate()
对此方法稍加修改,实现一个简单的付款逻辑

    /**
     * 付款逻辑
     *
     * @param orderNo 订单号
     * @param path    支付二维码保存的路径
     * @return
     */
    public ServerResponse qrCodePay(Long orderNo, String path) {
        //todo 可以根据userId检测是否为当前用户的订单

        // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
        // 需保证商户系统端不能重复,建议通过数据库sequence生成。这里随机生成一个订单号,根据具体需求修改
//        String outTradeNo=String.valueOf(orderNo);
        String outTradeNo = "tradeprecreate" + System.currentTimeMillis()
                + (long) (Math.random() * 10000000L);

        Map<String, String> resultMap = new HashMap();
        resultMap.put("orderNo", outTradeNo);
        // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
        String subject = "xxx品牌xxx门店当面付扫码消费";

        // (必填) 订单总金额,单位为元,不能超过1亿元
        // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
        String totalAmount = "0.01";

        // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
        // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
        String undiscountableAmount = "0";

        // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
        // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
        String sellerId = "";

        // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
        String body = "购买商品3件共20.00元";

        // 商户操作员编号,添加此参数可以为商户操作员做销售统计
        String operatorId = "test_operator_id";

        // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
        String storeId = "test_store_id";

        // 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
        ExtendParams extendParams = new ExtendParams();
        extendParams.setSysServiceProviderId("2088100200300400500");

        // 支付超时,定义为120分钟
        String timeoutExpress = "120m";

        // 商品明细列表,需填写购买商品详细信息,
        List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
        // 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
        GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "xxx小面包", 1000, 1);
        // 创建好一个商品后添加至商品明细列表
        goodsDetailList.add(goods1);

        // 继续创建并添加第一条商品信息,用户购买的产品为“黑人牙刷”,单价为5.00元,购买了两件
        GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "xxx牙刷", 500, 2);
        goodsDetailList.add(goods2);

        // 创建扫码支付请求builder,设置请求参数
        AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
                .setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
                .setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
                .setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
                .setTimeoutExpress(timeoutExpress)
                .setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"))//支付宝服务器主动通知商户服务器里指定的页面http路径,和沙箱配置中的授权回调路径一致
                .setGoodsDetailList(goodsDetailList);

        /** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数
         *  Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录
         */
        Configs.init("zfbinfo.properties");

        /** 使用Configs提供的默认参数
         *  AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new
         */
        AlipayTradeService tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();

        AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
        switch (result.getTradeStatus()) {
            case SUCCESS:
                logger.info("支付宝预下单成功: )");

                AlipayTradePrecreateResponse response = result.getResponse();
                dumpResponse(response);

                /**
                 * 生成二维码的逻辑
                 */

                File folder = new File(path);
                if (!folder.exists()) {
                    folder.setWritable(true);
                    folder.mkdirs();
                }
                // 根据订单号在指定路径下生成二维码
                String qrPath = String.format(path + "/qr-%s.png",
                        response.getOutTradeNo());
                //在指定路径生成二维码
                ZxingUtils.getQRCodeImge(response.getQrCode(), 256, qrPath);
                //todo 可以将二维码上传到ftp服务器上 以供访问
                resultMap.put("qrPath", qrPath);
                return ServerResponse.createBySuccess(resultMap);

            case FAILED:
                logger.error("支付宝预下单失败!!!");
                return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");

            case UNKNOWN:
                logger.error("系统异常,预下单状态未知!!!");
                return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");

            default:
                logger.error("不支持的交易状态,交易返回异常!!!");
                return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
        }
    }

注意:创建AlipayTradePrecreateRequestBuilder时,设置的setNotifyUrl,要和下文中的授权回调地址一致

第三步、回调逻辑
在支付成功后,支付宝需要将支付的信息返回给商户,所以需要告诉支付宝一个返回的地址
在这里插入图片描述
这里的授权回调地址,就是,支付成功后,支付宝需要访问的地址

回调的逻辑如下:

    public Object alipayCallback(HttpServletRequest request){
        Map<String,String> resultMap=new HashMap<>();

        Map<String, String[]> requestParams = request.getParameterMap();
        for(Iterator iter = requestParams.keySet().iterator(); iter.hasNext();){
            String name= (String) iter.next();
            String[] values=requestParams.get(name);
            String valueStr="";

            //将key对应的数组中的内容拼接一下,存入map中
            for(int i=0;i<values.length;i++){
                valueStr=(i==values.length-1)?valueStr+values[i]:valueStr+values[i]+",";
            }
            resultMap.put(name,valueStr);
        }
        //打印回调参数
        logger.info("支付宝回调,sign:{},trade_status:{},参数:{}",resultMap.get("sign"),resultMap.get("trade_status"),resultMap.toString());

        //需要移除sign_type结点
        resultMap.remove("sign_type");
        //非常重要,验证回调的正确性,是不是支付宝发的,还要避免重复通知
        try {
            boolean alipayRSACheckedV2=AlipaySignature.rsaCheckV2(resultMap,Configs.getAlipayPublicKey(),"utf-8",Configs.getSignType());
            if(!alipayRSACheckedV2){
                return ServerResponse.createByErrorMessage("非法请求,验证不通过,再恶意请求就报警了");
            }
        } catch (AlipayApiException e) {
            logger.error("支付宝验证回调异常",e);
        }
        String tradeStatus = resultMap.get("trade_status");
        if (Const.AlipayCallBack.TRADE_STATUS_TRADE_SUCCESS.equals(tradeStatus)) {
            return Const.AlipayCallBack.RESPONSE_SUCCESS;
        }
        return Const.AlipayCallBack.RESPONSE_FAILED;
    }

第四步、查询逻辑
这是官方demo中直接写好的,拿过来用就行

public ServerResponse trade_query(String tradeNo) {
        // (必填) 商户订单号,通过此商户订单号查询当面付的交易状态
        String outTradeNo = tradeNo;

        // 创建查询请求builder,设置请求参数
        AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
                .setOutTradeNo(outTradeNo);

        AlipayF2FQueryResult result = tradeService.queryTradeResult(builder);
        switch (result.getTradeStatus()) {
            case SUCCESS:
                logger.info("查询返回该订单支付成功: )");

                AlipayTradeQueryResponse response = result.getResponse();
                dumpResponse(response);

                logger.info(response.getTradeStatus());
                if (Utils.isListNotEmpty(response.getFundBillList())) {
                    for (TradeFundBill bill : response.getFundBillList()) {
                        logger.info(bill.getFundChannel() + ":" + bill.getAmount());
                    }
                }
                return ServerResponse.createBySuccessMessage("查询返回该订单支付成功");

            case FAILED:
                logger.error("查询返回该订单支付失败或被关闭!!!");
                return ServerResponse.createBySuccessMessage("查询返回该订单支付失败或被关闭!!!");

            case UNKNOWN:
                logger.error("系统异常,订单支付状态未知!!!");
                return ServerResponse.createBySuccessMessage("系统异常,订单支付状态未知!!!");

            default:
                logger.error("不支持的交易状态,交易返回异常!!!");
                return ServerResponse.createBySuccessMessage("不支持的交易状态,交易返回异常!!!");
        }
    }

第五步、测试
在浏览器输入:
http://localhost:8080/alipay/qrCodePay.do

在这里插入图片描述

找到二维码,打开沙箱版支付宝进行扫码

支付成功后根据订单号查询
浏览器输入:http://localhost:8080/alipay/alipay_trade_query.do?tradeNo=tradeprecreate15445145916834657431

在这里插入图片描述

完整代码:https://github.com/shaoxiaowen/alipaytradedemo

猜你喜欢

转载自blog.csdn.net/fighting_sxw/article/details/84957230