Spring Boot微信扫码支付

举报
王小贰 发表于 2021/01/27 16:55:07 2021/01/27
【摘要】 前言一、首先导入生成二维码和微信支付环境 <!-- 生成二维码工具 --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.2.1</version> <...

前言


一、首先导入生成二维码和微信支付环境

        <!-- 生成二维码工具 -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!-- 微信支付所需sdk -->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>

二、在application.yml文件配置微信所有需的基本配置

1.导入

代码如下(示例):

# 服务器域名地址
server:
  service-domain: //这里写你的域名地址
#微信app支付
pay:
  wxpay:
    app:
      appID: 微信appid
      mchID: 商户号
      key: //这个key实在微信支付公众品台自己定义的key 要求36位
      certPath: static/cert/wxpay/apiclient_cert.p12 # 从微信商户平台下载的安全证书存放的路径、我放在resources下面,切记一定要看看target目录下的class文件下有没有打包apiclient_cert.p12文件
      payNotifyUrl: # 微信支付成功的异步通知接口 这里引入你的回调接口。
      //这里直接写https://域名:端口/接口地址,注意一定是线上的接口,因为微信访问不到你本地的接口

2.创建MyWXPayConfig类引入配置信息

代码如下(示例):

package com.example.gasstation.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.io.InputStream;

@Data
@Component
@ConfigurationProperties(prefix = "pay.wxpay.app")
public class MyWXPayConfig implements WXPayConfig{

    /**
     * appID
     */
    private String appID;

    /**
     * 商户号
     */
    private String mchID;

    /**
     * API 密钥
     */
    private String key;

    /**
     * API证书绝对路径 (本项目放在了 resources/cert/wxpay/apiclient_cert.p12")
     */
    private String certPath;

    /**
     * HTTP(S) 连接超时时间,单位毫秒
     */
    private int httpConnectTimeoutMs = 8000;

    /**
     * HTTP(S) 读数据超时时间,单位毫秒
     */
    private int httpReadTimeoutMs = 10000;

    /**
     * 微信支付异步通知地址
     */
    private String payNotifyUrl;

    /**
     * 微信退款异步通知地址
     */
    private String refundNotifyUrl;

    /**
     * 统一下单url
     */
    private final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

	/** 这里实现了一个service层**/
    @Override
    public InputStream getCertStream() {
        InputStream certStream  =getClass().getClassLoader().getResourceAsStream(certPath);
        return certStream;
    }
    //在同层级下面新建WXPayConfig  service层
    package com.example.gasstation.config;
	import java.io.InputStream;
		public interface WXPayConfig {
		    InputStream getCertStream();//不要问我为啥不另起一行,因为我懒
		}
}


三、引入 WxPayServiceImpl 实现类

package com.example.gasstation.server.impl;

import com.example.gasstation.config.MyWXPayConfig;
import com.example.gasstation.entity.Result;
import com.example.gasstation.mapper.PayMapper;
import com.example.gasstation.model.Money_transfer;
import com.example.gasstation.model.Pay;
import com.example.gasstation.server.WxPayService;
import com.example.gasstation.util.HttpClientUtil;
import com.example.gasstation.util.WXPayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

@Service
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private MyWXPayConfig wxPayAppConfig;

    @Autowired
    private PayMapper payMapper;
    @Override
    public String save(String orderNo, double amount, String body,Integer uid) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
        // 1. 生成订单
        // 订单号,流水号,金额,付款状态,创建时间
        String product_id = WXPayUtils.generateUUID();
        Pay pay = new Pay();//这里新建一个实体类 用处存入数据库
        pay.setTradeNo(product_id);
        pay.setOutTradeNo(orderNo);
        pay.setBody(body);
        pay.setPaystatus(1);
        pay.setUid(uid);
        pay.setTotalAmount(amount);
        pay.setGmtCreate(df.format(new Date()));
        pay.setTradeStatus("0");
        pay.setAppId(wxPayAppConfig.getAppID());
        // 生成预支付订单,保存到数据库
        payMapper.insert(pay);
        // 调用统一下单方法,返回 codeUrl 地址
        String codeUrl = unifiedOrder(product_id,orderNo,amount,body);

        return codeUrl;
    }

    private String unifiedOrder(String product_id, String orderNo, double amount, String body){
        // 生成签名
        try{
            SortedMap<String, String> params = new TreeMap<>();
            params.put("appid",wxPayAppConfig.getAppID());
            params.put("mch_id",wxPayAppConfig.getMchID());
            params.put("nonce_str", WXPayUtils.generateUUID());
            params.put("body",body);                      // 商品描述
            params.put("out_trade_no",orderNo);           // 商户订单号
            params.put("total_fee",String.valueOf((int)(amount*100)));              // 标价金额(单位为分)
            params.put("spbill_create_ip", "这里写服务器IP");    // 终端IP

            params.put("notify_url", wxPayAppConfig.getPayNotifyUrl());    // 异步接收微信支付结果通知的回调地址
            params.put("trade_type","NATIVE");                  // 交易类型
            params.put("product_id",product_id);                // 微信支付要求NATIVE支付,此参数必填


            // sign 签名
            String sign = WXPayUtils.createSign(params, wxPayAppConfig.getKey());
            params.put("sign",sign);
            System.out.println(sign);

            // map转xml
            String payXml = WXPayUtils.mapToXml(params);
            System.out.println(payXml);

            // 统一下单
            String s = HttpClientUtil.doPost(wxPayAppConfig.getUNIFIED_ORDER_URL(), payXml, 10000);
            if(null == s){
                return null;
            }
            Map<String, String> unifiedOrderMap = WXPayUtils.xmlToMap(s);
            System.out.println(unifiedOrderMap.toString());
            if(unifiedOrderMap != null){
                // 前台添加定时器,进行轮询操作,直到支付完毕
                return unifiedOrderMap.get("code_url");
            }
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}


四、引入WxPayService层

package com.example.gasstation.server;

import com.example.gasstation.model.Money_transfer;

public interface WxPayService {

    String save(String orderNo, double amount, String body,Integer uid);

    boolean callBackPayUpdate(String outTradeNo,String totalFee);
}


五、引入Util类

package com.example.gasstation.util;

import com.github.wxpay.sdk.WXPayUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.*;

/**
 * @Author qjp
 */
public class WXPayUtils {
    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            WXPayUtils.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        Document document = WXPayXmlUtil.newDocument();
        Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }

    /**
     * 生成微信支付sign
     */
    public static String createSign(SortedMap<String, String> params, String key){
        StringBuilder sb = new StringBuilder();
        Set<Map.Entry<String, String>> es = params.entrySet();
        Iterator<Map.Entry<String, String>> it = es.iterator();
        while(it.hasNext()){
            Map.Entry<String, String> entry = it.next();
            String k = entry.getKey();
            String v = entry.getValue();
            if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=").append(key);
        String sign = MD5Util.MD5(sb.toString()).toUpperCase();

        return sign;
    }

    /**
     * 校验签名
     * @param params
     * @param key
     * @return
     */
    public static Boolean isCorrectSign(SortedMap<String, String> params, String key){
        String sign = createSign(params, key);
        String wxPaySign = params.get("sign").toUpperCase();

        return  wxPaySign.equals(sign);
    }
    /**
     * 获取有序map
     * @param map
     */
    public static SortedMap<String, String> getSortedMap(Map<String, String> map){
        SortedMap<String, String> sortedMap = new TreeMap<>();
        Iterator<String> it = map.keySet().iterator();
        while(it.hasNext()){
            String key = it.next();
            String value = map.get(key);
            String temp = "";
            if(null != value){
                temp = value.trim();
            }
            sortedMap.put(key, value);
        }
        return sortedMap;
    }

    /**
     * 日志
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * 获取当前时间戳,单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳,单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }
    /**
     * 生成UUID(用来表示一笔订单)
     * @return
     */
    public static String generateUUID(){
        String uuid = UUID.randomUUID().toString()
                .replaceAll("-","")
                .substring(0,32);
        return uuid;
    }
}


引入WXPayXmlUtil类


package com.example.gasstation.util;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;

public final class WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }
}

六、引入WxPayController类

提示:到这里没有报错咱们已经成功一半啦

@RestController
@RequestMapping("/wxPay")
public class WxPayController {

    @Autowired
    private WxPayService wxPayService;

    @Autowired
    private MyWXPayConfig wxPayConfig;

    @Autowired
    private WebMvcConfigurer webMvcConfigurer;

    /**
     * 微信支付 生成二维码
     *
     * @param money
     * @return
     */
    @GetMapping("/pay")
    public void wxPay(Double money,String body,Integer uid ,HttpServletResponse response){
        Double amount = money;//金额
        SimpleDateFormat date = new SimpleDateFormat("yyyyMMddHHmmss");
        String orderNo = date.format(new Date()) + WXPayUtils.getCurrentTimestampMs();
        String url_code = wxPayService.save(orderNo, amount, body,uid);
        System.out.println("url_code:----------"+url_code);
        if(url_code == null){
            throw new NullPointerException();
        }

        try {
            // 生成二维码配置
            Map<EncodeHintType, Object> hints = new HashMap<>();
            // 设置纠错等级
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
            // 编码类型
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");

            BitMatrix bitMatrix = new MultiFormatWriter().encode(url_code, BarcodeFormat.QR_CODE, 400, 400, hints);
            OutputStream outputStream = response.getOutputStream();

            MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 微信支付回调接口
     */
    @RequestMapping("/callback")
    public void OrderCallBack(HttpServletRequest request, HttpServletResponse response) {
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            // BufferedReader是包装设计模式,性能更高
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            StringBuffer stringBuffer = new StringBuffer();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuffer.append(line);
            }
            bufferedReader.close();
            inputStream.close();
            Map<String, String> callBackMap = WXPayUtils.xmlToMap(stringBuffer.toString());
            System.out.println(callBackMap.toString());

            SortedMap<String, String> sortedMap = WXPayUtils.getSortedMap(callBackMap);
            // 校验签名是否正确
            if (WXPayUtils.isCorrectSign(sortedMap, wxPayConfig.getKey())) {
                System.out.println("签名校验成功!");
                // 更新订单状态
                if ("SUCCESS".equals(sortedMap.get("result_code"))) {
                    String outTradeNo = sortedMap.get("out_trade_no"); // 流水号
                    String totalFee = sortedMap.get("total_fee"); // 交易金额
                    if (wxPayService.callBackPayUpdate(outTradeNo, totalFee)) {    // 通知微信订单处理成功
                        response.setContentType("text/xml");
                        response.setContentType("content-type");
                        response.getWriter().println("<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>");
                        //这里说明告诉微信你已经成功啦,别给老子重复回调我的方法啦,这里有一个坑,
                        response.setContentType("text/xml");
                        response.getWriter().println("SUCCESS")
                       //本身我就只有这两句话,然后就导致微信一直回调我的方法,废了半天的劲才搞好啦,
                       //原因就是格式不对,给他返回的值他不认识,这里可以看一下微信的支付开发文档,虽然文档写的很垃圾
                    }
                }
                // 未成功,就都处理为失败订单
                response.setContentType("text/html");
                response.getWriter().println("fail");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


七、MD5加密

@Slf4j
public class MD5Util {
   
    public static String MD5(String source) {
        return encodeMd5(source.getBytes());
    }
    private static String encodeMd5(byte[] source) {
        try {
            return encodeHex(MessageDigest.getInstance("MD5").digest(source));
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    private static String encodeHex(byte[] bytes) {
        StringBuffer buffer = new StringBuffer(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            if(((int) bytes[i] & 0xff) < 0x10) {
                buffer.append("0");
            }
            buffer.append(Long.toString((int) bytes[i] & 0xff, 16));
        }
        return buffer.toString();
    }
}


总结

有什么不对的地方欢迎各位码友指出问题,因为我也是第一次做这个微信扫码支付
回调方法返回类型是void 你设置其他返回类型,就会跟你 给微信返的起冲突,就会导致报错
有知道怎么解决的,欢迎指导。

此文章如有冲突,请联系我下架。上传这个只为记录,不作为其他用处

程序员不怕踩坑,就拍没坑可踩,加油打工人!

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。