• 支付宝当面付扫码支付功能详解


    前言: 上篇呢主要是针对微信验证登录做了讲解,当然微信也是提供了很多的接口来供开发者进行调用,同样,微信也有支付,相信小伙伴们学习了上篇的登录之后,已经能够融汇贯通,做出微信的支付功能。那么本篇呢就讲解一下支付宝的支付功能,同样的,通过这一个例子,你就能使用支付宝其它的功能,还是那句老话,就当做是一个敲门砖吧,好了,下面就开始吧。

    本篇为原创,转载请标出处http://www.cnblogs.com/gudu1/p/8094197.html

      微信有测试公众号测试,那么支付宝呢?他也有,不过名字是叫做支付宝沙箱环境,地址:https://sandbox.alipaydev.com/sms/receive.htm ,扫码登陆之后:

     

      >> APPID,支付宝网关,以及应用网关 这些呢是固定不变的。

      >> 我们已经知道微信使用的是 SHA-1 加密,那么支付宝使用的就是RSA 和 RSA2 加密,当然了,支付宝推荐使用RSA2加密,两个的区别就是RSA2 是2048 位,RSA 是1024位,所以RSA2加密更好,我们就使用它就好。

      >> 授权回调地址就是用户扫码进行支付之后,支付宝服务器回调我们程序接口的地址,规则呢跟微信的是一样的,这里不多讲了,不明白的请看一下上篇微信验证登录的讲解 http://www.cnblogs.com/gudu1/p/8087130.html

      >> 接下来的就配置一下我们的密匙,使用哪个加密方式就配置哪个就好了,支付宝很周到,给我们提供了生成密匙的工具,下载位置:https://docs.open.alipay.com/291/105971 ,这个也是支付宝的官方文档,具体怎么使用,里面都很详细,这里就不占篇幅了,然后生成之后,就直接 copy 进去配置就好了,很简单的。

      下面还是老样子,先贴出代码,然后讲解代码中的一些点,支付宝也提供了Java 、PHP、.NET  版本的支付代码,这点是非常到位的,所以我们只是站在巨人的肩膀上,前人栽树,后人乘凉,下载地址:https://docs.open.alipay.com/194/105201/ ,下载我们都会,下载之后把代码copy 出来,需要使用哪个功能就copy 哪个,因为我这里使用的支付宝生成二维码预下单的功能,然后:

    Controller 

    @Controller
    @RequestMapping("/order/")
    public class OrderController {
     @RequestMapping("pay.do")
        @ResponseBody
        private ServerResponse<Map<String, String>> pay(HttpSession session, Long orderNo, HttpServletRequest request) {
            Integer userId = ((User) session.getAttribute(Const.CURRENT_USER)).getId();
            String path = request.getSession().getServletContext().getRealPath(PropertiesUtil.getProperty("upload_image_path"));
            if (orderNo == null || orderNo == 0L) {
                return ServerResponse.createByErrorMessage("支付订单不能为空");
            }
            return orderService.pay(userId, orderNo, path);
        }
    }

     Service 的pay 方法

    @Service("iOrderService")
    public class OrderServiceImpl implements IOrderService {
    
        private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
        // 支付宝当面付2.0服务
        private static AlipayTradeService tradeService;
        static {
            /** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数
             *  Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录
             */
            Configs.init("zfbinfo.properties");
    
            /** 使用Configs提供的默认参数
             *  AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new
             */
            tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
        }
    /**
         * 支付宝预创建支付订单,生成支付的二维码用户用户扫码支付
         *
         * @param userId  用户ID
         * @param orderNo 订单号
         * @param path    本地上传图片地址
         * @return
         */
        @Transactional
        public ServerResponse pay(Integer userId, Long orderNo, String path) {
            Map<String, String> mapResult = Maps.newHashMap();
            Order order = orderMapper.selectOrderByOrderNo(orderNo);
            if (order == null) {
                return ServerResponse.createBySuccessMessage("该订单不存在");
            }
            // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
            // 需保证商户系统端不能重复,建议通过数据库sequence生成,
            String outTradeNo = order.getOrderNo().toString();
    
            // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
            String subject = "Happy_mmall 扫码付款";
    
            // (必填) 订单总金额,单位为元,不能超过1亿元
            // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
            String totalAmount = order.getPayment().toString();
    
            // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
            // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
            String undiscountableAmount = "0";
    
            // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
            // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
            String sellerId = "";
    
            // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
            String body = new StringBuffer().append("订单:").append(order.getOrderNo()).append(",共花费").append(order.getPayment()).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.selectListByUserIdAndOrderNo(orderNo, userId);
            for (OrderItem orderItem : orderItemList) {
                GoodsDetail goods1 = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName(),
                        BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doubleValue(), new Double(100)).longValue(),
                        orderItem.getQuantity());
                goodsDetailList.add(goods1);
            }
    
            // 创建扫码支付请求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);
    
            // 创建预支付订单对象
            AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
            switch (result.getTradeStatus()) {
                case SUCCESS:
                    logger.info("支付宝预下单成功: )");
                    // 获取响应 Response
                    AlipayTradePrecreateResponse response = result.getResponse();
                    //  简单打印一下日志
                    dumpResponse(response);
                    // 创建本地上传图片的文件夹,不存在则创建
                    File folder = new File(path);
                    if (!folder.exists()) {
                        folder.setWritable(true);
                        folder.mkdirs();
                    }
                    // 需要修改为运行机器上的路径,
                    String filePath = String.format(path + "/qr-%s.png",
                            response.getOutTradeNo());
                    // %s 是一种占位符,即后面的response.getOutTradeNo() ,只是生成额随机字符串,防止重名
                    String fileName = String.format("/qr-%s.png", response.getOutTradeNo());
    
                    logger.info("filePath:" + filePath);
                    // 上传到本地服务器
                    ZxingUtils.getQRCodeImge(response.getQrCode(), 256, filePath);
                    File targetFile = new File(path, fileName);
                    try {
                        // 上传图片到FTP服务器,上传FTP完毕之后,删除本地存储的图片
                        FTPUtil.upload(Lists.newArrayList(targetFile));
                    } catch (IOException e) {
                        logger.error("上传二维码失败", e);
                        e.printStackTrace();
                    }
                    // 刚刚上传到FTP的图片地址URL
                    String qrPathUrl = PathUtil.getFTPImgPath(targetFile.getName());
                    mapResult.put("qrPath", qrPathUrl);
                    mapResult.put("orderNo", orderNo.toString());
                    return ServerResponse.createBySuccess(mapResult);
                case FAILED:
                    logger.error("支付宝预下单失败!!!");
                    return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");
    
                case UNKNOWN:
                    logger.error("系统异常,预下单状态未知!!!");
                    return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");
    
                default:
                    logger.error("不支持的交易状态,交易返回异常!!!");
                    return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
            }
        }
    }

     

      >> 在此之前呢不要忘记添加支付宝的集成依赖jar包。

      >> 为了方便理解,代码中的每一步都添加了注释,代码比较多,但是大多数的代码都是直接copy支付宝 提供的Demo,然后根据我们自己的业务需求修改。

      >> 由于篇幅问题,请注意静态代码块中的代码,会加载一个配置文件,这个配置文件支付宝同样有提供,我们只要修改一下其中的参数值,APPID、PID(商户UID)、以及加密的公钥和私钥。

      >> 我们程序支付整体的思路是这样:用户确认下单后,点击支付,然后会调用我们pay.do 这个接口,然后我们的程序在向支付宝服务器发送请求之前会添加一些参数,就是商品的订单号、收款平台信息、以及购买商品需要支付的总价格,需要修改的一处是 创建AlipayTradePrecreateRequestBuilder对象的时候,把call_back的URL修改成我们自己程序的接口,然后程序就向支付宝发送消息,因为这里支付宝集成做的特别好,只需要创建一个预支付对象,把需要的参数传进去,就可以发起预支付请求,返回二维码字节流,我们把二维码进行保存,然后展示给用户,进行扫码支付,用户扫码之后,不管是支付成功或者支付失败都会回调我们的call_back中配置的URL,进行处理。

     接下来就是我们的 call_back :

    @Controller
    @RequestMapping("/order/")
    public class OrderController {
        @RequestMapping("alipay_callback.do")
        @ResponseBody
        private ServerResponse callBack(HttpServletRequest request) throws AlipayApiException {
            // 取出支付宝回调携带的所有参数并进行转换,数组转换为字符串
            Map<String, String[]> tempParams = request.getParameterMap();
            //  参数存放 Map
            Map<String, String> requestParams = Maps.newHashMap();
            for (Iterator<String> iterator = tempParams.keySet().iterator(); iterator.hasNext(); ) {
                String key = iterator.next();
                String[] strs = tempParams.get(key);
                String str = "";
                // 这里如果数组的长度是1,说明只有一个,直接赋值就好,如果超过一个,后面加一个逗号来隔离
                for (int i = 0; i < strs.length; i++) {
                    str = strs.length - 1 == i ? str + strs[i] : str + strs[i] + ",";
                }
                requestParams.put(key, str);
            }
            // 去除sign_type
            requestParams.remove("sign_type");
            try {
                // 验证签名
                boolean result = AlipaySignature.rsaCheckV2(requestParams, Configs.getPublicKey(), "utf-8", Configs.getSignType());
                if (!result) {
                    return ServerResponse.createByErrorMessage("非法请求,再恶意请求我就报警找网警了");
                }
            } catch (AlipayApiException e) {
                logger.error("支付宝回调验证异常", e);
                e.printStackTrace();
                throw e;
            }
            // 调用Service 方法进行处理
            ServerResponse serverResponse = orderService.alipayCallBack(requestParams);
            if (!serverResponse.isSuccess()) {
                logger.error("OrderController.callBack()","数据操作失败");
                return ServerResponse.createBySuccess(Const.AlipayCallback.RESPONSE_FAILED);
            }
            logger.info("支付宝支付回调完成,没有异常");
            return ServerResponse.createBySuccess(Const.AlipayCallback.RESPONSE_SUCCESS);
        }
    }

     

      >> 这个支付宝回调URL是这样:http://smyang.s1.natapp.cc/order/alipay_callback.do,对应我们的Controller 的RequestMapping中的路由,代码中都有添加注释,还是很清晰的。

      >> 支付宝的验证签名的规则是怎样的呢?在通知返回的参数中除了sign_type和sign,其余的都是待验签的参数,详细请看 https://docs.open.alipay.com/194/103296/ ,但是这里只remove 掉了sign_type,通过查看源码发现,在支付宝集成代码中需要获取一下sign,然后它才remove 掉了sign,所以这里我们只需要remove掉sign_type就好,而且是必须的。

      >> 在 rsaCheckV2() 方法中我们加入的参数sign_type,指定了使用哪种加密方式来验签,如果通过就确定这个支付过程是安全的,同时 Configs 这个类也是支付宝给我们提供的,很到位的吧!

      >> 最后调用了Service方法进行我们的代码逻辑,比如:更新数据库中用户的支付状态,这个就是我们自己的业务需求了,在我们的业务代码中主要是通过支付宝回调参数中的 tradeStatus 这个字段来判断用户是否支付成功,具体的tradeStatus的状态,可以自行查看支付宝官方文档。

    当然,支付宝不止这一种支付方式,另外还有好多,可以自行去查看,官方文档才是我们最好的老师。好了,到这里就先结束了,后续如果有不足的地方再另行修改。

            The End。。。。。

     

  • 相关阅读:
    调用EasyPlayer播放器报错FlvPlayer.load() has been called,pleasse call unload() first!,如何处理?
    开发webrtc P2P连接报错DOMException: Failed to execute XXXXXX排查及优化
    异地视频共享/组网工具EasyNTS如何进行穿透接口的数据迁移?
    视频监控如何实现异地共享/组网?EasyNTS解决远程难题
    每日总结
    关于RHEL7.5无法使用yum命令的解决方法
    java后端学习-第一部分java基础:面向对象编程
    每日总结
    每日总结
    每日总结
  • 原文地址:https://www.cnblogs.com/gudu1/p/8094197.html
Copyright © 2020-2023  润新知