JAVA生成二维码扫码进入h5微信支付宝支付

时间:2024-04-06 11:32:25

第一步准备

(1)微信需要的公众服务号和商户号;沙箱有很多问题,所以本人以正式的配置实现,其中公众号需要配置授权路径

其中公众号需配置

JAVA生成二维码扫码进入h5微信支付宝支付

商户号需到产品中心 -> 开发配置 -> 支付配置 ->添加JSAPI支付

JAVA生成二维码扫码进入h5微信支付宝支付

(2)支付宝需要的商户号

(3)WEB项目:本人的项目基于SpringBoot+SSM框架的网站项目

(4)demo:github链接


第二步生成二维码

(1)在pom.xml引入依赖:

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.0</version>
        </dependency>

(2)编写工具类QrCodeUtils:  

package com.meal.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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Random;

/**
 * 工程名:meal
 * 包名:com.meal.util
 * 文件名:QrCodeUtils.java
 * @author lcwen
 * @version $Id: QrCodeUtils.java 2020年3月10日 上午11:53:55 $
 */
public class QrCodeUtils {

    private static final String CHARSET = "utf-8";
    public static final String FORMAT = "JPG";
    
    // 二维码尺寸
    private static final int QRCODE_SIZE = 180;
    // LOGO宽度
    private static final int LOGO_WIDTH = 60;
    // LOGO高度
    private static final int LOGO_HEIGHT = 60;

    /**
     * 生成二维码
     *
     * @param content      二维码内容
     * @param logoPath     logo地址
     * @param needCompress 是否压缩logo
     * @return 图片
     * @throws Exception
     */
    public static BufferedImage createImage(String content, String logoPath, 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 (logoPath == null || "".equals(logoPath)) {
            return image;
        }
        // 插入图片
        QrCodeUtils.insertImage(image, logoPath, needCompress);
        return image;
    }

    /**
     * 插入LOGO
     *
     * @param source       二维码图片
     * @param logoPath     LOGO图片地址
     * @param needCompress 是否压缩
     * @throws IOException
     */
    private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = FileUtils.getResourceAsStream(logoPath);
            Image src = ImageIO.read(inputStream);
            int width = src.getWidth(null);
            int height = src.getHeight(null);
            if (needCompress) { // 压缩LOGO
                if (width > LOGO_WIDTH) {
                    width = LOGO_WIDTH;
                }
                if (height > LOGO_HEIGHT) {
                    height = LOGO_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();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 生成二维码(内嵌LOGO)
     * 二维码文件名随机,文件名可能会有重复
     *
     * @param content      内容
     * @param logoPath     LOGO地址
     * @param destPath     存放目录
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */
    public static String encode(String content, String logoPath, String destPath, boolean needCompress) throws Exception {
        BufferedImage image = QrCodeUtils.createImage(content, logoPath, needCompress);
        mkdirs(destPath);
        String fileName = new Random().nextInt(99999999) + "." + FORMAT.toLowerCase();
        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
        return fileName;
    }

    /**
     * 生成二维码(内嵌LOGO)
     * 调用者指定二维码文件名
     *
     * @param content      内容
     * @param logoPath     LOGO地址
     * @param destPath     存放目录
     * @param fileName     二维码文件名
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */
    public static String encode(String content, String logoPath, String destPath, String fileName, boolean needCompress) throws Exception {
        BufferedImage image = QrCodeUtils.createImage(content, logoPath, needCompress);
        mkdirs(destPath);
        fileName = fileName.substring(0, fileName.indexOf(".") > 0 ? fileName.indexOf(".") : fileName.length())
                + "." + FORMAT.toLowerCase();
        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
        return fileName;
    }

    /**
     * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.
     * (mkdir如果父目录不存在则会抛出异常)
     *
     * @param destPath 存放目录
     */
    public static void mkdirs(String destPath) {
        File file = new File(destPath);
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }

    /**
     * 生成二维码(内嵌LOGO)
     *
     * @param content  内容
     * @param logoPath LOGO地址
     * @param destPath 存储地址
     * @throws Exception
     */
    public static String encode(String content, String logoPath, String destPath) throws Exception {
        return QrCodeUtils.encode(content, logoPath, destPath, false);
    }

    /**
     * 生成二维码
     *
     * @param content      内容
     * @param destPath     存储地址
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */
    public static String encode(String content, String destPath, boolean needCompress) throws Exception {
        return QrCodeUtils.encode(content, null, destPath, needCompress);
    }

    /**
     * 生成二维码
     *
     * @param content  内容
     * @param destPath 存储地址
     * @throws Exception
     */
    public static String encode(String content, String destPath) throws Exception {
        return QrCodeUtils.encode(content, null, destPath, false);
    }

    /**
     * 生成二维码(内嵌LOGO)
     *
     * @param content      内容
     * @param logoPath     LOGO地址
     * @param output       输出流
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */
    public static void encode(String content, String logoPath, OutputStream output, boolean needCompress)
            throws Exception {
        BufferedImage image = QrCodeUtils.createImage(content, logoPath, needCompress);
        ImageIO.write(image, FORMAT, output);
    }

    /**
     * 生成二维码
     *
     * @param content 内容
     * @param output  输出流
     * @throws Exception
     */
    public static void encode(String content, OutputStream output) throws Exception {
        QrCodeUtils.encode(content, null, output, false);
    }

}

(3)调用工具类生产二维码(根据各自业务生成对应的二维码,其中二维码路径可用redis缓存并设置过期时间减少二维码频繁生成,过期可重新生成)


第三步:在微信和支付宝开发文档下载对应的SDK  DEMO

(1)微信支付宝引入依赖

<!-- 支付宝SDK -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.9.5.ALL</version>
        </dependency>
        
        <!-- 微信支付SDK -->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>
      
        <!-- XML转bean对象 -->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.10</version>
        </dependency>

(2)对应的包目录

   JAVA生成二维码扫码进入h5微信支付宝支付

(3)对应的代码:链接


第四步:扫描进入二维码对应的URL

(1)控制层处理

   /**
     * 扫描二维码进入控制器处理具体业务逻辑
     */

  @GetMapping(value = "****")
    public String payh5(@PathVariable String ***) throws Exception{
        String userAgent = request.getHeader("user-agent");
        if (userAgent == null
                || !(userAgent.contains("AlipayClient") || userAgent
                        .contains("MicroMessenger"))) {
            log.info("未知来源扫码进入付费模块,返回无页面...");
            return "****";
        }       
        if (userAgent.contains("AlipayClient")) {   //支付宝
            Map<String, Object> pay = vipChargeService.findParamForPay(***);//对应需要的参数
            request.setAttribute("info", pay.get("info")); //前端H5页面需要的参数
            request.setAttribute("payParam", pay.get("payParam"));//支付宝支付需要的参数
            return "school/vip/alipay_h5";
        }else{
            Map<String, Object> pay = vipChargeService.findParamForPay(****);//对应需要的参数
            request.setAttribute("appId", pay.get("appId"));//APPID - 公众号APPID
            request.setAttribute("url", pay.get("url")); //微信静默授权成功后  跳转到微信支付页面的URL
            request.setAttribute("state", pay.get("state"));//微信附加参数
            return "**/wx_auth"; //微信授权页面
        }
    }

 /**
     * 跳转到微信支付页 ,该处为微信授权成功后回调路径
     * @param state     微信返回附加参数
     * @param code      授权code
     * @return
     */
    @GetMapping(value = "/wxpayh5/")
    public String getWxpayParam(String state ,String code){
        if (StringUtils.isEmpty(state) || StringUtils.isEmpty(code)) {
            return "***/expire_h5";//过期页面
        }
        try {          
            vipChargeService.findWxPayMwebUrl(request,***, code);
            return "***/wxpay_h5";//支付页面
        } catch (Exception e) {
            log.error("微信获取支付参数出错,错误信息:", e.getMessage(), e);
            e.printStackTrace();
        }
        return "***/expire_h5";//过期页面
    }

(2)业务层处理vipChargeService

  @Override
    public Map<String, Object> findParamForPay(***,  int payType) throws Exception {     
        long time = redisGetService.getOrderExpireTime(***); //获取剩余过期时间单位秒
        Date expireTime = new Date(new Date().getTime() + 1000 * time);//获取过期的具体时间
        String backParams = ****;//附加参数       
        Map<String, Object> result = new HashMap<String, Object>();       
        if (payType == 1) {   //微信
            WXPayConfig config = new WxpayConfig();
            result.put("appId", config.getAppID());
            result.put("url", ****);
            result.put("state", backParams);
        }else {  //支付宝
            String payParam = AlipayUtil.getPayParam(****);
            payParam = payParam.replace("<script>document.forms[0].submit();</script>", "");
            result.put("payParam", payParam);      

    
            Map<String, Object> info = new HashMap<String, Object>();
            //TODO   添加页面需要的参数
            result.put("info", info);
        }
        return result;
    }

  @Override
    public void findWxPayMwebUrl(HttpServletRequest request, ***, String code)
            throws Exception {
        String openid = WxSignUtil.accessWithOpenid(code);//获取微信用户openid

       //TODO 获取相关的参数
        long time = redisGetService.getOrderExpireTime(***);
        Date expireTime = new Date(new Date().getTime() + 1000 * time);
        
        String ip = *****;//获取客户端IP地址 -> 自己百度
        String notify_url = UrlConstants.VIP_NOTIFY_URL;
        String attach = "****";//附带参数
        Map<String, Object> result = WxPayUtil.getH5PayParam(***, openid);
        if (result == null) {
            throw new Exception("获取微信订单参数失败");
        }
        Map<String, Object> info = new HashMap<String, Object>();
        //TODO 返回至支付页面需要的参数
        request.setAttribute("info", info);
        request.setAttribute("result", result);
    }

(3)微信的授权页面、微信的支付页面、支付宝的支付页面:链接


第五步:异步通知

(1)支付宝异步通知/同步通知

package com.meal.module.school.api;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.alipay.api.internal.util.AlipaySignature;
import com.meal.constant.UrlRoot;
import com.meal.module.admin.order.service.PcOrderService;
import com.meal.pay.alipay.config.AlipayConfig;

/**
 * 文件名:AlipayController.java
 * @author lcwen
 * @version $Id: AlipayController.java 2020年3月11日 上午11:34:45 $
 */
@Controller
@RequestMapping(value = UrlRoot.ALIPAY_NOTIFY_URL)
public class AlipayController {
    
    private static final Logger log = LoggerFactory.getLogger(AlipayController.class);
    
    @Autowired
    private HttpServletRequest request;
    
    @RequestMapping(value = "/notify_url")
    public void notify_url(){
        try {
            boolean signVerified = signVerify(request);
            if (!signVerified) {// 是否验证不成功
                log.error("支付宝异步通知验证签名失败");
                return;
            }    
            
            String trade_status = new String(request.getParameter(
                    "trade_status").getBytes("ISO-8859-1"), "UTF-8");
            if (!trade_status.equals("TRADE_FINISHED") && !trade_status.equals("TRADE_SUCCESS")) {
                log.info("支付宝异步通知回调结果:失败");
                return;
            }
            
            String out_trade_no = request.getParameter("out_trade_no");
            String trade_no = request.getParameter("trade_no");
            //TODO处理订单      
            //**********************
        } catch (Exception e) {
            log.error("支付宝异步通知出错,错误信息:",e.getMessage(),e);
            e.printStackTrace();
        }
    }
    
    /**
     * 简要说明:同步路径 <br>
     * 创建者:lcwen
     * 创建时间:2020年3月12日 下午5:47:17
     * @return
     */
    @RequestMapping(value = "/return_url")
    public String return_url(){
        try {
            boolean signVerified = signVerify(request);
            if (!signVerified) {
                request.setAttribute("msg", "支付失败,签名验证错误!");
                return "******"; //支付失败页面
            }           
            return "*****";//支付成功页面       
        } catch (Exception e) {
            request.setAttribute("msg", "支付失败,同步通知出错!");
            log.error("支付宝同步通知出错,错误信息:",e.getMessage(),e);
            e.printStackTrace();
        }
        return "*****";    //支付失败页面   
    }
    
    /**
     * 简要说明:支付宝签名验证 <br>
     *************************页面功能说明*************************
     * 创建该页面文件时,请留心该页面文件中无任何HTML代码及空格。
     * 该页面不能在本机电脑测试,请到服务器上做测试。请确保外部可以访问该页面。
     * 如果没有收到该页面返回的 success
     * 建议该页面只做支付成功的业务逻辑处理,退款的处理请以调用退款查询接口的结果为准。
     * 创建者:lcwen
     * 创建时间:2020年3月12日 下午5:00:28
     * @param request
     * @return
     * @throws Exception
     */
    private boolean signVerify(HttpServletRequest request) throws Exception{
        boolean signVerified = false;
        // 获取支付宝POST过来反馈信息
        Map<String, String> params = new HashMap<String, String>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter
                .hasNext();) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            // 乱码解决,这段代码在出现乱码时使用
            valueStr = new String(valueStr.getBytes("utf-8"), "utf-8");
            params.put(name, valueStr);
        }
        signVerified = AlipaySignature.rsaCheckV1(params,
                AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET,
                AlipayConfig.SIGNTYPE); // 调用SDK验证签名
        return signVerified;
    }

}

(2)微信异步通知

package com.meal.module.school.api;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.math.BigDecimal;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;
import com.meal.constant.UrlRoot;
import com.meal.module.admin.order.service.PcOrderService;
import com.meal.pay.wxpay.config.WxpayConfig;
import com.meal.pay.wxpay.pojo.NotifyInfo;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;

/**
 * 文件名:WxpayController.java
 * @author lcwen
 * @version $Id: WxpayController.java 2020年3月11日 上午11:35:02 $
 */
@Controller
@RequestMapping(value = ****)
public class WxpayController {
    
    private static final Logger log = LoggerFactory.getLogger(WxpayController.class);
    
    @Autowired
    private HttpServletRequest request;
    
    @Autowired
    private HttpServletResponse response;
    
    /**
     * 简要说明:异步通知 <br>
     * 详细说明:TODO
     * 创建者:lcwen
     * 创建时间:2020年3月13日 上午10:39:26
     * 更新者:
     * 更新时间:
     */
    @RequestMapping(value = "/notify_url")
    public void notify_url() throws Exception{
        String notityXml = "";
        String inputLine = "";
        while ((inputLine = request.getReader().readLine()) != null) {
            notityXml += inputLine;
        }
        request.getReader().close();
        
        WXPayConfig config = new WxpayConfig();
        XStream xs = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-",
                "_")));
        xs.alias("xml", NotifyInfo.class);
        NotifyInfo ntfInfo = (NotifyInfo)xs.fromXML(notityXml.toString());
        
        // 验证签名是否正确
        boolean isSign = WXPayUtil.isSignatureValid(notityXml, config.getKey());
        if(!isSign){
            signFail(response);
            return;
        }
        if (!"SUCCESS".equals(ntfInfo.getReturn_code())
                || !"SUCCESS".equals(ntfInfo.getResult_code())) {
            payFail(response,ntfInfo.getErr_code());
            return;
        }
        
        //订单号、微信交易号、
        String out_trade_no = ntfInfo.getOut_trade_no();
        String trade_no = ntfInfo.getTransaction_id();
        
        //TODO 订单处理
        //**************************
        payOk(response);
    }
    
    /**
     * 支付成功
     */
    private static void payOk(HttpServletResponse response){
        log.info("================  微信支付异步回调支付成功返回  =================");
        String resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code>"
                + "<return_msg><![CDATA[OK]]></return_msg></xml> ";
        flushResponse(response, resXml);
    }
    
    
    /**
     * 支付失败
     */
    private static void payFail(HttpServletResponse response ,String errCode){
        log.info("================  微信支付异步回调支付失败返回  =================");
        String resXml = "<xml><return_code><![CDATA[FAIL]]></return_code>"
                + "<return_msg><![CDATA[" + errCode + "]]></return_msg></xml>";
        flushResponse(response, resXml);
    }
    
    /**
     * 签名失败
     */
    private static void signFail(HttpServletResponse response){
        log.info("================  微信支付异步回调签名失败  =================");
        String resXml = "<xml><return_code><![CDATA[FAIL]]></return_code>"  
                + "<return_msg><![CDATA[签名有误]]></return_msg></xml> ";
        flushResponse(response, resXml);
    }
    
    private static void flushResponse(HttpServletResponse response ,String resXml){
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(response.getOutputStream());      
            out.write(resXml.getBytes("utf-8"));
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}


第六部:效果(实际项目)

JAVA生成二维码扫码进入h5微信支付宝支付

                    JAVA生成二维码扫码进入h5微信支付宝支付                                 JAVA生成二维码扫码进入h5微信支付宝支付