JAVA PC-side scan code payment (1) WeChat payment

WeChat payment from configuration to development

One, placement

1. Open the payment function of the public platform

business number

The WeChat payment function must first apply for the WeChat (enterprise) public platform, and then open the enterprise public platform payment function. The picture below shows the WeChat (enterprise) public platform page, where you can see the merchant number and other information

Enter image description

WeChat public account APPID

Get APPID from dev-base config

Enter image description

2. Related configuration of WeChat merchant platform

WeChat merchant platform related configuration

Due to the adjustment of the WeChat public platform, the WeChat payment official account payment authorization directory of the public platform, and the configuration entry of the scan code payment callback URL were migrated to the merchant platform (pay.weixin.qq.com) on August 1, 2017. Therefore, the WeChat payment configuration and related The information can only be obtained by logging in to the merchant platform. (It is estimated that WeChat wants to separate the management function and development function of the official account)

callback link

Enter image description

From the product center - development configuration - payment configuration of the WeChat merchant platform, configure the scan code callback link (the scan code callback link is the name of the WeChat payment callback function in your project, what is needed here is the full name of the function with the project domain name added, and it must be guaranteed to be available from the public Internet access. Why do you need a callback function? This belongs to the callback mechanism of WeChat payment: when the user completes the payment using WeChat, you cannot know whether the payment is successful locally, and WeChat obtains the payment completion status after the payment is completed. , take the initiative to access the callback function address you set, and return the payment status and other related information. We can easily proceed to the next step as long as we judge the payment status in the callback function!)

Set up API key

Enter image description

download wechat sdk

WeChat sdk is the WeChat payment demo officially given by WeChat. There are many useful tools in it. The demo itself can also provide a reference for our payment interface development ( https://pay.weixin.qq.com/wiki/doc/ api/native.php?chapter=11_1)

[Enter image description]

Import the sdk into the project

Enter image description

2. Development

Main business process

  • Main business process

    (1) Merchant's back-end system generates orders according to the products purchased by users.

    (2) After the user confirms the payment, the user calls WeChat Pay [Unified Order API] to generate a prepayment transaction;

    (3) After receiving the request, the WeChat payment system generates a prepaid transaction slip, and returns the QR code link code_url of the transaction session (code_url is the WeChat payment address).

    (4) The merchant's background system generates a QR code according to the returned code_url (using a third-party plug-in to generate the QR code).

    (5) The user opens WeChat and scans the QR code, and the WeChat client sends the scanned code content to the WeChat payment system.

    (6) The WeChat payment system receives the client request, and after verifying the validity of the link, initiates user payment and requires user authorization.

    (7) The user enters the password on the WeChat client, and after confirming the payment, the WeChat client submits the authorization.

    (8) The WeChat payment system completes the payment transaction according to the user's authorization.

    (9) After the WeChat payment system completes the payment transaction, it returns the transaction result to the WeChat client, and informs the user of the transaction result through SMS and WeChat messages. The WeChat client displays the payment transaction result page.

    (10) The WeChat payment system notifies the merchant's background system of the payment result by sending an asynchronous message. The merchant's back-end system needs to reply to the reception status and notify the WeChat back-end system to no longer send the payment notification for the order.

This is the official document, and I will sort it out here. It is very simple to simply scan and develop on the PC side. The main thing is to make a request to WeChat Pay's [Unified Order API], and to send order information and signature (signature is more troublesome, and may report multiple signature errors in the early test, but the official SDK has The method of generating the signature, of course, you can also write it yourself), request a successful WeChat payment and return the QR code link code_url, note that this is the WeChat payment link, not a QR code! Not a QR code! Not a QR code! The QR code needs to be generated by yourself, don't directly hang the code_url on the page~

Request the parameter list of [Unified Order API]

Enter image description

Okay, let's get to the code~

Business logic not related to payment

  • Business logic not related to payment

    Here I create a separate class PayController to write my own business logic, generate business orders, save business orders in the database, query order information, verify whether the payment is completed, etc. My business logic is relatively simple, for reference only~

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

WeChat payment logic

  • WeChat payment logic

    1. Generate a signature, and then package it into an order (parameter list) in the format required by [Unified Order API], WeChat payment requires XMl format

    2. Call the [Unified Order API] WeChat interface, and send the parameter list we packaged in XMl format to the [Unified Order API]. If the call is successful, the return value in XMl format will be received, parsed into the format we need, and judged whether If the request is successful, the code_url will be returned if successful.

    3. Then we generate a QR code from code_url and show it to the user, and it's OK!

    4. After the user's payment is completed, WeChat will access our callback interface and modify the database payment status according to the returned result.

Request [Unified Order API] to return a list of parameters

Enter image description

Encapsulate the fixed parameters required by WeChat payment into the class WXpayConfig

Package fixed parameters

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, the main controller

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

If the parameters of the request [Unified Ordering Interface] are correct and the signature does not report an error, then we can successfully obtain the code_url, thereby generating a QR code, allowing the user to scan the code to pay.

Generate QR code tool class QRCodeUtil

The third-party tool class zxing is used, please download the zxing dependency package used here

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: [email protected]
	 * @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);
	}
}

Front-end polling

When the user's payment is completed, WeChat successfully calls our callback method, the database order status is changed to "paid", and the Java back-end work is basically completed. How does the front-end know that the user has completed the payment? The common method now is that the front-end writing method polls the payment status, and the next step is performed when the payment status is "paid" within a limited time. The code for users of this project to query the payment status has been written in the previous "business logic not related to payment"~

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325195100&siteId=291194637