SpringBoot微信扫码支付的实现示例

作者:代码中的余温 时间:2023-02-08 03:03:44 

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

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

    • 1.导入

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

  • 三、引入 WxPayServiceImpl 实现类

    • 四、引入WxPayService层

      • 五、引入Util类

        • 六、引入WxPayController类

          • 七、MD5加密

            • 总结

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


                 <!-- 生成二维码工具 -->
                 <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 你设置其他返回类型,就会跟你 给微信返的起冲突,就会导致报错

              来源:https://blog.csdn.net/weixin_48847506/article/details/113064052

              标签:SpringBoot,扫码支付
              0
              投稿

              猜你喜欢

            • Flutter实现笑嘻嘻的动态表情的示例代码

              2023-02-13 21:39:51
            • Android线程间通信 Handler使用详解

              2022-10-16 09:22:33
            • 总结Bean的三种自定义初始化和销毁方法

              2023-01-05 13:38:09
            • C#中WPF内存回收与释放LierdaCracker的实现

              2022-11-13 00:15:44
            • Android 判断SIM卡是中国移动\\中国联通\\中国电信(移动运营商)

              2023-01-22 04:20:27
            • C#基于QRCode实现动态生成自定义二维码图片功能示例

              2023-04-03 04:08:43
            • openFeign服务之间调用保持请求头信息处理方式

              2022-11-07 23:45:21
            • Android使用音频信息绘制动态波纹

              2022-11-05 00:51:50
            • kotlin源码结构层次详解

              2022-08-19 14:24:48
            • Java如何实现定时任务

              2021-11-07 02:57:38
            • Android开发之对话框案例详解(五种对话框)

              2021-08-07 11:37:47
            • 使用logback屏蔽一些包的日志

              2023-08-08 20:46:20
            • Android studio删除Android项目方法

              2022-01-24 01:56:05
            • SpringMVC如何用Post方式重定向

              2021-10-05 21:34:27
            • 详解java中产生死锁的原因及如何避免

              2022-04-22 00:36:14
            • java.lang.UnsatisfiedLinkError: %1 不是有效的Win32应用程序错误解决

              2022-06-14 23:21:51
            • Kotlin可见性修饰符详解

              2023-02-28 22:36:18
            • springboot自定义stater启动流程

              2023-06-07 06:33:45
            • java中JVM中如何存取数据和相关信息详解

              2023-08-10 03:49:46
            • SpringBoot整合阿里云短信服务的方法

              2022-03-24 18:17:11
            • asp之家 软件编程 m.aspxhome.com