SpirngBoot做H5支付 JSSDK支付 为uniapp小程序提供接口 详细内容包含支付、退款以及支付退款回调 纯微信支付V2支付 最后附上完整代码

news2024/12/23 8:54:59

可以先看一下下面的IndexController 先理解一下需要用到哪些参数然后再从头开始准备就不会一头蒙了

完整代码:
链接: https://pan.quark.cn/s/518e02b22e4f
提取码:J6f2

可能遇到的坑:

微信支付这个的文档可以说跟没有是一样的 只写入参回参 加密解密搞得人一头雾水 网上找的大多数的都是用的别人封装好的接口 我是觉得一个是怕不安全 另一个是直接调别人写好的出了啥bug也不好改
最大的坑其实就是签名失败我的一次报签名失败的时候搞了三天 网上翻了个遍 代码试了个遍 最后发现是密钥用的不对 我的代码需要用的是V2的密钥 因为在我来公司之前公司做过微信支付的项目 当时主要怕重置了密钥会影响其他项目 所以就没有去重置 直接在之前的项目里面找了一下密钥拿上就去用了,结果就是 一个BUG卡了两三天
好 BUG把我心态搞崩了之后我抱着试试的心理我说我改一下密钥看看吧 结果改完密钥之后依然报错,报签名错误。好好好 又去网上翻代码 翻翻翻 改改改 又是两三天 然后突然在一篇博客里看到博主跟我一样 报签名错误 结果是因为密钥不能纯大写 也不能纯小写 因为微信官方文档写的是 包含 大写!小写!数字! 好家伙 咱以为人家是建议呢 没想到是硬性规定! 看完之后我赶紧又去换密钥 换完之后果然不报签名错误了 不过也仅仅是第一次签名的时候不报错了
紧接着就来到了第三次报错签名失败 这次报签名失败不是后端报错 后端签名后请求微信 请求参数和返回参数都没问题 已经拿到了timeStamp nonceStr package signType这些需要返回给前端的参数了 到了微信开发工具里都生成出了付款二维码 竟然是在扫码之后报签名失败的错!!!! 好好好 又开始换代码 换换换 换到最后 发现我在第二次签名的时候 在map里多加了一个在第一次签名时候生成的当前订单号 去掉之后立马不报错签名失败了!!
签名失败报完之后 到了回调 回调又开始报错 支付成功之后发现微信一直在调回调 搞了半天才知道 解析之后还要返回给微信String格式的xml 不然微信就会一直调用回调方法
后端的坑走完了之后还有前端的坑 最严重的时候我自己写了个uniapp的小程序才找到问题!!!!
第一次因为前端报错是因为 他拿code来我这获取openid 这能有什么错呢 这有错! 微信规定一个code只能用一次 然后哥们每次进来的时候都报错code有问题 最后最后还了各种代码之后发现url拼的有问题!!! 一定要用这个地址!! 后面我写uniapp的时候还发现了前端直接获取openid的方法
String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + WxPayConfig.APPID + "&secret=" + WxPayConfig.APPSECRET + "&js_code=" + code + "&grant_type=authorization_code";
				uni.login({
					provider: 'weixin',
					success: res => {
						uni.request({
							url: 'https://api.weixin.qq.com/sns/jscode2session', // 请求微信服务器
							method: 'GET',
							data: {
								appid: 'XXXXXXXXX', //你的小程序的APPID
								secret: 'XXXXXXXXX', //你的小程序秘钥secret,  
								js_code: res.code, //wx.login 登录成功后的code
								grant_type: 'authorization_code'
							},
							success: (res) => {
								console.log('获取信息', res.data); // 换取成功后 暂存这些数据 留作后续操作
								res.data.openid //openid 用户唯一标识
								res.data.session_key //session_key  会话密钥
							}
						});
					}
				});
第二次因为前端报错是获取用户信息 还是给我code让我去获取用户头像和名字还有手机号和openid我又是换了各种代码 最后发现 人家微信把获取用户信息给停用了 改成了按钮获取 头像和名字 直接在前端获取的! 具体我在我的uniapp小程序里都有写
第三次因为前端报错还是获取手机号 报这个错的时候觉得我被玩了 获取手机号的时候我代码没问题 但是就是怎么都获取不到 一直报错 最后我说我TM自己写个uniapp我试试 我就不信了 结果 结果 结果我写了之后发现一点问题没有 最后发现是前端拿着获取open的code给的我让我获取手机号 但是其实两个code是不一样的 不能互用的 好好好 我给前端反馈回去这个问题 然后! 重点来了 ! 哥们跟我说,我知道呀 是的呀 是这样的呀 无语呀!!!!!
最后一次因为前端报错是个小问题 第一个做支付的项目的时候我的接口是让前端直接传金额进来的 然后 微信传金额的时候需要以分为单位 但是他给我传的不对 然后一直报错

下面就是我的uniapp的源码 配合后端项目的话多看看debug 很快就能理解代码了

链接: https://pan.quark.cn/s/49059110c037
提取码:2Sqv

前期准备

登录已认证的小程序并开通微信支付
在微信支付-产品中心-我的产品中开通JSAPI支付并同时配置JSAPI支付支付授权目录 配置成自己服务器的域名 后面部署之后会用到 本地测试的话回调需要用内网穿透 我用的是Natapp 很简单

在微信支付中下载并配置商户证书 证书在退款的时候会用到
链接: https://pay.weixin.qq.com/docs/merchant/development/development-preparation/download-configure-merchant-certificates.html
还需要设置V2密钥!注意!注意!注意!去生成一个包含大写 小写 数字的32位随机数
参照:
Kc9f924v44bXOce8Ta32U90O43d620RN
heVvwN9g5HoTBubDRoldzUgeKMmyfaRI
也可以直接拿去用 无所谓的 V2 V3最好设置成一样的 省的后面忘了
在这里插入图片描述

如果是小程序的话基本不需要做什么配置 保存AppID和AppSecret 用于获取AccessToken 部署的时候需要配置一下服务器域名

在这里插入图片描述

pom.xml中
		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.39</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.15</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
获取到所有需要的参数之后放在一个配置类里 WxPayConfig:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.song.wxpay.controller.IndexController;
import com.song.wxpay.util.ip.AddressUtils;
import com.song.wxpay.util.ip.IpUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.*;

public class WxPayConfig {
    public static final String APPID = "你的小程序APPID";
    //小程序秘钥
    public static final String APPSECRET = "你的APPSECRET";
    //商户号
    public static String weChatPayMchId = "微信支付的商户号";
    //商户秘钥 商户号里你自己设置的32位密码
    public static String weChatV2PayKey = "Kc9f924v44bXOce8Ta32U90O43d620RN";
    //商户秘钥 商户号里你自己设置的32位密码
    public static String weChatV3PayKey = "heVvwN9g5HoTBubDRoldzUgeKMmyfaRI";

    public static String weChatPayKeyPath = "apiclient_key.pem";

    public static String weChatPayP12Path = "apiclient_cert.p12";
    //获取用户手机号url
    public static String weChatGetPhoneUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
    //支付回调地址    可以使用内网穿透也可以部署到服务器后用服务器的真实域名加地址
    public static String weChatPayNotifyUrl = "http://3dm924.natappfree.cc/weChatPayNotify";
    //退款回调地址    可以使用内网穿透也可以部署到服务器后用服务器的真实域名加地址
    public static String weChatRefundNotifyUrl = "http://3dm924.natappfree.cc/weChatRefundNotify";

    public static String weChatAuthorizeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";

    public static String weChatAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";

    public static String weChatUserinfoUrl = "https://api.weixin.qq.com/sns/userinfo";

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

    public static String weChatRefundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";

    public static String weChatPayOrderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";


    /**
     * 获取微信Jsapi的accessToken
     */
    public static String getAccessToken() throws IOException {
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
        url = url.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
        String result = HttpUtils.sendGet(url);
        JSONObject jsonObject = JSON.parseObject(result);
        String accessToken = jsonObject.getString("access_token");
        return accessToken;
    }

    public static String getPhone(String code) throws IOException {
        String accessToken = getAccessToken();
        String url = weChatGetPhoneUrl + "?access_token=" + accessToken;
        // 请求参数
        Map<String, String> map = new HashMap<>(1);
        map.put("code", code);
        // 发起请求 注意请求参数map要转成json字符串
        String res = HttpUtils.sendPost(url, JSON.toJSONString(map));
        // 解析请求结果 拿到手机号
        JSONObject jsonObject = JSONObject.parseObject(res);
        String phone_info = jsonObject.get("phone_info").toString();
        JSONObject jsonPhone_info = JSONObject.parseObject(phone_info);
        String phoneNumber = jsonPhone_info.get("phoneNumber").toString();
        System.out.println("用户手机号:" + phoneNumber);
        return phoneNumber;
    }

    public static JSONObject getPhoneNumber(String code) {
        try {
            String accessToken = getAccessToken();
            String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESSTOKEN";
            url = url.replace("ACCESSTOKEN", accessToken);
            HashMap<String, Object> requestParam = new HashMap<>();
            // 手机号调用凭证
            requestParam.put("code", code);
            JSONObject jsonObject = JSONObject.parseObject(HttpUtils.sendPost(url, JSON.toJSONString(requestParam)));
            return jsonObject;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static JSONObject login(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + WxPayConfig.APPID + "&secret=" + WxPayConfig.APPSECRET + "&js_code=" + code + "&grant_type=authorization_code";
        String result = HttpUtils.sendGet(url);
        JSONObject resultObject = JSON.parseObject(result);
        String ip = IpUtils.getIpAddr();    //当前用户登录Ip
        String address = AddressUtils.getRealAddressByIP(ip);   //当前用户登录地址
        System.out.println("根据code去获取信息:" + resultObject);
        resultObject.put("Ip", ip);
        resultObject.put("address", address);
        return resultObject;
    }

    public static String doRefund(String url, String data){
        StringBuilder sb = new StringBuilder();
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            InputStream instream = IndexController.class.getClassLoader().getResourceAsStream("apiclient_cert.p12");
            String mchid = WxPayConfig.weChatPayMchId;
            try {
                keyStore.load(instream, mchid.toCharArray());
            } finally {
                instream.close();
            }
            // 证书
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchid.toCharArray()).build();
            // 只允许TLSv1协议
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    sslcontext,
                    new String[]{"TLSv1"},
                    null,
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            //创建基于证书的httpClient,后面要用到
            CloseableHttpClient client = HttpClients.custom()
                    .setSSLSocketFactory(sslsf)
                    .build();
            HttpPost httpPost = new HttpPost(url);//退款接口
            StringEntity reqEntity = new StringEntity(data);
            // 设置类型
            reqEntity.setContentType("application/x-www-form-urlencoded");
            httpPost.setEntity(reqEntity);
            CloseableHttpResponse response = client.execute(httpPost);
            try {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
                    String text = "";
                    while ((text = bufferedReader.readLine()) != null) {
                        sb.append(text);
                    }
                }
                EntityUtils.consume(entity);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    /**
     * 随机字符串
     * @return
     */
    public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    public static String generateSignature(final Map<String, String> data, String key, String 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("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 ("MD5".equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }else if ("HMACSHA256".equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }
    public static String MD5(String data) throws Exception {
        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(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();
    }
    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();
    }
    public static String mapToXml(Map<String, String> map) throws Exception {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set<String> set = map.keySet();
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            String key = it.next();
            sb.append("<" + key + ">").append(map.get(key)).append("</" + key + ">");
        }
        sb.append("</xml>");
        return sb.toString();

    }
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = 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) {
            getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }
    }
    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 Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }
    public static String generateOrderNumber() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");   // 生成日期部分
        String datePart = dateFormat.format(new Date());
        Random random = new Random();   // 生成随机数部分
        int randomPart = random.nextInt(999999);
        String orderNumber = datePart + String.format("%06d", randomPart);  // 拼接订单号
        return orderNumber;
    }
    public static byte[] readInput(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int len = 0;
        byte[] buffer = new byte[1024];
        while ((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
        }
        out.close();
        in.close();
        return out.toByteArray();
    }
    private static final String ALGORITHM = "AES";
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
    /**
     * AES解密
     *
     * @param base64Data 64
     * @return str
     * @throws Exception e
     */
    public static String decryptData(String base64Data,String mchKey) throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        SecretKeySpec key = new SecretKeySpec(WxPayConfig.MD5(mchKey).toLowerCase().getBytes(), ALGORITHM);
        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;
    }

}

HttpUtils StringUtils.EMPTY 替换成 “” 空 Constants.UTF8替换成UTF-8 或者在完整项目代码中拿到这两个类
import com.song.wxpay.util.Result.StringUtils;
import com.song.wxpay.util.ip.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;

/**
 * 通用http发送方法
 * 
 * @author ruoyi
 */
public class HttpUtils
{
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url)
    {
        return sendGet(url, StringUtils.EMPTY);
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param)
    {
        return sendGet(url, param, Constants.UTF8);
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @param contentType 编码类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param, String contentType)
    {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try
        {
            String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
            log.info("sendGet - {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("recv - {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (in != null)
                {
                    in.close();
                }
            }
            catch (Exception ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param)
    {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try
        {
            log.info("sendPost - {}", url);
            URL realUrl = new URL(url);
            URLConnection conn = realUrl.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("recv - {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (out != null)
                {
                    out.close();
                }
                if (in != null)
                {
                    in.close();
                }
            }
            catch (IOException ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    public static String sendSSLPost(String url, String param)
    {
        StringBuilder result = new StringBuilder();
        String urlNameString = url + "?" + param;
        try
        {
            log.info("sendSSLPost - {}", urlNameString);
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
            URL console = new URL(urlNameString);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);

            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.connect();
            InputStream is = conn.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String ret = "";
            while ((ret = br.readLine()) != null)
            {
                if (ret != null && !"".equals(ret.trim()))
                {
                    result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
                }
            }
            log.info("recv - {}", result);
            conn.disconnect();
            br.close();
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
        }
        return result.toString();
    }

    private static class TrustAnyTrustManager implements X509TrustManager
    {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
        {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
        {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[] {};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier
    {
        @Override
        public boolean verify(String hostname, SSLSession session)
        {
            return true;
        }
    }
}
接下来就是接口层IndexController
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.song.wxpay.util.HttpUtils;
import com.song.wxpay.util.WxPayConfig;
import com.song.wxpay.util.ip.Constants;
import com.song.wxpay.util.ip.IpUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController("/")
public class IndexController {

    @RequestMapping("/index")
    public String index() {
        return "Wellcome";
    }

    //根据Code获取用户手机号!注意!登录的Code和获取手机号的Code是不同的 uniapp获取手机号需要button设置 @getphonenumber="getUserPhone" open-type="getPhoneNumber"
    @PostMapping("/getPhone")
    public JSONObject getPhone(String code) {
        return WxPayConfig.getPhoneNumber(code);
    }

    //根据Code进入login接口获取当前用户openID   顺带获取了一下当前请求来源用户的登录Ip以及登录地址
    @PostMapping("/login")
    public JSONObject login(String code) {
        return WxPayConfig.login(code);
    }

    @PostMapping("/v2pay")
    public Map v2pay() throws Exception {
        Map<String, String> map = new HashMap<>();
        map.put("appid", WxPayConfig.APPID);                        //公众账号ID
        map.put("mch_id", WxPayConfig.weChatPayMchId);              //商户号
        map.put("nonce_str", WxPayConfig.generateNonceStr());       //随机字符串
        map.put("body", "商品描述");                                  //商品描述
        map.put("out_trade_no", "88888888202311061200");            //商户订单号
        map.put("total_fee", "1");                                  //标价金额 转换成分 微信发起付款请求要求是字符串
        map.put("trade_type", "JSAPI");                             //交易类型
        map.put("spbill_create_ip", IpUtils.getIpAddr());           //终端IP
        map.put("notify_url", WxPayConfig.weChatPayNotifyUrl);      //通知地址
        map.put("openid", "oIdOA4otaUBfXVxWlB2QtpL2oFEc");          //openid
        map.put("sign_type", "MD5");                                //签名类型
        map.put("sign", WxPayConfig.generateSignature(map, WxPayConfig.weChatV2PayKey, "MD5"));
        String resultXml = HttpUtils.sendPost(WxPayConfig.weChatPayUrl, WxPayConfig.mapToXml(map));
        System.out.println("请求参数===========");
        System.out.println("商品详情:" + map.get("body") + " 商户订单号:" + map.get("out_trade_no") + " OPENID:" + map.get("openid") + " JSON:" + JSON.toJSONString(map));
        Map<String, String> wxResultMap = WxPayConfig.xmlToMap(resultXml);
        System.out.println("返回参数===========");
        System.out.println(JSON.toJSONString(wxResultMap));
        Map<String, String> resultMap = new HashMap<>();
        if (wxResultMap.get("return_code").equals("SUCCESS") && wxResultMap.get("result_code").equals("SUCCESS")) {
            resultMap.put("appId", WxPayConfig.APPID);
            resultMap.put("timeStamp", System.currentTimeMillis() / 1000 + "");
            resultMap.put("nonceStr", WxPayConfig.generateNonceStr());
            resultMap.put("package", "prepay_id=" + wxResultMap.get("prepay_id"));
            resultMap.put("signType", "MD5");
            //generateSignature在这里会去生成签名 所以resultMap中不能再去多加参数 否则会报生成签名的错误!!!!!
            resultMap.put("paySign", WxPayConfig.generateSignature(resultMap, WxPayConfig.weChatV2PayKey, "MD5"));
        } else {
            resultMap.put("code", "500");
        }
        return resultMap;
    }

    @RequestMapping("/weChatPayNotify")
    public String newweChatPayNotify(HttpServletRequest request) throws Exception {
        System.out.println("支付回调");
        String notifyXml = new String(WxPayConfig.readInput(request.getInputStream()), "utf-8");
        Map<String, String> notifyMap = WxPayConfig.xmlToMap(notifyXml);
        if ("SUCCESS".equals(notifyMap.get("result_code"))) {     //return直接返回notifyXml 当result_code返回的结果是SUCCESS时 则不进行调用了 如果不返回固定格式微信就会反复调用
            System.out.println("回调成功");    //支付成功时候,处理业务逻辑
            System.out.println("微信交易订单号:" + notifyMap.get("transaction_id") + " 商户订单号:" + notifyMap.get("out_trade_no") + " OPENID:" + notifyMap.get("openid") + " JSON:" + JSON.toJSONString(notifyMap));
            return notifyXml;
        }
        return notifyXml;
    }

    @RequestMapping("/weChatRefundNotify")
    public String weChatRefundNotify(HttpServletRequest request) throws Exception {
        System.out.println("退款回调");
        Map<String, String> returnMap = new HashMap<>();
        String xml = new String(WxPayConfig.readInput(request.getInputStream()), "utf-8");
        Map<String, String> notifyMap = WxPayConfig.xmlToMap(xml);
        // 验签返回的数据
        if (notifyMap.get("return_code").equals("SUCCESS")) {
            // 此时在解密 req_info 字段信息
            Map<String, String> reqInfo = WxPayConfig.xmlToMap(WxPayConfig.decryptData(notifyMap.get("req_info"), WxPayConfig.weChatV2PayKey));
            if (!reqInfo.get("refund_status").equals(Constants.SUCCESS)) {
                System.out.println("回调成功");
                returnMap.put("return_code", "SUCCESS");
                returnMap.put("return_msg", "OK");
                System.out.println(reqInfo);    //transaction_id:交易成功后的微信交易单号   success_time:成功时间   refund_recv_accout:退款到哪个位置  out_refund_no、out_trade_no
                return WxPayConfig.mapToXml(returnMap);
            }
        }
        returnMap.put("return_code", "FAIL");
        returnMap.put("return_msg", "");
        String returnXml = WxPayConfig.mapToXml(returnMap);
        return returnXml;
    }

    @PostMapping("/v2refund")
    public Map v2refund() throws Exception {
        Map<String, String> reqData = new HashMap<>();
        reqData.put("appid", WxPayConfig.APPID);
        reqData.put("mch_id", WxPayConfig.weChatPayMchId);
        reqData.put("nonce_str", WxPayConfig.generateNonceStr());//随机字符串
        reqData.put("sign_type", "MD5");
        reqData.put("transaction_id", "4200001972202311061014940605");//支付成功后支付回调返回的transaction_id
        reqData.put("out_refund_no", WxPayConfig.generateOrderNumber());
        reqData.put("total_fee", "1");//订单总金额,单位为分
        reqData.put("refund_fee", "1");//退款总金额,单位为分
        reqData.put("notify_url", WxPayConfig.weChatRefundNotifyUrl);
        reqData.put("sign", WxPayConfig.generateSignature(reqData, WxPayConfig.weChatV2PayKey, "MD5"));
        String xmlParam = WxPayConfig.mapToXml(reqData);
        System.out.println("退款请求参数===========");
        System.out.println(xmlParam);
        String xmlStr = WxPayConfig.doRefund("https://api.mch.weixin.qq.com/secapi/pay/refund", xmlParam);
        Map<String, String> map = WxPayConfig.xmlToMap(xmlStr);
        if (map == null || !"SUCCESS".equals(map.get("return_code"))) {
            System.out.println("-----退款发起失败-----" + JSON.toJSONString(map));
            return null;
        } else {//返回退款微信订单号
            System.out.println("退款返回参数===========");
            System.out.println(JSON.toJSONString(map));
            return map;
        }
    }


}

如果是公众号的话需要做一下配置 并保存AppID和AppSecret 用于获取AccessToken

在这里插入图片描述 请添加图片描述
配置IP白名单(配置本地的ip地址)
服务器配置 :(其实就是需要微信服务器调用自己项目中的接口去验证)

//微信回调的校验接口
    @RequestMapping(value = "/check", produces = "text/plain;charset=UTF-8", method = {RequestMethod.GET, RequestMethod.POST})
    //微信服务器根据小程序配置的token,结合时间戳timestamp和随机数nonce通过SHA1生成签名,发起get请求,检验token的正确性,
    //检验正确原样返回随机字符串echostr,失败返回空字符串
    public String check(HttpServletRequest request, HttpServletResponse response,
                        @RequestParam("signature") String signature,
                        @RequestParam("timestamp") String timestamp,
                        @RequestParam("nonce") String nonce,
                        String echostr) throws Exception {
        //若是为get请求,则为开发者模式验证
        if ("get".equals(request.getMethod().toLowerCase())) {
            return wechatService.checkSignature(signature, timestamp, nonce, echostr);
        }else {
            return null;
        }
    }

	public String checkSignature(String signature, String timestamp, String nonce, String echostr) {
        try {
            //这里的“token”是正确的token,由服务器定义,小程序只有使用正确的token,微信服务器才会验证通过
            String checkSignature = SHA1.creatSHA1("token", timestamp, nonce);
            if (checkSignature.equals(signature)) {
                return echostr;
            }
        } catch (AesException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static String creatSHA1(String token, String timestamp, String nonce) throws AesException
    {
        try {
            String[] array = new String[] { token, timestamp, nonce};
            StringBuffer sb = new StringBuffer();
            // 字符串排序
            Arrays.sort(array);
            for (int i = 0; i < 3; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexstr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.ComputeSignatureError);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1180332.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【HMS Core】推送热门合集4

【问题描述1】 如果云端通知的category&#xff0c;本地通知不支持&#xff0c;如何处理&#xff1f; 【解决方案】 如果云端通知的category&#xff0c;本地通知不支持&#xff0c;或者云端通知的category的取值在分类标准中是不涉及&#xff0c;那么说明该类型不支持本地通…

工业物联网网关解决方案openwrt二次开发无线路由WiFi模块选型

在现今高科技快速发展的时代&#xff0c;无线通信技术已经成为人们日常生活中不可或缺的一部分。而在众多的无线技术产品中&#xff0c;基于IEEE 802.11系列协议的WiFi技术无疑是其中的主流。随着WiFi技术的广泛应用&#xff0c;市面上涌现出了各种各样的主控平台和WiFi模块。今…

魔众文库系统 v5.5.0 批量快捷上传,文档图标优化,档转换逻辑优化

魔众文库系统基于文档系统知识&#xff0c;建立平台与领域&#xff0c;打造流量、用户、付费和变现的闭环&#xff0c;帮助您更好的搭建文库系统。 魔众文库系统发布v5.5.0版本&#xff0c;新功能和Bug修复累计14项&#xff0c;批量快捷上传&#xff0c;文档图标优化&#xff…

JQ完成模拟QQ好友分组案例(介绍JQ实现原理)

当我们写这个案例之前&#xff0c;需要引入好JQ文件&#xff0c;以防没有效果 这个案例的需求请看以下效果图 不能重复点击&#xff0c;只有删除掉之后才可以继续点击 效果图&#xff1a; 代码介绍&#xff1a; <!DOCTYPE html> <html lang"en"><h…

【每日一题】2586. 统计范围内的元音字符串数-2023.11.7

题目&#xff1a; 2586. 统计范围内的元音字符串数 给你一个下标从 0 开始的字符串数组 words 和两个整数&#xff1a;left 和 right 。 如果字符串以元音字母开头并以元音字母结尾&#xff0c;那么该字符串就是一个 元音字符串 &#xff0c;其中元音字母是 a、e、i、o、u 。…

Python采集数据代码示例

基本的爬虫程序的示例&#xff1a; typescript import * as request from request; // 信息 const proxyHost ; const proxyPort ; // 网站的 URL const url ; // 使用 request 库发起请求 request({ url, method: GET, proxy: { host: proxyHost…

vue递归获取树形菜单

文章目录 前言什么是递归&#xff1f; 一、数据集二、 递归函数三、打印树形结构展示 前言 什么是递归&#xff1f; 程序调用自身的编程技巧称为递归&#xff08; recursion&#xff09;。 递归 粗略的理解为 循环 &#xff0c;只不过 递归 是调用自身。 在实际使用中&#xf…

libwebsockets入门

WebSocket是一种在单个TCP连接上进行全双工通讯的协议&#xff0c;用于在Web客户端和服务器之间建立持久连接&#xff0c;进行实时通信。它是HTML5开始提供的一种通讯方式&#xff0c;通过使用WebSocket连接&#xff0c;web应用程序可以执行实时的交互&#xff0c;而不是以前的…

将json数据转换为Python字典

import requests from bs4 import BeautifulSoup import json# 定义代理信息 proxy_host www.duoip.cn proxy_port 8000# 定义要爬取的url url http://localhost:9200/_search# 创建一个requests.Session对象&#xff0c;并设置代理 session requests.Session() session.pr…

Python3,3行代码,给照片填充背景色,从此跟照相馆说拜拜。

3行代码照片上背景色 1、引言2、代码实战2.1 思路2.2 安装2.3 实例 3、总结 1、引言 小屌丝&#xff1a; 鱼哥&#xff0c;帮个忙。 小鱼&#xff1a; 在开车。 小屌丝&#xff1a;… 那你先忙&#xff0c;不打扰你了。 小鱼&#xff1a;…我可以说话的&#xff0c;没事。 小屌…

3.4、Linux小程序:进度条

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 回车与换行的概念和区别 行缓冲区概念 进度条代码 version1 version2 version3 回车与换行的概念和区别 换行\n&#xff0c;回车\r 似乎无需多言 行缓冲区概念 这里我们通过例子来简单理解即可&#xff0c;深入…

Wait-Notify机制

文章目录 1. 简介2. 相关API3. wait notify的正确姿势4. 总结 1. 简介 回顾Minitor锁的结构&#xff1a; Owner线程发现条件不满足&#xff0c;调用wait方法&#xff0c;即可进入WaitSet变为WAITING状态BLOCKED和WAITING的线程都处于阻塞状态&#xff0c;不占用CPU时间BLOCK…

新浪微博一键删除所有内容

亲自测试用 具体操作如下&#xff1a; 对应的 1 2 如下&#xff0c;进入这个界面是按F12 就可以看到 最后画横线的位置 替换自己的id 对应的就是 3 具体代码如下 //向删除接口发起请求&#xff0c;删除对应节点 function del_weibo(id) {var myHeaders new Headers();myHea…

小白学爬虫:通过商品ID获取1688跨境属性数据接口|1688商品属性接口|1688一件代发数据接口|1688商品详情接口

通过商品ID获取1688跨境属性数据接口可以使用1688开放平台提供的API接口实现。以下是获取跨境属性数据的基本步骤&#xff1a; 点击获取测试key和secret构造请求参数&#xff0c;包括商品ID和其他必要参数&#xff0c;如接口权限、请求类型等。通过API接口链接&#xff0c;将请…

App启动——Application的创建

Application的创建 一个 app 启动时候创建一个 Application 对象。这个对象的创建时间在 frameworks 中调用创建的&#xff0c;创建流程可见下图&#xff0c;涉及的几个主要的类调用。 一、主线程运行入口 ​ APP进程起来后&#xff0c;主线程运行入库 ActivityThread.main()…

Docker DeskTop的安装(Windows版本)

目录 一、官网下载Docker安装包 二、安装Docker DeskTop 2.1 双击 Docker Installer.exe 以运行安装程序 2.2 安装操作 2.3 关于更改Docker安装位置 2.3.1 自定义安装路径&#xff08;不推荐&#xff09; 2.3.2 移动 Docker 镜像存储位置 三、启动Docker DeskTop 一、官…

GaussDB SQL基础语法-变量常量

目录 一、前言 二、GaussDB数据库中的常量和变量的基本概述及语法定义 1、变量定义 2、常量定义 3、其他&#xff08;%TYPE、%ROWTYPE属性&#xff09; 三、在GaussDB数据库中如何使用变量&常量&#xff08;示例&#xff09; 示例一&#xff0c;定义常量&变量&a…

有什么软件可以管控员工的电脑桌面

信息化的快速发展&#xff0c;员工在工作中使用电脑的情况越来越普遍。然而&#xff0c;员工在使用电脑时可能会出现工作效率低下、滥用公司资源等问题&#xff0c;因此对员工电脑进行监测和管理显得尤为重要。 1、域之盾软件 它是一款功能强大的电脑监控软件&#xff0c;可以…

(二)Spring源码解析:默认标签解析

一、概述 还记得我们在上一讲末尾提到的关于默认标签解析和自定义标签解析吧。本讲就来针对默认标签解析进行讲解。为了便于衔接上一讲的内容&#xff0c;我们将源码部分粘贴出来&#xff1a; 从上图中的源码中&#xff0c;我们可以看出默认标签的解析是在parseDefaultElement…

Unit2_1:动态规划DP

文章目录 一、介绍二、0-1背包问题问题描述分析伪代码时间复杂度 三、钢条切割问题问题描述分析伪代码过程 四、矩阵链乘法背景性质分析案例伪代码 一、介绍 动态规划类似于分治法,它们都将一个问题划分为更小的子问题 最优子结构:问题的最优解包含子问题的最优解。DP适用的原…