Springboot整合微信支付 --- 付款码支付

2023-10-27

场景介绍

在这里插入图片描述

开发指引

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

接入准备

下面是我们必须带入的几个值,需要自己去 微信支付官网 获取

在这里插入图片描述

所需依赖

 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--微信支付-->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>


        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

			<!--解密依赖-->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk16</artifactId>
            <version>1.46</version>
        </dependency>

配置文件

MyWXPayConfig.java


import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

@Configuration
public class MyWXPayConfig implements WXPayConfig {

   /**
    * appid
    * @return
    */
   public String getAppID() {
      return "小程序appid";
   }

   /**
    * 商户号
    * @return
    */
   public String getMchID() {
      return "商户号";
   }

   /**
    * API密钥
    */
   public String getKey() {
      return "v2API密钥";
   }

   /**
    * 证书
    * @return
    */
   public InputStream getCertStream() {
      try {
         return new FileInputStream("apiclient_cert.p12");
      } catch (FileNotFoundException e) {
         throw new RuntimeException("私钥文件不存在", e);
      }
   }

   /**
    * 回调通知地址
    * @return
    */
   public String getNotifyDomain(){
      return "https://bcae-119-39-4-32.jp.ngrok.io";
   }

   public int getHttpConnectTimeoutMs() {
      return 8000;
   }

   public int getHttpReadTimeoutMs() {
      return 10000;
   }

	/**
	* 将配置文件及参数带入获取微信支付的接口调取类
	*/
   public WXPay getWxPay(){
      return new WXPay(this);
   }

}

工具类

** 回调通知解密工具类 AESUtil.java**


import com.github.wxpay.sdk.WXPayUtil;
import com.hai.wxpaycode.config.MyWXPayConfig;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Base64;

/**
 * 微信支付AES加解密工具类
 *
 * @author yclimb
 * @date 2018/6/21
 */
@Component
public class AESUtil {




    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "AES";

    /**
     * 加解密算法/工作模式/填充方式
     */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";

    /**
     * 生成key
     */
    private static SecretKeySpec KEY;

    static {
        MyWXPayConfig myWXPayConfig = new MyWXPayConfig();
        try {
            KEY = new SecretKeySpec(WXPayUtil.MD5(myWXPayConfig.getKey()).toLowerCase().getBytes(), ALGORITHM);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * AES加密
     *
     * @param data d
     * @return str
     * @throws Exception e
     */
    public static String encryptData(String data) throws Exception {
        // 创建密码器
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        // 初始化
        cipher.init(Cipher.ENCRYPT_MODE, KEY);
        return base64Encode8859(new String(cipher.doFinal(data.getBytes()), "ISO-8859-1"));

    }

    /**
     * AES解密
     *
     * @param base64Data 64
     * @return str
     * @throws Exception e
     */
    public static String decryptData(String base64Data) throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        cipher.init(Cipher.DECRYPT_MODE, KEY);
        return new String(cipher.doFinal(base64Decode8859(base64Data).getBytes("ISO-8859-1")), "utf-8");
    }

    /**
     * Base64解码
     * @param source base64 str
     * @return str
     */
    public static String base64Decode8859(final String source) {
        String result = "";
        final Base64.Decoder decoder = Base64.getDecoder();
        try {
            // 此处的字符集是ISO-8859-1
            result = new String(decoder.decode(source), "ISO-8859-1");
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * Base64加密
     * @param source str
     * @return base64 str
     */
    public static String base64Encode8859(final String source) {
        String result = "";
        final Base64.Encoder encoder = Base64.getEncoder();
        byte[] textByte = null;
        try {
            //注意此处的编码是ISO-8859-1
            textByte = source.getBytes("ISO-8859-1");
            result = encoder.encodeToString(textByte);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }
}

获取ip地址工具类 IPUtil.java

/**
 * @author LiFucheng
 * @version 1.0
 * @description: TODO 获取ip地址工具
 * @date 2022/5/2 15:19
 */
public class IPUtil {
    //获取真实IP
    public static String getRemoteHost(javax.servlet.http.HttpServletRequest request){
        String ip = request.getHeader("x-forwarded-for");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }
        return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
    }
}

**订单号工具类 OrderNoUtils.java **

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * 订单号工具类
 *
 * @author qy
 * @since 1.0
 */
public class OrderNoUtils {

    /**
     * 获取订单编号
     * @return
     */
    public static String getOrderNo() {
        return "ORDER_" + getNo();
    }

    /**
     * 获取退款单编号
     * @return
     */
    public static String getRefundNo() {
        return "REFUND_" + getNo();
    }

    /**
     * 获取编号
     * @return
     */
    public static String getNo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String newDate = sdf.format(new Date());
        String result = "";
        Random random = new Random();
        for (int i = 0; i < 3; i++) {
            result += random.nextInt(10);
        }
        return newDate + result;
    }

}

做完这些准备工作我们就可以开始正式的去调取支付接口了

调起支付

开始支付

/**
     * 付款码支付
     * @param request
     */
    @GetMapping("/weChatPayMicroPay")
    public void weChatPayMicroPay(HttpServletRequest request) throws Exception {

        String auth_code = request.getParameter("auth_code");

        //组装参数
        Map<String,String> requestData = new HashMap<String, String>();
        //商品描述
        requestData.put("body","测试商品");
        // 支付的商户订单号
        requestData.put("out_trade_no", OrderNoUtils.getOrderNo());
        // 支付金额
        requestData.put("total_fee","1");
        // 终端IP
        requestData.put("spbill_create_ip",IPUtil.getRemoteHost(request));
        // 付款码
        requestData.put("auth_code",auth_code);
        //发起支付
        Map<String, String>  mapBack = myWXPayConfig.getWxPay().microPay(requestData);
        if("SUCCESS".equals(mapBack.get("return_code"))){
             //支付成功,处理业务
             System.out.println("支付成功");
        }else{
            System.out.println("通信异常信息====="+mapBack.get("return_msg"));
        }
    }

撤销订单 (需要证书—证书使用)

/**
     * 撤销订单
     * @param outTradeNo 商户订单号
     */
    @GetMapping("/weChatPayReverse")
    public void weChatPayReverse(String outTradeNo) throws Exception {
        //获取配置
        WXPay wxPay = new WXPay(myWXPayConfig);
        Map<String,String> requestData = new HashMap<String, String>();
        //传入商户订单号    这里还可以传入微信的订单号, 具体可以去看官方的api文档
        requestData.put("out_trade_no",outTradeNo);
        myWXPayConfig.getWxPay().reverse(requestData);
    }

查询订单

 /**
     * 查询订单
     * @param orderNo 订单号
     * @throws Exception
     */
    @GetMapping("/weChatPayOrderQuery")
    public void weChatPayOrderQuery(@RequestParam(required = true,value = "orderNo")String orderNo) throws Exception {

        Map<String,String> requestData = new HashMap<String, String>();

        requestData.put("out_trade_no", orderNo);
        Map<String, String>  mapBack = null;
        try {
            mapBack = myWXPayConfig.getWxPay().orderQuery(requestData);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

申请退款 ( 需要证书—证书使用)

/**
     * 申请退款
     * @param outTradeNo 商户订单号
     * @throws Exception
     */
    @GetMapping("/weChatPayRefund")
    public void weChatPayRefund(String outTradeNo) throws Exception {

        Map<String,String> requestData = new HashMap<String, String>();
        //商户订单号  同样也可以使用微信订单号
        requestData.put("out_trade_no",outTradeNo);
        // 退款商户号
        requestData.put("out_refund_no", OrderNoUtils.getRefundNo());
        // 订单总金额
        requestData.put("total_fee","1");
        // 退款金额
        requestData.put("refund_fee","1");
        // 退款通知回调地址
        requestData.put("notify_url",myWXPayConfig.getNotifyDomain().concat("/wxPay/refunds/code"));
        log.info("退款参数--->{}",requestData);
        Map<String, String> refund = myWXPayConfig.getWxPay().refund(requestData);
        System.out.println(refund);
    }

查询退款

/**
     * 查询退款
     * @param outRefundNo 商户订单号
     */
    @GetMapping("/weChatPayRefundQuery")
    public void weChatPayRefundQuery(String outRefundNo){
    
        Map<String,String> requestData = new HashMap<String, String>();
        // 商户退款号  可以使用其他三个(商户订单号,微信订单号,微信退款订单号)
        requestData.put("out_refund_no",outRefundNo);
        try {
            Map<String, String> map = myWXPayConfig.getWxPay().refundQuery(requestData);
            System.out.println(map);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

退款回调通知

/**
     * 退款回调通知
     * @param request
     * @param response
     */
    @PostMapping("/refunds/code")
    public void  refundNotify(HttpServletRequest request, HttpServletResponse response){
        log.info("进入退款回调通知");
        String resXml = "";
        InputStream inStream;
        try {
            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);
            }
            // 获取微信调用我们notify_url的返回信息
            String result = new String(outSteam.toByteArray(), "utf-8");
            // 关闭流
            outSteam.close();
            inStream.close();
            // xml转换为map
            Map<String, String> map = WXPayUtil.xmlToMap(result);
            // 加密信息:加密信息请用商户秘钥进行解密,详见解密方式
            String req_info = map.get("req_info");
            /**
             * 解密方式
             * 解密步骤如下:
             * (1)对加密串A做base64解码,得到加密串B
             * (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
             * (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
             */
            String resultStr = AESUtil.decryptData(req_info);

            // WXPayUtil.getLogger().info("refund:解密后的字符串:" + resultStr);
            Map<String, String> aesMap = WXPayUtil.xmlToMap(resultStr);
            
            log.info("结果---->{}",aesMap);
            /** 以下为返回的加密字段: */
            //  商户退款单号  是   String(64)  1.21775E+27 商户退款单号
            String out_refund_no = aesMap.get("out_refund_no");
            //  退款状态    是   String(16)  SUCCESS SUCCESS-退款成功、CHANGE-退款异常、REFUNDCLOSE—退款关闭
            String refund_status = aesMap.get("refund_status");
            //  商户订单号   是   String(32)  1.21775E+27 商户系统内部的订单号
            String out_trade_no = aesMap.get("out_trade_no");

             // 退款是否成功
            if (!WXPayConstants.SUCCESS.equals(refund_status)) {
                    log.info("退款状态---{}",refund_status);
            } else {
                // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
                log.info("退款状态---{}",refund_status);
            }
        } catch (Exception e) {
            log.error("refund:微信退款回调发布异常:", e);
        } finally {
            try {
                // 处理业务完毕
                BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                out.write(resXml.getBytes());
                out.flush();
                out.close();
            } catch (IOException e) {
                log.error("refund:微信退款回调发布异常:out:", e);
            }
        }
    }

到这里我们从支付到退款一条龙服务就做完了

结尾

这里我们唯一要注意的是我们有些码友不知道的一点, 就是在证书这里
在这里插入图片描述如图中一样,证书就是我左下角圈出来的文件,它一定与src目录同级,这边格式也一定是.p12格式,作者本人在这踩了一个大坑,所以在此告诉各位码友防止大家又踩同样的坑。

如果大家觉得有所帮助就点赞收藏吧,防止找不到,后续将持续更新其他技术点

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Springboot整合微信支付 --- 付款码支付 的相关文章

  • 无法在 Android 10 中创建目录

    我无法在 android 10 中创建目录 它可以在 android Oreo 之前的设备上运行 我尝试了两种创建文件夹的方法 Using File mkdir File f new File Environment getExternal
  • 将 jar 作为 Linux 服务运行 - init.d 脚本在启动应用程序时卡住

    我目前正在致力于在 Linux VM 上实现一个可运行的 jar 作为后台服务 我已经使用了找到的例子here https gist github com shirish4you 5089019作为工作的基础 并将 start 方法修改为
  • 无法解析类型为 xxx 的任何 bean;限定符:[@javax.enterprise.inject.Any()]

    我有一个 LoginProvider 接口 public interface LoginProvider boolean login String username String password 以及两种不同的实现 public clas
  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • 禁用 Eclipse Java 调试器的热代码替换 [重复]

    这个问题在这里已经有答案了 可能的重复 如何在 Eclipse 中禁用热代码替换 https stackoverflow com questions 2594408 how do i disable hot code replace in
  • 如何使用 SimpleDateFormat 解析多种格式的日期

    我正在尝试解析文档中的一些日期 用户似乎以类似但不完全相同的格式输入了这些日期 以下是格式 9 09 9 2009 09 2009 9 1 2009 9 1 2009 尝试解析所有这些内容的最佳方法是什么 这些似乎是最常见的 但我想让我困扰
  • 无法使用maven编译java项目

    我正在尝试在 java 16 0 1 上使用 maven 构建 IntelliJ 项目 但它无法编译我的项目 尽管 IntelliJ 能够成功完成 在此之前 我使用maven编译了一个java 15项目 但我决定将所有内容更新到16 0 1
  • eclipse中导入项目文件夹图标

    我在 Eclipse 工作区中新导入的 Maven 项目有J and M项目文件夹顶部的图标 项目和包资源管理器 而其他导入的 Maven 项目只有一个J icon 有人可以解释其中的区别吗 该项目有J装饰器被称为 Java 项目和具有M装
  • 如何在 JSP 中导入类?

    我是一个完全的JSP初学者 我正在尝试使用java util List在 JSP 页面中 我需要做什么才能使用除以下类之外的类java lang 使用以下导入语句进行导入java util List 顺便说一句 要导入多个类 请使用以下格式
  • Condition 接口中的 signalAll 与对象中的 notificationAll

    1 昨天我才问过这个问题条件与等待通知机制 https stackoverflow com questions 10395571 condition vs wait notify mechanism 2 我想编辑相同的内容并在我的问题中添加
  • 在 HTTP 标头中发送 UTF-8 值会导致 Mojibake

    我想使用 servlet 发送阿拉伯语数据HTTPServletResponse给客户 我正在尝试这个 response setCharacterEncoding UTF 8 response setHeader Info arabicWo
  • 具有共享依赖项的多模块项目的 Gradle 配置

    使用 gradle 制作第一个项目 所以我研究了 spring gradle hibernate 项目如何组织 gradle 文件 并开始制作自己的项目 但是 找不到错误 为什么我的配置不起作用 子项目无法解决依赖关系 所以项目树 Root
  • Java:正则表达式排除空值

    在问题中here https stackoverflow com questions 51359056 java regexp for a separated group of digits 我得到了正则表达式来匹配 1 到 99 之间的一
  • Cloudfoundry:如何组合两个运行时

    cloundfoundry 有没有办法结合两个运行时环境 我正在将 NodeJS 应用程序部署到 IBM Bluemix 现在 我还希望能够执行独立的 jar 文件 但应用程序失败 APP 0 bin sh 1 java not found
  • 如何记录来自 Akka (Java) 的所有传入消息

    在 Scala 中 您可以使用 LoggingReceive 包装接收函数 如何通过 Java API 实现相同的目标 def receive LoggingReceive case x do something Scala API 有Lo
  • 如何在 Eclipse Java 动态 Web 项目中使用 .properties 文件?

    我正在 Eclipse 中开发动态 Web 项目 我创建了一个 properties 文件来存储数据库详细信息 用户名 密码等 我通过右键单击项目和 New gt File 添加它 我使用了Java util包Properties类 但它不
  • 解决错误javax.mail.AuthenticationFailedException

    我不熟悉java中发送邮件的这个功能 我在发送电子邮件重置密码时遇到错误 希望你能给我一个解决方案 下面是我的代码 public synchronized static boolean sendMailAdvance String emai
  • 如何在Java中正确删除数组[重复]

    这个问题在这里已经有答案了 我刚接触 Java 4 天 从我搜索过的教程来看 讲师们花费了大量精力来解释如何分配二维数组 例如 如下所示 Foo fooArray new Foo 2 3 但我还没有找到任何解释如何删除它们的信息 从内存的情
  • 哪个集合更适合存储多维数组中的数据?

    我有一个multi dimensional array of string 我愿意将其转换为某种集合类型 以便我可以根据自己的意愿添加 删除和插入元素 在数组中 我无法删除特定位置的元素 我需要这样的集合 我可以在其中删除特定位置的数据 也
  • JSON 到 hashmap (杰克逊)

    我想将 JSON 转换为 HashMapJackson http jackson codehaus org 这是我的 JSON String json Opleidingen name Bijz trajecten zorg en welz

随机推荐