zoukankan      html  css  js  c++  java
  • Spring使用支付宝扫码支付

    前一段一直在研究支付宝的扫码支付,不得不说,支付宝的文档写的真是一个烂(起码在下刚开始看的时候是mengbi的)。文档上面的示例和demo里面的示例长的完全不一样。往往文档上面的例子很简单,而demo的代码写的很复杂,所以一开始就不知道该采用哪个代码,后来仔细看了一下demo的那些包里面的代码,发现也是调用的文档示例的那些接口,这才明白它们原来是一个东西,只不过demo对文档的接口进行了一些包装而已。

    首先申请一个企业的支付宝账号,这个账号有个pid,需要向这个账号里面添加应用,每个应用都有一个appid,和一个公钥和私钥。公钥和私钥可以通过支付宝提供的工具生成,另外,java开发者需要使用pkcs6格式的私钥。如果应用需要使用扫码的功能,就需要在应用里面添加当面付的选项,这个需要签约。签约了当面付功能之后,还不能直接使用,因为应用需要上线才能使用,所以开发的时候可以使用沙箱版本的应用,支付宝提供的有沙箱版本的网关、支付宝公钥、pid和appid,在配置的时候需要修改过来。

    代码可以直接使用demo里面的代码,先在工程里面导入支付宝提供的api(注意不是demo代码),然后再导入demo代码,如图所示: 
    这里写图片描述

    这里写图片描述 
    这个com.alipay.demo.trade.Main文件是能够直接运行的,不过需要配置一个资源文件: 
    这里写图片描述

    # 支付宝网关名、partnerId和appId
    #此为沙箱环境的网关
    open_api_domain = https://openapi.alipaydev.com/gateway.do
    mcloud_api_domain = http://mcloudmonitor.com/gateway.do
    #此为沙箱环境的商户UID
    pid = 2088102172329883
    #此处请填写你沙箱环境当面付的APPID
    appid = 2016082000300485
    
    # RSA私钥、公钥和支付宝公钥
    #此处请填写你的商户私钥且转PKCS8格式
    private_key = MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAMKXZrFR+rnvYgBs9qz2cE1mCSIBReaqan+5Pf5+02Hyj4HzcNTTWqHFm91IH3wYPyhpM7XlbgJ5yWJtgC4g1lz75r8a+UCyuxP8by1LV/44Gi/TIfLSgATfQ73OcM9imXocRdYz2ZCwqi1gV+b3UDoy/Da5w07gRWizFzS6Vq1rAgMBAAECgYEAqHHc4GRBsRCKeinYtK1Vhqcj0Yg11Lvy85z3si0fNY26dvs8R5gFydzC/Mx5f8rNPUUYUHQn+4CqOR3D/c291X1iToV2NEVLHeJrOUDknP4oQriqt2w9pZ8rzwZp2jcWvRVUF4zTpEiMppmORP6spRfX6DLZg29SFI6GZWu6TkCQQDp3mim1BhuS3YONEZgqC69zn0/DGOFkeIx0S18qAu1X4I1FEjVTkY4HPdwihpgYajm0UFg1lk8mTiunHpZRCnAkEA1QF6U1AKjM6zsVdEnRXEDTCC75uVJGSYFJWHHx9Pjyd9vX8nSZV0Z0U4V0ZG0n0yvHj5LRO6U5FCqFRw1WixnQJBALmCKz8SvF/H9N6LiwmSPY6w5q82kNRlRc7wSceNspQT0wqL5+SACG98M0xXY5j1HmiOlHxgCTvyriXOwObivQcCQQCTNaNB4uZ3q/86R/KukbVd3DIRwLfRYAhO6Yxp8Oy+Je/bv/359+Vr3cXzYyldHZOr9/tVsPWr/Y9Q4JLemq1tAkEAlBU7+4EdzFap7e/FMgyKD5DmL8H2iAEuMRRCPL84GhFfK/7PSQ/40NgKxpTgY44NlElHXcRPw5CZu6gqdiNJOA==
    #此处请填写你的商户公钥
    public_key = MIGfMA0GCSqGSIbDQEBAQUAA4GNADCBiQKBgQDCl2axUfq572IAbPas9nBNZgkiAUXmqmp/uT3+ftNh8o+B83DU01qhxZvdSB98GD8oaTO15W4CeclibYAuINZc++a/GvlAsrsT/G8tS1f+OBov0yHy0oAE30O9znDPYpl6HEXWM9mQsKotYFfm91A6Mvw2ucNO4EVosxc0ulatawIDAQAB
    
    #此为沙箱环境的公钥
    alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB
    
    # 当面付最大查询次数和查询间隔(毫秒)
    max_query_retry = 5
    query_duration = 5000
    
    # 当面付最大撤销次数和撤销间隔(毫秒)
    max_cancel_retry = 3
    cancel_duration = 2000
    
    # 交易保障线程第一次调度延迟和调度间隔(秒)
    heartbeat_delay = 5
    heartbeat_duration = 900

    然后运行就可以运行Main.java文件了。至于我们实际应用中的扫码支付代码可以直接copy Main.java文件中的test_trade_precreate()函数,在Controller中建立一个函数:

    @RequestMapping(value = "/pay/alipay", method = RequestMethod.POST)
        public Map<String, String> alipay(@RequestParam String amount, @RequestParam int userid) {
    
            Map<String, String> map = new HashMap<String, String>();
    
            // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
            // 需保证商户系统端不能重复,建议通过数据库sequence生成,
            String outTradeNo = "xxxxx" + System.currentTimeMillis() + (long)(Math.random() * 10000000L);
    
            // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
            String subject = "支付";
    
            // (必填) 订单总金额,单位为元,不能超过1亿元
            // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
            String totalAmount = amount;
    
            // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
            // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
            String undiscountableAmount = "0";
    
            // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
            // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
            String sellerId = "2088102172329883";
    
            // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
            String body = "购买商品3件共20.00元";
    
            // 商户操作员编号,添加此参数可以为商户操作员做销售统计
            String operatorId = "test_operator_id";
    
            // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
            String storeId = "2088102172329883";
    
            // 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
            ExtendParams extendParams = new ExtendParams();
            extendParams.setSysServiceProviderId("2088100200300400500");
    
            // 支付超时,定义为120分钟
            String timeoutExpress = TIMEOUT;
    
    //        // 商品明细列表,需填写购买商品详细信息,
    //        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("http://xxx.xx.xxx.xxx:8080/baobiao/pay/notify");//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置,这里我们设置的是我们自己写的一个接口,等下会有介绍
    //                .setGoodsDetailList(goodsDetailList);
    
            AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
            switch (result.getTradeStatus()) {
                case SUCCESS:
                    log.info("支付宝预下单成功: )");
                    System.out.println("支付宝预下单成功: )");
    
                    AlipayTradePrecreateResponse response = result.getResponse();
    //                dumpResponse(response);
    //                System.out.println(response.getBody());
    
    //                // 需要修改为运行机器上的路径
    //                String filePath = String.format("/Users/liuyangkly/qr-%s.png", response.getOutTradeNo());
    //                log.info("filePath:" + filePath);
    //                ZxingUtils.getQRCodeImge(response.getQrCode(), 256, filePath);
    //                System.out.println(response.getQrCode());
    
                    //生成订单,插入数据库
                    BaobiaoOrder order = new BaobiaoOrder(userid, outTradeNo, "", Double.parseDouble(amount), new Date(), 1);
                    baobiaoOrderService.insertOrder(order);
    
                    map.put("status", "true");
                    map.put("qrcode", response.getQrCode()); //返回给客户端二维码
                    map.put("outtradeno", outTradeNo);
    
                    return map;
    
                case FAILED:
                    log.error("支付宝预下单失败!!!");
                    System.out.println("支付宝预下单失败!!!");
                    System.out.println(result.getResponse().getBody());
                    break;
    
                case UNKNOWN:
                    log.error("系统异常,预下单状态未知!!!");
                    System.out.println("系统异常,预下单状态未知!!!");
                    break;
    
                default:
                    log.error("不支持的交易状态,交易返回异常!!!");
                    System.out.println("不支持的交易状态,交易返回异常!!!");
                    break;
            }
            map.put("status", "false");
            map.put("msg", "系统出现异常,请稍后再试!");
            return map;
        }

    然后的逻辑就是用户会用手机扫码给支付宝付款,然后支付宝收到之后会发送一条支付成功的消息给我们设置的notify_url,如下所示:

    @RequestMapping(value = "/pay/notify", method = RequestMethod.POST)
        public String notifyResult(HttpServletRequest request, HttpServletResponse response) {
            log.info("收到支付宝异步通知!");
            Map<String, String> params = new HashMap<String, String>();
    
            //取出所有参数是为了验证签名
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String parameterName = parameterNames.nextElement();
                params.put(parameterName, request.getParameter(parameterName));
            }
            boolean signVerified;
            try {
                signVerified = AlipaySignature.rsaCheckV1(params, Configs.getAlipayPublicKey(), "UTF-8");
            } catch (AlipayApiException e) {
                e.printStackTrace();
                return "failed";
            }
            if (signVerified) {
                String outtradeno = params.get("out_trade_no");
                log.info(outtradeno + "号订单回调通知。");
    //            System.out.println("验证签名成功!");
                log.info("验证签名成功!");
    
                //若参数中的appid和填入的appid不相同,则为异常通知
                if (!Configs.getAppid().equals(params.get("app_id"))) {
                    log.warn("与付款时的appid不同,此为异常通知,应忽略!");
                    return "failed";
                }
    
                //在数据库中查找订单号对应的订单,并将其金额与数据库中的金额对比,若对不上,也为异常通知
                BaobiaoOrder order = baobiaoOrderService.findOrderByOuttradeno(outtradeno);
                if (order == null) {
                    log.warn(outtradeno + "查无此订单!");
                    return "failed";
                }
                if (order.getAmount() != Double.parseDouble(params.get("total_amount"))) {
                    log.warn("与付款时的金额不同,此为异常通知,应忽略!");
                    return "failed";
                }
    
                if (order.getStatus() == BaobiaoOrder.TRADE_SUCCESS) return "success"; //如果订单已经支付成功了,就直接忽略这次通知
    
                String status = params.get("trade_status");
                if (status.equals("WAIT_BUYER_PAY")) { //如果状态是正在等待用户付款
                    if (order.getStatus() != BaobiaoOrder.WAIT_BUYER_PAY) baobiaoOrderService.modifyTradeStatus(BaobiaoOrder.WAIT_BUYER_PAY, outtradeno);
                } else if (status.equals("TRADE_CLOSED")) { //如果状态是未付款交易超时关闭,或支付完成后全额退款
                    if (order.getStatus() != BaobiaoOrder.TRADE_CLOSED) baobiaoOrderService.modifyTradeStatus(BaobiaoOrder.TRADE_CLOSED, outtradeno);
                } else if (status.equals("TRADE_SUCCESS") || status.equals("TRADE_FINISHED")) { //如果状态是已经支付成功
                    if (order.getStatus() != BaobiaoOrder.TRADE_SUCCESS) baobiaoOrderService.modifyTradeStatus(BaobiaoOrder.TRADE_SUCCESS, outtradeno);
                } else {
                    baobiaoOrderService.modifyTradeStatus(BaobiaoOrder.UNKNOWN_STATE, outtradeno);
                }
                log.info(outtradeno + "订单的状态已经修改为" + status);
            } else { //如果验证签名没有通过
                return "failed";
            }
            return "success";
        }

    大概就是这样子,只不过少了给客户端发送支付成功的通知,还有一些安全性的问题。

    最后总结一下在这个过程中遇到的问题:

    1. 支付宝返回的二维码不能直接在浏览器中打开,而要用二维码转换工具来生成二维码,或者可以通过cli.im这个网站查看
    2. 支付宝沙箱环境生成的二维码只能用沙箱版本的手机支付宝来扫码,正常版本的支付宝扫会出现此二维码过期之类的错误
    3. 支付之后如果收不到支付宝发送的异步通知,可以使用postman等工具检查一下填写的notify_url是否能用公网ip访问到
    4. 如果遇到isv权限不足的问题就是因为没有签约或者应用没有添加相应的功能,应用没有上线也不能使用,开发的时候可以选择沙箱应用
    5. 沙箱版本的手机支付宝注册的时候收不到短信,可以联系客服索要一个账号
  • 相关阅读:
    准备 FRM 考试——方法、工具与教训
    930. 和相同的二元子数组 前缀和
    1906. 查询差绝对值的最小值 前缀和
    剑指 Offer 37. 序列化二叉树 二叉树 字符串
    815. 公交路线 BFS
    518. 零钱兑换 II dp 完全背包
    1049. 最后一块石头的重量 II dp
    5779. 装包裹的最小浪费空间 二分
    5778. 使二进制字符串字符交替的最少反转次数 字符串 滑动窗口
    474. 一和零 dp
  • 原文地址:https://www.cnblogs.com/barrywxx/p/8523759.html
Copyright © 2011-2022 走看看