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

场景介绍

在这里插入图片描述

开发指引

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

接入准备

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

在这里插入图片描述

所需依赖

 		<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格式,作者本人在这踩了一个大坑,所以在此告诉各位码友防止大家又踩同样的坑。

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

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值