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

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

 

本篇为原创,转载请标出处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。。。。。

 

 

posted @ 2017-12-23 19:30  孤独是1态度  阅读(11560)  评论(0编辑  收藏  举报