【Springboot开发之】微信小程序支付

最近做了微信小程序支付,进行记录一下。
官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

第一步:微信需要的依赖

  <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>

第二步:配置文件的配置
微信支付需要的配置文件有:
1.微信的appId;
2.商户号mch_id;
3.交易类型trade_type:小程序的默认为JSAPI
4.统一下单地址:UNIFIED_ORDER_URL;固定值为:https://api.mch.weixin.qq.com/pay/unifiedorder

  public static final String WECHAT_APPID = "";

    public static final String WECHAT_MACH_ID = "";

    public static final String WECHAT_key = "";

    public static final String appsecret = "";

    public static final String tradeType = "JSAPI";

    public static final String NOTIFYURL = "";

    public static final String SHOPNOTIFYURL = "";

    public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

在这里插入图片描述
第三步:配置文件HttpUtils;这个可以复制

@Slf4j
public class HttpUtils {
    
    
    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
    
    
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
    
    
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key  API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
    
    
        return generateSignature(data, key, WXPayConstants.SignType.MD5);  //MD5是常量 不想写常量可以直接写成"MD5"
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
    
    
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
    
    
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
    
       // FIELD_SIGN = sign
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (WXPayConstants.SignType.MD5.equals(signType)) {
    
    
            return MD5(sb.toString()).toUpperCase();
        } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
    
      //HMACSHA256常量 可以直接写成 "HMACSHA256"
            return HMACSHA256(sb.toString(), key);
        } else {
    
    
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
    
    
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
    
    
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }


    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
    
    
     /*   org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
        org.w3c.dom.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();
            org.w3c.dom.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;
    }

    /**
     * 发送https post请求。
     *
     * @param requestUrl 请求的url(https://xxx.com)
     * @param postData   post传输的数据。
     * @return 请求成功返回请求内容,请求失败直接返回null。
     */
    public static String httpsPost(String requestUrl, String postData) {
    
    
        return httpsRequest2(requestUrl, "POST", postData);
    }


    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     *
     * @param strxml
     * @return
     * @throws
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws Exception {
    
    
        if (null == strxml || "".equals(strxml)) {
    
    
            return null;
        }

        Map m = new HashMap();
        InputStream in = String2Inputstream(strxml);
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
    
    
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
    
    
                v = e.getTextNormalize();
            } else {
    
    
                v = getChildrenText(children);
            }

            m.put(k, v);
        }

        //关闭流
        in.close();

        return m;
    }

    public static InputStream String2Inputstream(String str) {
    
    
        return new ByteArrayInputStream(str.getBytes());
    }

    /**
     * 获取子结点的xml
     *
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
    
    
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
    
    
            Iterator it = children.iterator();
            while (it.hasNext()) {
    
    
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
    
    
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }


    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
    
    
        // 创建SSLContext
        StringBuffer buffer = null;
        try {
    
    
            URL url = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(requestMethod);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.connect();
            //往服务器端写内容
            if (null != outputStr) {
    
    
                OutputStream os = conn.getOutputStream();
                os.write(outputStr.getBytes("utf-8"));
                os.close();
            }
            // 读取服务器端返回的内容
            InputStream is = conn.getInputStream();
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            BufferedReader br = new BufferedReader(isr);
            buffer = new StringBuffer();
            String line = null;
            while ((line = br.readLine()) != null) {
    
    
                buffer.append(line);
            }
            br.close();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return buffer.toString();
    }


    /**
     * 发送https请求,请求成功返回请求内容,请求失败直接返回空串。
     *
     * @param requestUrl 请求的url(https://xxx.com)
     * @param method     请求方法,一般有GET和POST方法。
     * @param outputStr  请求时附带的数据。
     * @return 请求成功返回请求内容,请求失败直接返回null。
     */
    public static String httpsRequest2(String requestUrl, String method, String outputStr) {
    
    
        HttpsURLConnection conn = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
    
    
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = null;
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);

            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(method);

            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
    
    
                conn.setDoOutput(true);
                outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }

            // 从输入流读取返回内容
            inputStream = conn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
            bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
    
    
                buffer.append(str);
            }

            return buffer.toString();
        } catch (ConnectException ce) {
    
    
            log.error("连接超时:{}", ce);
        } catch (Exception e) {
    
    
            log.error("https请求异常:{}", e);
        } finally {
    
    
            try {
    
    
                if (bufferedReader != null) {
    
    
                    bufferedReader.close();
                }
                if (inputStreamReader != null) {
    
    
                    inputStreamReader.close();
                }
                if (inputStream != null) {
    
    
                    inputStream.close();
                }
                if (conn != null) {
    
    
                    conn.disconnect();
                }
            } catch (IOException e) {
    
    
                log.warn(e.getLocalizedMessage(), e);
            }
        }

        return null;
    }

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


}

在这里插入图片描述
第四步:小程序请求的固定模板

@Slf4j
@Component
public class WeChatPayUtil {
    
    


    public Map<String, String> getPrePayInfo(WechatMiniDTO miniDTO, String openId) throws Exception {
    
    
        Map<String, String> map = Maps.newHashMap();
        map.put("appid", WeChatConfig.WECHAT_APPID);
        map.put("mch_id", WeChatConfig.WECHAT_MACH_ID);
        map.put("nonce_str", WXPayUtil.generateNonceStr());
        map.put("body", miniDTO.getBody());
        map.put("out_trade_no", miniDTO.getOutTradeNo());
        map.put("total_fee", miniDTO.getTotalFee());
        map.put("spbill_create_ip", getLocalIp());
        map.put("trade_type", WeChatConfig.tradeType);
        map.put("notify_url", miniDTO.getNotifyURL());
        map.put("openid", openId);
        String unifiedorderUrl = WeChatConfig.UNIFIED_ORDER_URL; // 微信统一下单URL
        String sign = generateSignature(map, WeChatConfig.WECHAT_key);// 生成签名 PAY_API_SECRET=微信支付相关API调用时使用的秘钥
        map.put("sign", sign);  // 参数配置 我直接写成"sign"
        String xml = mapToXml(map);
        //请求微信统一下单接口
        String xmlStr = HttpUtils.httpRequest(unifiedorderUrl, "POST", xml);

        Map map1 = HttpUtils.doXMLParse(xmlStr);
        String return_code = (String) map1.get("return_code");//返回状态码
        String result_code = (String) map1.get("result_code");//返回状态码
        String err_code = (String) map1.get("err_code");//返回状态码
        String err_code_des = (String) map1.get("err_code_des");//返回状态码
        log.info(xmlStr);
        if (return_code.equals("SUCCESS") || return_code.equals(result_code)) {
    
    
            // 业务结果
            String prepay_id = (String) map1.get("prepay_id");//返回的预付单信息
            Map<String, String> payMap = new HashMap<>();
            payMap.put("appId", WeChatConfig.WECHAT_APPID);  // 参数配置
            payMap.put("timeStamp", getCurrentTimestamp() + "");  //时间
            payMap.put("nonceStr", generateNonceStr());  // 获取随机字符串
            payMap.put("signType", "MD5");
            payMap.put("package", "prepay_id=" + prepay_id);
            String paySign = generateSignature(payMap, WeChatConfig.WECHAT_key); //第二次生成签名
            payMap.put("paySign", paySign);
            payMap.put("prepayId", prepay_id);
            return payMap;   //返回给前端,让前端去调支付 ,完成后你去调支付查询接口,看支付结果,处理业务。
        } else {
    
    
            //打印失败日志
        }
        return null;

    }

    /**
     * 获取当前机器的ip
     *
     * @return String
     */
    public static String getLocalIp() {
    
    
        InetAddress ia = null;
        String localip = null;
        try {
    
    
            ia = ia.getLocalHost();
            localip = ia.getHostAddress();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return localip;

    }


}

在这里插入图片描述
第五步,实体所需要传入的参数


    @NotBlank(message = "支付类型不能为空")
    @ApiModelProperty(value = "支付类型,小程序支付传入11", required = true)
    private String payType;

    @ApiModelProperty(value = "商品描述")
    private String body;

    @ApiModelProperty(value = "订单号", required = true)
    @NotNull(message = "缺少请求参数")
    private String outTradeNo;

    @ApiModelProperty(value = "金额")
    private String totalFee;

    @ApiModelProperty(value = "终端IP")
    private String spbillCreateIp;

在这里插入图片描述

第六步:请求支付:

  public ResponseClass<Map<String, String>> getPrePayInfo(@RequestBody WeChatDto param) {
    
    
        String userId = SecurityUtils.getUser().getUserId();
        User user = userService.getUserByUserId(userId);
        if (Objects.isNull(user)) {
    
    
            throw new YamiShopBindException("用户未找到!");
        }
        String payType = param.getPayType();
        if ("11".equals(payType)) {
    
    
            Map<String, String> resultMap = null;
            String openId = user.getOpenId();
            WechatMiniDTO miniDTO = new WechatMiniDTO();
            miniDTO = xyPayService.changeParam(param);
            miniDTO.setOutTradeNo(param.getOutTradeNo());
            miniDTO.setNotifyURL(WeChatConfig.NOTIFYURL);
            try {
    
    
                resultMap = weChatPayUtil.getPrePayInfo(miniDTO, openId);
                log.info(resultMap.toString());
            } catch (Exception e) {
    
    
                log.error("生成微信预支付订单失败", e);
                throw new YamiShopBindException("微信支付失败");
            }
            // 处理公司业务
            xyPayService.xyBusiness(userId, miniDTO.getTotalFee(), miniDTO.getOutTradeNo());
            return ResponseClass.ok(resultMap);

        }

        return null;
    }

在这里插入图片描述
通过Postman或者其他接口测试工具测试即可。返回如下结构的即可
在这里插入图片描述
到此处,微信小程序的请求支付流程基本完成,交给前端解析并付款即可。

第七步,支付回调
将项目发到服务器或者内网穿透后,即可调用我们的支付回调接口

 @PostMapping("/notify")
    public String wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
        Map orderMap = new HashMap();
        BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
    
    
            sb.append(line);
        }
        String notityXml = sb.toString();
        String resXml = "";
        Map resPrint = new HashMap();
        Map<String, String> resultMap = WXPayUtil.xmlToMap(notityXml);
        String returnCode = (String) resultMap.get("return_code");//业务结果
        String orderNo = resultMap.get("out_trade_no");//订单号
        String sign = resultMap.get("sign");//获取微信签名
        resultMap.remove("sign");//去除签名字段
        String signNew = WXPayUtil.generateSignature(resultMap, WeChatConfig.WECHAT_key); //重新签名
        if (signNew.equals(sign)) {
    
    
            if ("SUCCESS".equals(returnCode)) {
    
    
                System.out.println(signNew + "ppppp");
                resPrint.put("return_code", "SUCCESS");
                resPrint.put("return_msg", "ok");
                resXml = WXPayUtil.mapToXml(resPrint);
                orderMap.put("orderStatus", 1);
                orderMap.put("orderNo", orderNo);
                String check = xyPayService.checkParam(orderNo);
                if ("SUCCESS".equals(check)) {
    
    
                    // 处理自己的业务逻辑
                    xyPayService.xyPaySusess(orderNo);
                }
                return WxPayNotifyResponse.success("成功");
            } else {
    
    
                System.out.println("业务结果失败");
                return WxPayNotifyResponse.success("code:" + 9999 + "微信回调结果异常,异常原因:");
            }

        } else {
    
    
            resPrint.put("return_code", "FAIL");
            resPrint.put("return_msg", "签名失败");
            resXml = WXPayUtil.mapToXml(resPrint);
        }
        log.info(resXml);
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
        br.close();
        return null;
    }

在这里插入图片描述
回调成功后,更换成处理自己的业务逻辑即可。
至此,微信小程序的支付以及回调过程处理完成。
代码地址为Github:
https://github.com/virtuousOne/pay

猜你喜欢

转载自blog.csdn.net/qq_35529931/article/details/105848407