美文网首页支付相关
对接支付宝支付接口开发笔记

对接支付宝支付接口开发笔记

作者: 无能的计科狗 | 来源:发表于2018-10-17 19:58 被阅读597次

    支付宝对接学习笔记

    功能介绍:

    • 支付宝对接
    • 支付宝回调
    • 查询支付状态(略过不讲)

    要求:

    • 熟悉支付宝对接核心文档,调通支付宝官方Demo
    • 解析支付宝SDK对接源码
    • RSA1和RSA2验证签名及加解密
    • 避免支付宝的重复通知而加数据校验(略)

    技巧:

    • ngrok 外网穿透
    • 生成二维码并持久化到图片服务器

    调试完demo后,集合到开发项目。
    把支付宝依赖的jar宝按照提供版本要求导入,sdk则放在web下lib文件夹下。然后在module的依赖中导入lib下的本地jar包(坑!!不然会报红)
    那么为什么不统一使用pom导入呢?原因就是阿里没有提供该jar包的线上导入,只能本地导入。为了统一jar地址,所以必须先配置sdk的jar包的位置。(在这之前还要配置一个maven插件以加载本地jar包).

    接下来简单梳理一遍流程:

    一、登录进入蚂蚁金服

    本次使用沙箱环境下进行整合,沙箱环境开发上线流程差别不大,和正式几乎是一致的,只是切换不同的APPID和支付宝网关。


    image.png

    二、下载官方的demo

    这里选中java版的demo


    image.png

    选中idea导入。先在本地调通再集成到系统中去。


    image.png

    右键运行主函数会发现运行不了,那是因为我们还没有修改配置文件中设置。

    对应配置如下。


    image.png

    那么问题来了怎么生成这些公钥私钥呢?前往这里根据系统下载对应的工具。

    image.png

    接着:

    image.png
    配置好配置文件后,运行一下:
    image.png
    运行没有问题,证明已经调通。下载沙箱版的支付宝,登录沙箱提供的买家账户,复制当面付二维码找一个二维码生成工具扫描支付看能不能成功。
    image.png

    扫描支付后:


    image.png

    到此为止本地支付宝已经调通,这个还是相对来说比较简单的。从demo的项目结构来看,这是一个web项目,可以自行配置运行环境再运行,测试会更加方便一点,如果没有出错的话就会出现下图:


    image.png
    image.png

    三、系统对接支付宝支付接口

    虽然官网已经写得很清楚了,但是第一次对接还是很吃力,这里写一下思路:

    1、先把demo中的aplipay那个包及配置文件复制放到需要集成项目的类路径下:
    image.png
    2、把支付宝依赖的jar宝按照提供版本要求导入,sdk则放在web下lib文件夹下。然后在module的依赖中导入lib下的本地jar包(坑!!不然会报红)

    那么为什么不统一使用pom导入呢?原因就是阿里没有提供该jar包的线上导入,只能本地导入。为了统一jar地址,所以必须先配置sdk的jar包的位置。


    image.png
    image.png

    还要配置一个maven插件以加载本地jar包.


    image.png
    3、运行下主函数没有报错就是初步导入成功。
    image.png

    四、对接支付宝支付接口

    1、这里是整个过程中最难的部分。

    从下订单到支付到支付完成,省去下订单的接口,支付过程需要用到两个接口,一个是支付接口,一个是给支付宝授权回调接口。订单这里采用模拟数据。

    2、首先支付接口

    扫码支付调用流程:

    image.png

    官方文档参数描述:


    image.png

    因此先查询数据组装支付宝要求的参数值:

    Map<String, String> resultMap = Maps.newHashMap();
            Order order = orderMapper.selectByUserAndOrderNo(userId, orderNo);
            if (order == null) {
                return ServerRespond.createByErrorMessage("用户没有该订单");
    
            }
            resultMap.put("orderNo", String.valueOf(order.getOrderNo()));
    
            // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
            // 需保证商户系统端不能重复,建议通过数据库sequence生成,
            String outTradeNo = order.getOrderNo().toString();
    
            // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
            String subject = new StringBuilder().append("寸金在线商城,订单号:").append(outTradeNo).toString();
    
            // (必填) 订单总金额,单位为元,不能超过1亿元
            // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
            String totalAmount = order.getPayment().toString();
    
            // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
            // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
            String undiscountableAmount = "0";
    
            // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
            // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
            String sellerId = "";
    
            // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
            String body = new StringBuilder().append("订单").append(outTradeNo).append("购买商品共").append(totalAmount).append("元").toString();
    
            // 商户操作员编号,添加此参数可以为商户操作员做销售统计
            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>();
    
            List<OrderItem> orderItemList = orderItemMapper.getByOrderNoUserId(orderNo, userId);
            System.out.println(orderItemList.get(0));
            for (OrderItem orderItem : orderItemList) {
    
                GoodsDetail goods = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName().toString(),
    
                        BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doubleValue(), new Double(100).doubleValue()).longValue(), orderItem.getQuantity());
                goodsDetailList.add(goods);
            }
    
    //        // 创建一个商品信息,参数含义分别为商品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);
    

    接着就是出参,二维码的生成,并展示给用户支付。图片展示通过上传到图片服务器的方式。所以前提得已经有一个ftp服务器和连接服务器的ftp工具类。

    switch (result.getTradeStatus()) {
                case SUCCESS:
                    log.info("支付宝预下单成功: )");
    
                    AlipayTradePrecreateResponse response = result.getResponse();
                    dumpResponse(response);
    
    //                关键部分,把生成二维码上传到图片服务器
                    File folder = new File(path);
                    if (!folder.exists()) {
    
                        folder.setWritable(true);
                        folder.mkdirs();
                    }
    
                    // 需要修改为运行机器上的路径
                    //替换s占位符
                    String QRPath = String.format(path + "/qr-%s.png",
                            response.getOutTradeNo());
                    String qrFileName = String.format("qr-%s.png", response.getOutTradeNo());
                    //支付宝调用guava生成二维码
                    ZxingUtils.getQRCodeImge(response.getQrCode(), 256, QRPath);
    
                    File targetFile = new File(path, qrFileName);
                    try {
                        FTPUtil.uploadFile(Lists.newArrayList(targetFile));
                    } catch (IOException e) {
                        log.error("上传二维码异常", e);
                    }
                    log.info("QRPath:" + QRPath);
                    String qrUrl = PropertiesUtil.getProperty("ftp.server.http.prefix") + targetFile.getName();
                    resultMap.put("qrUrl", qrUrl);
    
                    return ServerRespond.createBySuccess(resultMap);
    
                case FAILED:
                    log.error("支付宝预下单失败!!!");
                    return ServerRespond.createByErrorMessage("支付宝预下单失败");
    
                case UNKNOWN:
                    log.error("系统异常,预下单状态未知!!!");
                    return ServerRespond.createByErrorMessage("系统异常,预下单状态未知!!!");
                default:
                    log.error("不支持的交易状态,交易返回异常!!!");
                    return ServerRespond.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
            }
    
    
    3、支付宝回调接口

    这个授权支付宝调用的接口,所以不能是本地ip,必须得有一个外网ip,最直接的方式是服务器上操作,但显然现在是没办法这样做的,于是采用了内网穿透的办法,内网穿透工具我采用ngrok,缺点是不能绑定固定域名。


    image.png

    授权回调接口:

    public ServerRespond aliCallback(Map<String, String> params) {
        //处理回调数据
            Long orderNo = Long.parseLong(params.get("out_trade_no"));
            String tradeNo = params.get("trade_no");
            String tradeStatus = params.get("trade_status");
            Order order = orderMapper.selectByOrderNo(orderNo);
            if (order == null) {
                return ServerRespond.createByErrorMessage("寸金商场订单,回调忽略");
            }
            if (order.getStatus() >= Const.OrderStatus.PAID.getCode()) {
                return ServerRespond.createBySuccess("支付宝重复调用");
            }
            if (Const.alipayCallback.TRADE_STATUS_TRADE_SUCCESS.equals(tradeStatus)) {
                order.setPaymentTime(DateTimeUtil.strToDate(params.get("gmt_payment")));
                order.setStatus(Const.OrderStatus.PAID.getCode());
                orderMapper.updateByPrimaryKeySelective(order);
            }
            PayInfo payInfo = new PayInfo();
            payInfo.setUserId(order.getUserId());
            payInfo.setOrderNo(order.getOrderNo());
            payInfo.setPayPlatform(Const.PayPlatFormEnum.ALIPAY.getCode());
            payInfo.setPlatformNumber(tradeNo);
            payInfo.setPlatformStatus(tradeStatus);
            payInfoMapper.insert(payInfo);
            return ServerRespond.createBySuccess();
        }
    

    关于回调接口可以看看文档

    image.png

    第一步: 在通知返回参数列表中,除去sign、sign_type两个参数外,凡是通知返回回来的参数皆是待验签的参数。

    这一步很重要,不然没办法验签,看源码,便可知sdk已经做了,接着组装StringBuffer,因为StringBuffer是线程安全的,可以以应付高并发操作。


    image.png

    第三步: 将签名参数(sign)使用base64解码为字节码串。

    这一步sdk也做了。


    image.png

    第四步: 使用RSA的验签方法,通过签名字符串、签名参数(经过base64解码)及支付宝公钥验证签名。

    然而需要注意的是上面这个方法实际上是不ok的,因为它的算法请求类型跟配置中的不一致。我们的请求算法类型是RSA2不是SHA1WithRSA。


    image.png

    然而还有一个函数重载允许多了一个可以选择加密类型的参数。


    image.png
    点击rsaCheck,当signType的值equal不同的值调用不同的方法,很明显第二个就是我们要的。
    image.png

    于是在控制器中就得这样写:


    image.png

    五、对接测试

    完成接口编写后就是接口测试

    从数据库中提一个未付款的订单号做测试

    image.png
    成功的话会返回一个付款二维码
    image.png
    打开该二维码:
    image.png
    如图则已经对接成功:
    image.png
    以上就是支付宝集成的主要过程,代码只是贴了一部分具体可以看这里

    六、总结

    虽然官方的说明已经够详细了,但是真正入手去做还是有很多坑,此次对接过程中学习很多,其中尤其要注意的是因为官方的关系,sdk必须放在lib下,为了打包时能够同其他依赖包一起打包,还需配置好对sdk的打包插件,其他支付宝需要的依赖包如果用maven引入的话尽量保持版本一致或者一起跟支付宝sdk一同从Demo中复制过来放在lib包下

    相关文章

      网友评论

        本文标题:对接支付宝支付接口开发笔记

        本文链接:https://www.haomeiwen.com/subject/bttdzftx.html