JAVA PC端扫码支付(一)微信支付

微信支付从配置到开发

一、配置

1、开通公众平台支付功能

商户号

微信支付功能先要申请微信(企业)公众平台,然后开通企业公众平台付功能。下图为微信(企业)公众平台页面,可以看到商户号等信息

输入图片说明

微信公众号APPID

从开发-基本配置中获取APPID

输入图片说明

2、微信商户平台相关配置

微信商户平台相关配置

因为微信公众平台调整,公众平台微信支付公众号支付授权目录、扫码支付回调URL配置入口于2017年8月1日迁移至商户平台(pay.weixin.qq.com),所以微信支付配置和相关信息要登录商户平台才能拿到。(估计是微信想要把公众号的管理功能和开发功能分离)

回调链接

输入图片说明

从微信商户平台的产品中心-开发配置-支付配置配置扫码回调链接(扫码回调链接就是你项目中微信支付回调函数名称,这里需要的是加了项目域名的函数全称,必须保证能从公网访问。为什么需要一个回调函数呢?这属于微信支付的回调机制:当用户使用微信支付完成后,你从本地是无法得知是否支付成功的,而微信这边在获取到支付完成的状态后,主动去访问你所设置的回调函数地址,将支付状态等相关信息返回,我们只要在回调函数中判断支付状态,就能够便捷的进行下一步操作!)

设置API密钥

输入图片说明

下载微信sdk

微信sdk是微信官方给出的微信支付demo,其中有很多好用的工具类,demo本身也可以为我们的支付接口开发提供参考(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1)

[输入图片说明]

把sdk导入到项目中

输入图片说明

二、开发

主要业务流程

  • 主要业务流程

    (1)商户后台系统根据用户选购的商品生成订单。

    (2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;

    (3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url(code_url就是微信支付地址)。

    (4)商户后台系统根据返回的code_url生成二维码(用第三方插件生成二维码)。

    (5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。

    (6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。

    (7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。

    (8)微信支付系统根据用户授权完成支付交易。

    (9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。

    (10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。

这是官方给出的文档,这里再梳理一下。单纯做PC端扫一扫开发很简单,主要是向微信支付的【统一下单API】请求,发送订单信息和签名(签名比较麻烦,可能前期测试会报多次签名错误,不过官方SDK中有生成签名的方法,当然,自己也可以写),请求成功微信支付返回二维码链接code_url,注意这是微信支付的链接,不是二维码!不是二维码!不是二维码!二维码需要自己生成,不要直接就把code_url挂在页面上~

请求【统一下单API】的参数列表

输入图片说明

好了,上代码~

与支付无关的业务逻辑

  • 与支付无关的业务逻辑

    这里我单独创建一个类PayController来写自己的业务逻辑,生成业务订单啊,业务订单保存在数据库啊,查询订单信息啊,验证是否支付完成啊等等,我的业务逻辑比较简单,仅供参考~

  • package com.xxx.controller;
    import com.xxx.pojo.ProductOrders;
    import com.xxx.service.ProOrdersService;
    import com.xxx.util.testPay;
    import org.apache.ibatis.annotations.Param;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import java.text.SimpleDateFormat;
    import java.util.*;
    
    /**
     * 与支付无关的业务逻辑
     * */
    @Controller
    public class PayController {
        @Resource
        private ProOrdersService proOrdersService;//订单增删查改的接口
        /**
         * 调用接口 生成业务订单信息保存在数据库,并返回订单号
         *
         * @param filetxt
         * @return ordernum
         */
        @RequestMapping(value = "getOrder.do")
        @ResponseBody
        public Map getorder(HttpServletRequest request, @Param("filetxt") String filetxt) {
            Map<String, Object> map = new HashMap<>();
            //获取当前用户
            String username = (String) request.getSession().getAttribute("username");
            if (username.isEmpty())
            {
                map.put("type","2");
                map.put("data","用户登陆超时,请重新登陆");
                return map;
            }
            //订单对象,用户存储用户的订单信息,这里有些参数是请求【统一下单API】需要的,等我需要的时候就根据订单号从数据库取出相关参数
            ProductOrders productOrders = new ProductOrders();
            productOrders.setUserId(username);//用户
            productOrders.setOrdernumber(getOutTradeNo());//订单号是随机生成的16位唯一字符串,用于匹配订单
            productOrders.setProductId("XB001");//商品
            int wordnum = filetxt.trim().length();//字数
            productOrders.setQuantity(wordnum);//数量
            Integer pay1 = testPay.getPay1(wordnum);//计算价格
            productOrders.setTotalPrice(pay1);//总价
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
            String format = df.format(new Date());//日期格式转换
            productOrders.setOrdertime(format);
            productOrders.setOrderDetails(filetxt);//文章内容
            productOrders.setStatus(0);
            //设置订单详情格式
            try {
                int insert = proOrdersService.insert(productOrders);
            } catch (Exception e) {
                System.out.println("Exception:添加订单异常");
                e.printStackTrace();
            }
    
            //封装返回值
            map.put("orderid", productOrders.getOrdernumber());//订单号
            return map;
        }
        /**
         * 查询订单信息
         *
         * @param orderid
         * @return filetxt
         */
        @RequestMapping(value = "selectOrder.do")
        @ResponseBody
        public Map selectOrder(@Param("orderid") String orderid) {
            ProductOrders productOrders = this.proOrdersService.selectByOrderId(orderid);
            Map<String, Object> map = new HashMap<>();
            map.put("wordnum", productOrders.getQuantity());
            map.put("totelprice", productOrders.getTotalPrice());
            map.put("filetxt", productOrders.getOrderDetails());
            return map;
        }
        /**
         * 验证支付状态,这个是查询是否支付完成的方法,微信在支付完成后访问了我的回调方法,修改数据库的订单状态
         * 我通过此方法查询数据库中相关订单是否完成支付
         * @Param orderid
         */
        @RequestMapping(value = "OrderStatus.do")
        @ResponseBody
        public Map SelectOrderStatus(HttpServletRequest request, @Param("orderid") String orderid) {
            Map<String, Object> map = new HashMap<>();
            int i = this.proOrdersService.selectOrderStatus(orderid);
            if (i == 1)//支付成功
            {
                map.put("type", "SUCCESS");
                return map;
            }
            map.put("type", "FAIL");
            return map;
        }
        /**
         * 生成16位随机订单号
         * @return key
         */
        private static String getOutTradeNo() {
            SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());
            Date date = new Date();
            String key = format.format(date);
            Random r = new Random();
            key = key + r.nextInt();
            key = key.replaceAll("-", "").substring(0, 15);
            return key;
        }
    }

    微信支付逻辑

  • 微信支付逻辑

    1、生成签名,然后打包成【统一下单API】要求格式的订单(参数列表),微信支付要求为XMl格式

    2、调用【统一下单API】微信接口,将我们打包好XMl格式的参数列表发送给【统一下单接口】,调用成功会接收到XMl格式的返回值,解析成我们需要的格式,判断是否请求 成功,成功的话是会返回code_url的

    3、然后我们把code_url生成二维码展现给用户就OK了!

    4、用户支付完成后,微信会访问我们的回调接口,根据返回的结果修改数据库支付状态

  • 请求【统一下单API】返回参数列表

    输入图片说明

    将微信支付所需要的固定参数封装到类WXpayConfig中

    封装固定参数

  • package com.xxx.conf;
    
    public class WXpayConfig {
        public static String APPID = "wx830cXXXXXXX";//微信公众号APPID
        public static String WXPAYMENTACCOUNT = "xxxxxxxxxx";//微信公众号的商户号
        public static String APIKEY = "xxxxxxxxxxx";//微信公众号的商户支付密钥
        public static String basePath = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单请求地址
        public static String notify_url = "http://www.xxxxx.com.cn/wxPayCallBack.do";//回调地址
    }

    WXPayController,主控制器

  • import com.xxx.pojo.ProductOrders;
    import com.xxx.pojo.Products;
    import com.xxx.service.ProOrdersService;
    import com.xxx.service.ProductsService;
    import com.xxx.util.GetIPAdder;
    import com.xxx.util.QRCodeUtil;
    import com.xxx.conf.WXpayConfig;
    import com.github.wxpay.sdk.WXPayConstants;
    import com.github.wxpay.sdk.WXPayConstants.SignType;
    import org.apache.ibatis.annotations.Param;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import javax.annotation.Resource;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.*;
    import static com.github.wxpay.sdk.WXPayUtil.*;
    
    @Controller
    public class WXPayController {
        @Resource
        private ProOrdersService proOrdersService;//订单操作接口
        @Resource
        private ProductsService productsService;//产品操作接口
    
        /**
         * 支付主接口,用于控制整体支付流程
         * */
        @RequestMapping(value = "pay")
        @ResponseBody
        public Map createQRCode(HttpServletRequest request, HttpServletResponse response,
                                 @Param("orderid") String orderid) {
            Map<String,String> map=new HashMap<>();
            if (orderid.isEmpty())
            {
                map.put("type","2");
                map.put("data","订单号为空");
                return map;
            }
            ServletOutputStream sos = null;
            try {
                String orderInfo = createOrderInfo(orderid);//生成【统一下单API】所需参数的接口
                String code_url = httpOrder(orderInfo);//调用统一下单接口
                sos = response.getOutputStream();
                QRCodeUtil.encode(code_url, sos);//调用生成二维码的方法
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return map;
        }
        /**
         * 生成统一下单格式的订单,生成一个XML格式的字符串
         * @param orderId
         * @return
         */
        private String createOrderInfo(String orderId) throws Exception {
            return createOrderInfo(orderId, 1);
        }
    
        private String createOrderInfo(String orderId, Integer productid) throws Exception {
            Products products = productsService.selectByPrimaryKey(Long.valueOf(productid));//商品对象
            ProductOrders productOrders = this.proOrdersService.selectByOrderId(orderId);//订单信息
            //生成订单对象
            Map<String, String> map = new HashMap<>();
            map.put("appid", WXpayConfig.APPID);//公众账号ID
            map.put("mch_id", WXpayConfig.WXPAYMENTACCOUNT);//商户号
            map.put("body", productOrders.getOrderDetails());//商品描述
            map.put("nonce_str", generateUUID());
            map.put("notify_url", WXpayConfig.notify_url);//通知地址
            map.put("out_trade_no", orderId);//订单号
            map.put("spbill_create_ip", GetIPAdder.getMyIP());//终端ip
            map.put("trade_type", "NATIVE");//交易类型
            map.put("total_fee", String.valueOf(productOrders.getTotalPrice()));//总金额
            String sign = createSign(map, WXpayConfig.APIKEY);//调用生成签名的方法,用以Map集合中的相关参数生成签名
            map.put("sign", sign);//签名
            //将订单对象转为xml格式
            String s = null;
            try {
                return mapToXml(map);//maptoXml方法是微信sdk自带的方法
            } catch (Exception e) {
                e.printStackTrace();
            }
            return new String(s.getBytes("UTF-8"));
        }
    
        /**
         * 调统一下单API
         * @param orderInfo
         * @return
         */
        private String httpOrder(String orderInfo) {
            String url = WXpayConfig.basePath;
            try {
                HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
                //加入数据
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);
    
                BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
                buffOutStr.write(orderInfo.getBytes("UTF-8"));
                buffOutStr.flush();
                buffOutStr.close();
    
                //获取输入流
                BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
    
                String line = null;
                StringBuffer sb = new StringBuffer();
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                Map<String, String> map = xmlToMap(sb.toString());
                //返回字段很多,这里只取我们所需的字段
                String return_msg = map.get("return_msg");//返回信息
                String return_code = map.get("return_code");//状态码
                String result_code = map.get("result_code");//业务结果
                String code_url = map.get("code_url");
                //根据微信文档return_code 和result_code都为SUCCESS的时候才会返回code_url
                if (null != map && "SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)) {
                    return code_url;
                } else {
                    return null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 微信回调函数
         * 支付成功后微信服务器会调用此方法,修改数据库订单状态
         */
        @RequestMapping(value = "/wxPayCallBack.do")
        @ResponseBody
        public String wxPayCallBack(HttpServletRequest request, HttpServletResponse response) {
            System.out.println("回调成功");
            try {
                InputStream inStream = request.getInputStream();
                ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = inStream.read(buffer)) != -1) {
                    outSteam.write(buffer, 0, len);
                }
                outSteam.close();
                inStream.close();
                String result = new String(outSteam.toByteArray(), "utf-8");// 获取微信调用我们notify_url的返回信息
                Map<String, String> map = xmlToMap(result);
                if (map.get("result_code").equalsIgnoreCase("SUCCESS")) {
                    //返回成功后修改订单状态
                    String out_trade_no = map.get("out_trade_no");
                    this.proOrdersService.updateByOrderId(out_trade_no);
                }
            } catch (Exception e) {
    
            }
            return "SUCCESS";
        }
    
        /**
         * 生成签名
         * 这个方法是从微信sdk里copy过来的,自己也可以写,要注意生成签名后UTF-8的转换,要不然容易报签名Body UTF-8错误
         * @param data 待签名数据
         * @param key  API密钥
         */
        public static String createSign(final Map<String, String> data, String key) throws Exception {
            return createSign(data, key, SignType.MD5);
        }
    
        /**
         * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
         *
         * @param data     待签名数据
         * @param key      API密钥
         * @param signType 签名方式
         * @return 签名
         */
        private static String createSign(final Map<String, String> data, String key, SignType signType) throws Exception {
            //根据规则创建可排序的map集合
            Set<String> keySet = data.keySet();
            String[] keyArray = keySet.toArray(new String[keySet.size()]);
            Arrays.sort(keyArray);
            StringBuilder sb = new StringBuilder();
            for (String k : keyArray) {
                if (k.equals(WXPayConstants.FIELD_SIGN)) {
                    continue;
                }
                if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                    sb.append(k).append("=").append(data.get(k).trim()).append("&");
            }
            sb.append("key=").append(key);
            //转换UTF-8
            String str = new String(sb.toString().getBytes("UTF-8"));
            if (WXPayConstants.SignType.MD5.equals(signType)) {
                return MD5(sb.toString()).toUpperCase();
            } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
                return HMACSHA256(sb.toString(), key);
            } else {
                throw new Exception(String.format("Invalid sign_type: %s", signType));
            }
        }
    }

    如果请求【统一下单接口】的参数正确,签名也没有报错,那我们就能成功获取到code_url,从而生成二维码,让用户扫码支付了。

    生成二维码工具类QRCodeUtil

    使用了第三方工具类zxing,这里用到的zxing依赖包请自行下载

    package com.xxx.util;
    
    import com.google.zxing.BarcodeFormat;
    import com.google.zxing.EncodeHintType;
    import com.google.zxing.MultiFormatWriter;
    import com.google.zxing.common.BitMatrix;
    import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
    
    
    
    import javax.imageio.ImageIO;
    import java.awt.*;
    import java.awt.geom.RoundRectangle2D;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.OutputStream;
    import java.util.Hashtable;
    import java.util.Random;
    
    /**
     * 二维码工具类
     * 
     */
    public class QRCodeUtil {
    
    	private static final String CHARSET = "utf-8";
    	private static final String FORMAT_NAME = "JPG";
    	// 二维码尺寸
    	private static final int QRCODE_SIZE = 300;
    	// LOGO宽度
    	private static final int WIDTH = 60;
    	// LOGO高度
    	private static final int HEIGHT = 60;
    
    	private static BufferedImage createImage(String content, String imgPath, boolean needCompress) throws Exception {
    		Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
    		hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    		hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
    		hints.put(EncodeHintType.MARGIN, 1);
    		BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,hints);
    		int width = bitMatrix.getWidth();
    		int height = bitMatrix.getHeight();
    		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    		for (int x = 0; x < width; x++) {
    			for (int y = 0; y < height; y++) {
    				image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
    			}
    		}
    		if (imgPath == null || "".equals(imgPath)) {
    			return image;
    		}
    		// 插入图片
    		QRCodeUtil.insertImage(image, imgPath, needCompress);
    		return image;
    	}
    
    	/**
    	 * 插入LOGO
    	 * 
    	 * @param source
    	 *            二维码图片
    	 * @param imgPath
    	 *            LOGO图片地址
    	 * @param needCompress
    	 *            是否压缩
    	 * @throws Exception
    	 */
    	private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception {
    		File file = new File(imgPath);
    		if (!file.exists()) {
    			System.err.println("" + imgPath + "   该文件不存在!");
    			return;
    		}
    		Image src = ImageIO.read(new File(imgPath));
    		int width = src.getWidth(null);
    		int height = src.getHeight(null);
    		if (needCompress) { // 压缩LOGO
    			if (width > WIDTH) {
    				width = WIDTH;
    			}
    			if (height > HEIGHT) {
    				height = HEIGHT;
    			}
    			Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
    			BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    			Graphics g = tag.getGraphics();
    			g.drawImage(image, 0, 0, null); // 绘制缩小后的图
    			g.dispose();
    			src = image;
    		}
    		// 插入LOGO
    		Graphics2D graph = source.createGraphics();
    		int x = (QRCODE_SIZE - width) / 2;
    		int y = (QRCODE_SIZE - height) / 2;
    		graph.drawImage(src, x, y, width, height, null);
    		Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
    		graph.setStroke(new BasicStroke(3f));
    		graph.draw(shape);
    		graph.dispose();
    	}
    
    	/**
    	 * 生成二维码(内嵌LOGO)
    	 * 
    	 * @param content
    	 *            内容
    	 * @param imgPath
    	 *            LOGO地址
    	 * @param destPath
    	 *            存放目录
    	 * @param needCompress
    	 *            是否压缩LOGO
    	 * @throws Exception
    	 */
    	public static void encode(String content, String imgPath, String destPath, boolean needCompress) throws Exception {
    		BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
    		mkdirs(destPath);
    		String file = new Random().nextInt(99999999) + ".jpg";
    		ImageIO.write(image, FORMAT_NAME, new File(destPath + "/" + file));
    	}
    
    	/**
    	 * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
    	 * 
    	 * @author lanyuan Email: mmm333zzz520@163.com
    	 * @date 2013-12-11 上午10:16:36
    	 * @param destPath
    	 *            存放目录
    	 */
    	public static void mkdirs(String destPath) {
    		File file = new File(destPath);
    		// 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
    		if (!file.exists() && !file.isDirectory()) {
    			file.mkdirs();
    		}
    	}
    
    	/**
    	 * 生成二维码(内嵌LOGO)
    	 * 
    	 * @param content
    	 *            内容
    	 * @param imgPath
    	 *            LOGO地址
    	 * @param destPath
    	 *            存储地址
    	 * @throws Exception
    	 */
    	public static void encode(String content, String imgPath, String destPath) throws Exception {
    		QRCodeUtil.encode(content, imgPath, destPath, false);
    	}
    
    	/**
    	 * 生成二维码
    	 * 
    	 * @param content
    	 *            内容
    	 * @param destPath
    	 *            存储地址
    	 * @param needCompress
    	 *            是否压缩LOGO
    	 * @throws Exception
    	 */
    	public static void encode(String content, String destPath, boolean needCompress) throws Exception {
    		QRCodeUtil.encode(content, null, destPath, needCompress);
    	}
    
    	/**
    	 * 生成二维码
    	 * 
    	 * @param content
    	 *            内容
    	 * @param destPath
    	 *            存储地址
    	 * @throws Exception
    	 */
    	public static void encode(String content, String destPath) throws Exception {
    		QRCodeUtil.encode(content, null, destPath, false);
    	}
    
    	/**
    	 * 生成二维码(内嵌LOGO)
    	 * 
    	 * @param content
    	 *            内容
    	 * @param imgPath
    	 *            LOGO地址
    	 * @param output
    	 *            输出流
    	 * @param needCompress
    	 *            是否压缩LOGO
    	 * @throws Exception
    	 */
    	public static void encode(String content, String imgPath, OutputStream output, boolean needCompress)
    			throws Exception {
    		BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
    		ImageIO.write(image, FORMAT_NAME, output);
    	}
    
    	/**
    	 * 生成二维码
    	 * 
    	 * @param content
    	 *            内容
    	 * @param output
    	 *            输出流
    	 * @throws Exception
    	 */
    	public static void encode(String content, OutputStream output) throws Exception {
    		QRCodeUtil.encode(content, null, output, false);
    	}
    
    	public static void main(String[] args) throws Exception {
    		String text = "test";
    		QRCodeUtil.encode(text, "/Users/noahshen/Downloads/6BFAADD4-256D-447B-B742-1E1DFF11094F_meitu_1.png",
    				"/Users/noahshen/Downloads", true);
    		// QRCodeUtil.encode(text, null, "/Users/noahshen/Downloads", true);
    	}
    }

    前端轮询

    当用户支付完成后,微信成功调用了我们的回调方法,数据库订单状态修改为“已支付”,Java后端的工作就基本完成了,那前端怎么知道用户完成了支付呢?现在普遍的办法是,前端写方法轮询支付状态,限定时间内查询到支付状态为“已支付”就进行下一步操作,限定时间后未支付就做支付超时的操作。本项目用户查询支付状态的代码已经写在了之前“与支付无关的业务逻辑”中了

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值