JAVA PC端掃碼支付(一)微信支付

微信支付從配置到開發

1、配置php

一、開通公衆平臺支付功能前端

商戶號

微信支付功能先要申請微信(企業)公衆平臺,而後開通企業公衆平臺付功能。下圖爲微信(企業)公衆平臺頁面,能夠看到商戶號等信息java

輸入圖片說明

微信公衆號APPID

從開發-基本配置中獲取APPIDgit

輸入圖片說明

二、微信商戶平臺相關配置github

微信商戶平臺相關配置

由於微信公衆平臺調整,公衆平臺微信支付公衆號支付受權目錄、掃碼支付回調URL配置入口於2017年8月1日遷移至商戶平臺(pay.weixin.qq.com),因此微信支付配置和相關信息要登陸商戶平臺才能拿到。(估計是微信想要把公衆號的管理功能和開發功能分離)web

回調連接

輸入圖片說明

從微信商戶平臺的產品中心-開發配置-支付配置配置掃碼回調連接(掃碼回調連接就是你項目中微信支付回調函數名稱,這裏須要的是加了項目域名的函數全稱,必須保證能從公網訪問。爲何須要一個回調函數呢?這屬於微信支付的回調機制:當用戶使用微信支付完成後,你從本地是沒法得知是否支付成功的,而微信這邊在獲取到支付完成的狀態後,主動去訪問你所設置的回調函數地址,將支付狀態等相關信息返回,咱們只要在回調函數中判斷支付狀態,就可以便捷的進行下一步操做!)spring

設置API密鑰

輸入圖片說明

下載微信sdk

微信sdk是微信官方給出的微信支付demo,其中有不少好用的工具類,demo自己也能夠爲咱們的支付接口開發提供參考(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1)數據庫

[輸入圖片說明]

把sdk導入到項目中apache

輸入圖片說明

2、開發後端

主要業務流程

  • 主要業務流程

    (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;
    }
}

微信支付邏輯

  • 微信支付邏輯

    一、生成簽名,而後打包成【統一下單API】要求格式的訂單(參數列表),微信支付要求爲XMl格式

    二、調用【統一下單API】微信接口,將咱們打包好XMl格式的參數列表發送給【統一下單接口】,調用成功會接收到XMl格式的返回值,解析成咱們須要的格式,判斷是否請求 成功,成功的話是會返回code_url的

    三、而後咱們把code_url生成二維碼展示給用戶就OK了!

    四、用戶支付完成後,微信會訪問咱們的回調接口,根據返回的結果修改數據庫支付狀態

請求【統一下單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後端的工做就基本完成了,那前端怎麼知道用戶完成了支付呢?如今廣泛的辦法是,前端寫方法輪詢支付狀態,限定時間內查詢到支付狀態爲「已支付」就進行下一步操做,限定時間後未支付就作支付超時的操做。本項目用戶查詢支付狀態的代碼已經寫在了以前「與支付無關的業務邏輯」中了~

相關文章
相關標籤/搜索