zoukankan      html  css  js  c++  java
  • 二维码收款——后端API

    后端使用springboot

    API流程见下图,手机画图,见谅

    1. 用户通过微信公众号授权获取其openid,微信支付需要使用openid,授权的流程等同于登录,授权完成后将openid写入session表示登录成功,后面接口通信基于session验证是否登录
    2. 用户端输入金额后发起支付请求,接口 /create/pay/order
    3. 服务端收到支付请求后创建本系统订单并持久化,然后向微信支付统一下单接口发起请求获取微信支付必要参数,获取参数后返回给客户端(/create/pay/order接口的返回)
    4. 用户端(微信中)收到/create/pay/order接口的返回,本地拉起微信支付,用户支付成功
    5. 用户支付成功后,微信支付服务器会通知我的服务端,服务端将本系统订单状态修改为已支付,同时将订单支付成功的信息发送给语音播报程序(语音播报装在电脑上通过socket与服务端连接)

    注:服务端提供websocket服务,用于和语音播报程序长连接

    按流程顺序,核心代码如下:

    登录验证,通过springboot拦截器实现,拦截器类代码

    @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            logger.info("请求dump:{}", WebUtil.dumpRequest(request));
            String servletPath = WebUtil.getServletPathWithParam(request);
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }
            //进入拦截处理逻辑
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();
            //a.终端用户登录
            if (clazz.isAnnotationPresent(LoginUserRequired.class) || method.isAnnotationPresent(LoginUserRequired.class)) {
                LoginUserBean userBean = AuthHelper.getUserBean(request);
                logger.info("微信网页端登录:request中的用户=>{}", userBean);
                //如果request中已经有用户对象
                if (userBean != null) {
                    return true;
                }
                logger.info("微信网页登录失效,请重新授权");
                //ajax请求返回json
                if (WebUtil.isAjaxRequest(request)) {
                    String s = APIUtil.getReturn(401, "请重新登录");
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().print(s);
                } else {
                    //跳转到微信授权的页面
                    String referer = Base64.encodeBase64String(servletPath.getBytes(StandardCharsets.UTF_8));
                    response.sendRedirect("/c/auth/wechat?referer=" + referer);
                }
                return false;
            }
        return false;
    }

    微信授权代码(/c/auth/wechat接口),Controller中

    /**
         * 终端用户微信网页登录
         * @param code
         * @param state
         * @param referer base64编码的原始请求链接,带有全部get参数
         * @return
         */
        @RequestMapping(value = "/c/auth/wechat")
        public Object wxLogin(String code, String state, String referer, HttpServletRequest request,
                HttpServletResponse response) throws UnsupportedEncodingException {
            Object result;
            logger.info("微信登录 code=>{}, state=>{}, referer=>{}", code, state, referer);
            LoginUserBean userBean = AuthHelper.getUserBean(request);
            if (userBean != null) {
                logger.info("已经登录成功,无需再次微信授权,{}", userBean);
                if (StringUtils.isBlank(referer)) {
                    result = new MsgTipsView(MsgTipsView.Level.INFO, "提示", "已经登录");
                } else {
                    referer = new String(Base64.decodeBase64(referer));
                    result = "redirect:" + referer;
                }
                return result;
            }
            //微信授权第一次请求
            if (StringUtils.isAnyBlank(code, state)) {
                //authBackURL的host必须与微信后台设置的相同
                String basePath = appConf.getApiHost();
                String authBackURL = basePath + "/c/auth/wechat?referer=" + referer;
                try {
                    authBackURL = URLEncoder.encode(authBackURL, "utf-8");
                } catch (Exception e) {
                    logger.error("URLEncode错误, URL: {}, error: {}", authBackURL, e.getMessage());
                }
                logger.debug("authBackURL: " + authBackURL);
                String rand = RandomStringUtils.randomAlphanumeric(5);
                String wxAuthURL = gzhApiService.getWxGZHApiModule().gzhWebAuthURL(authBackURL, rand, "snsapi_base");
                result = "redirect:" + wxAuthURL;
                return result;
            }
            //微信授权完成,通过code获取用户信息
            WeixinUserInfo userInfo = gzhApiService.getWxGZHApiModule().getWeixinIDByAuth2Code(code, "snsapi_base");
            logger.info("获取的微信用户信息: {}", userInfo);
            if (userInfo == null) {
                result = new MsgTipsView(MsgTipsView.Level.ERROR, "微信登录", "获取微信信息失败");
                return result;
            }
            //获取openid成功,写入session以表示登录
            userBean = new LoginUserBean(userInfo.getOpenid());
            AuthHelper.writeUserBean(request, userBean);
            if (StringUtils.isBlank(referer)) {
                return new MsgTipsView(MsgTipsView.Level.SUCCESS, "登录成功", "登录成功");
            }
            //恢复最开始请求的页面
            return "redirect:" + new String(Base64.decodeBase64(referer));
        }

    本系统创建订单(/create/pay/order接口)

    /**
         * 创建支付订单
         * @param orderParam   支付订单所需的参数 {'mercNo': 'xxx','amount': '订单金额', 'memo': '订单备注', 'channel': '支付通道'}
         * @return 客户端实际支付所需的参数
         */
        @PostMapping(value = "/a/create/pay/order")
        @ResponseBody
        @LoginUserRequired
        public String createPayOrder(@RequestBody Map<String, String> orderParam, LoginUserBean userBean,
                HttpServletRequest request) {
            String openid = userBean.getWxOpenid();
            String mercNo = orderParam.getOrDefault("mercNo", "");
            String payChannel = orderParam.getOrDefault("channel", "");
            BigDecimal orderAmount = new BigDecimal(orderParam.getOrDefault("amount", "0.00"));
            String orderMemo = orderParam.getOrDefault("memo", "");
            //验证参数
            RMerc merc;
            if ((merc = mercService.selectMerc(mercNo)) == null) {
                return APIUtil.getReturn(1, "商户号不存在");
            }
            List<String> availableChannels = ImmutableList.of("wxpay");
            if (!availableChannels.contains(payChannel)) {
                return APIUtil.getReturn(1, "不支持的支付通道");
            }
            if (orderAmount.compareTo(BigDecimal.ZERO) <= 0 || orderAmount.scale() > 2) {
                return APIUtil.getReturn(1, "金额格式错误");
            }
            //微信支付
            if ("wxpay".equals(payChannel)) {
                RPayOrder order = payOrderService.createOrderAndSave(openid, mercNo, orderAmount, "wxpay", orderMemo);
                logger.info("创建支付订单:{}", JSON.toJSONString(order));
                //构造微信统一下单API所需的参数
                String orderId = order.getOrderId();
                int fee = orderAmount.multiply(new BigDecimal(100)).intValue();
                String ip = WebUtil.getRealIp(request);
                String notifyURL = getPayNotifyURL();
                GZHWxpayModule module = gzhApiService.getGzhWxpayModule();
                WxpayModule wxpayModule = module.getWxpayModule();
                String body = String.format("支付订单%s%s", orderId, StringUtils.isNotBlank(orderMemo) ? ":" + orderMemo : "");
                SortedMap<String, String> ret = wxpayModule.getJsApiPayParam(module.getAppid(), openid, orderId, body, fee,
                        ip, notifyURL);
                if (CollectionUtils.isEmpty(ret)) {
                    return APIUtil.getReturn(1, "系统错误");
                }
                return APIUtil.getReturn(APIConst.OK, ret);
            }
            return APIUtil.getReturn(1, "不支持的支付通道");
        }

    微信端拉起微信支付代码,见前一篇文章pay.js中代码

    微信支付成功后,微信支付系统回调我们的服务端

    /**
         * 微信支付回调通知
         * @param content   post的内容
         * @return 响应给微信支付的文本
         */
        @PostMapping(value = "/c/wxpay/notify")
        @ResponseBody
        public String wxpayNotifyBack(@RequestBody String content) {
            String resp = PayCommonUtil.setXML("FAIL", "exception");
            try {
                Map<String, String> resMap = XMLUtil.doXMLParse(content);
                logger.info("微信支付回调通知:{}", resMap);
                if (resMap == null) {
                    resp = "请求内容非法";
                    throw new RuntimeException("请求内容格式错误");
                }
                WxpayModule module = gzhApiService.getGzhWxpayModule().getWxpayModule();
                //验证签名
                if (!module.checkSignValid(resMap)) {
                    resp = PayCommonUtil.setXML("FAIL", "invalid sign");
                    throw new RuntimeException("签名错误");
                }
                String resCode = resMap.get("return_code");
                String bizCode = resMap.get("result_code");
                if ("FAIL".equals(resCode) || "FAIL".equals(bizCode)) {
                    logger.error("微信回调失败:{}", content);
                    resp = PayCommonUtil.setXML("FAIL", "weixin pay fail");
                    throw new RuntimeException("微信回调系统错误");
                }
                if (!"SUCCESS".equals(bizCode)) {
                    logger.error("微信支付系统业务错误:{}", content);
                    throw new RuntimeException("微信支付系统业务错误");
                }
                //------------支付成功----------------
                // 业务系统的订单号
                String outTradeNo = resMap.get("out_trade_no");
                // 微信支付的订单号
                String transactionId = resMap.get("transaction_id");
                // 支付金额(分)
                int totalFee = Integer.parseInt(resMap.get("total_fee"));
                //调用内部处理
                doActionWithWhenPaySuccess(outTradeNo, transactionId, totalFee);
                //给微信支付接口返回成功
                resp = PayCommonUtil.setXML("SUCCESS", "OK");
            } catch (Exception e) {
                logger.error("微信支付回调异常:{},{}", content, e.getMessage());
            }
            return resp;
        }
    
        /**
         * 支付成功后处理业务逻辑
         * @param outTradeNo        业务系统订单号
         * @param transactionId     支付方单号
         * @param totalFee          实际支付金额,分
         */
        private void doActionWithWhenPaySuccess(String outTradeNo, String transactionId, int totalFee) {
            RPayOrder order = payOrderService.queryById(outTradeNo);
            if (order == null || order.getOrderStatus() > 0) {
                return;
            }
            //验证实际支付的金额
            BigDecimal payAmount = new BigDecimal(totalFee).divide(new BigDecimal(100), 2, RoundingMode.UNNECESSARY);
            if (order.getAmount().compareTo(payAmount) != 0) {
                logger.info("实际支付金额与订单金额不符,订单:{},实际支付金额:{}元,订单金额:{}元", order.getOrderId(), payAmount, order.getAmount());
                return;
            }
            //填入支付信息,让回调队列去处理
            order.setPayOrderId(transactionId);
            order.setOrderStatus(1);
            orderCallbackTaskService.putTask(order);
        }
    OrderCallbackTaskService类专门用于订单支付成功后处理
    /**
     * 订单支付完成后回调任务服务
     */
    @Service
    public class OrderCallbackTaskService implements CommandLineRunner {
        private final Logger logger = LoggerFactory.getLogger(OrderCallbackTaskService.class);
    
        private LinkedBlockingDeque<RPayOrder> callbackOrderQueue = new LinkedBlockingDeque<>();
        @Resource private ThreadPoolTaskExecutor taskExecutor;
        @Resource private RPayOrderService payOrderService;
    
        @Override
        public void run(String... args) throws Exception {
            taskExecutor.execute(() -> {
                boolean exit = false;
                while (!exit) {
                    try {
                        RPayOrder order = callbackOrderQueue.take();
                        //1. 支付成功,更新订单状态
                        updateOrderStatus(order);
                    } catch (InterruptedException e) {
                        logger.error("订单回调队列中断");
                        exit = true;
                    }
                }
            });
        }
    
        /**
         * 向队列加入一个任务
         * @param order 订单
         */
        @SneakyThrows
        public void putTask(RPayOrder order) {
            callbackOrderQueue.put(order);
        }
    
        /**
         * 更新订单的状态
         * @param order 订单
         */
        private void updateOrderStatus(RPayOrder order) {
            boolean s = payOrderService.updatePayInfo(order.getOrderId(), order.getPayOrderId(), order.getOrderStatus());
            if (s) {
                //发送通知
                pushNotice(order);
            }
        }
    
        /**
         * 推送通知
         * @param order
         */
        private void pushNotice(RPayOrder order) {
            //给语音播报设备发送通知
            WebSocketService.sendMessage(order.getMercNo(), new RefuWsMessage<>("order", order));
        }
    }

    Java端websocket服务比较简单,参考文章很多

    相关文档资料

    微信公众号授权:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html

    微信支付:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml

    Websocket:https://www.cnblogs.com/onlymate/p/9521327.html

    下一篇,通知程序(语音播报)

     
  • 相关阅读:
    2017-09-13
    JavaSE07——异常
    FastDFS入门、搭建以及应用(转载)
    Centos7安装JDK1.8
    「扫盲」 Elasticsearch(转载)
    Java06——面向对象
    Java05——数组
    Java02——基础
    spring boot 配置文件配置项 数字特殊处理问题
    java动态代理机制之自定义实现动态代理
  • 原文地址:https://www.cnblogs.com/ieinstein/p/15756368.html
Copyright © 2011-2022 走看看