微信H5支付——java

使用微信H5支付

限制条件:只能在微信环境外部发起

 微信官方文档:H5下单 - H5支付 | 微信支付商户文档中心

 此业务逻辑为用户购买VIP,根据实际业务进行修改

wxH5Pay:

transactionAmount:付款金额

userId:用户ID

cardId:卡片ID

package com.yys.szcp.controller.app;

import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONObject;
import com.yys.szcp.config.jwt.JwtIgnore;
import com.yys.szcp.entity.SharingConfig;
import com.yys.szcp.entity.TAppUser;
import com.yys.szcp.entity.TCard;
import com.yys.szcp.entity.TMember;
import com.yys.szcp.service.SharingConfigService;
import com.yys.szcp.service.TAppUserService;
import com.yys.szcp.service.TCardService;
import com.yys.szcp.service.TMemberService;
import com.yys.szcp.utils.ResultUtil;
import com.yys.szcp.utils.WebUtils;
import com.yys.szcp.utils.WechatPayApiV3Util;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.yys.szcp.utils.WebUtils.getRequest;


@RequestMapping("app/user")
@RestController
public class H5Controller {

    @Autowired
    private SharingConfigService sharingConfigService;
    @Autowired
    private TAppUserService userService;
    @Resource
    private TMemberService tMemberService;
    @Autowired
    private TCardService tCardService;

    private static final Logger logger = LoggerFactory.getLogger(H5Controller.class);
    String result = null;

    @JwtIgnore
    @ApiOperation(value = "支付", notes = "支付")
    @PostMapping("/wxH5Pay")
    public ResultUtil wxH5Pay(HttpServletRequest request,
                              @RequestParam("transactionAmount") Double transactionAmount,
                              @RequestParam("userId") String userId,
                              Integer cardId) {

        TAppUser userInfo = userService.getById(userId);
        if (Objects.isNull(userInfo)) {
            return ResultUtil.error("用户不存在");
        }
        JSONObject jsonObject = new JSONObject();
        //设置请求参数
        String appid = sharingConfigService.getGlobalConfig(SharingConfig.MINI_APPID);
        jsonObject.put("appid", appid);
        //设置请求参数(商户号)
        String mch_id = sharingConfigService.getGlobalConfig(SharingConfig.MCH_ID);
        jsonObject.put("mchid", mch_id);
        jsonObject.put("description", "VIP");
        DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
        Calendar calendar = Calendar.getInstance();
        String dateName = df.format(calendar.getTime());
        int v = (int) ((Math.random() * 9 + 1) * 1000000);
        //设置商户订单号
        String outTradeNo = dateName + v;
        jsonObject.put("out_trade_no", outTradeNo);
        jsonObject.put("attach", userInfo.getId().toString());
        String notifyHost = sharingConfigService.getGlobalConfig(SharingConfig.DOMAIN_HOST);
        jsonObject.put("notify_url", notifyHost + "/app/user/payCallback");
        JSONObject amount = new JSONObject();
        amount.put("total", transactionAmount.intValue());
        amount.put("currency", "CNY");
        jsonObject.put("amount", amount);
        JSONObject scene_info = new JSONObject();
        scene_info.put("payer_client_ip", WebUtils.getRemoteAddr(request));
        JSONObject h5_info = new JSONObject();
        h5_info.put("type", getDeviceType());
        scene_info.put("h5_info", h5_info);
        jsonObject.put("scene_info", scene_info);
        String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/h5";
        try {
            result = HttpRequest.post(url)
                    .header(Header.CONTENT_TYPE, "application/json")
                    .header("ACCEPT", "application/json")
                    // 签名
                    .header("Authorization", "WECHATPAY2-SHA256-RSA2048" + " "
                            + WechatPayApiV3Util.getToken("POST", url, jsonObject.toJSONString(), mch_id, "5CD531F298000804A4AEB3A798A878DA0BA0AFBF", "pem/apiclient_key_jq.pem"))
                    .body(jsonObject.toJSONString())
                    .execute().body();
            //应该创建 支付表数据
            if (result != null) {
               //业务逻辑-----------
               System.out.println("微信接口调用成功 并且新增支付信息成功");
                
            }
            return ResultUtil.success(JSONObject.parse(result));
        } catch (Exception e) {
            e.printStackTrace();
            return ResultUtil.error("支付失败");
        }
    }
}

 payCallback:回调

 /**
     * 充值vip回调
     */
    @RequestMapping(value = "/payCallback")
    @JwtIgnore
    @ResponseBody
    @ApiOperation(value = "充值vip回调", notes = "充值vip回调")
    public void payCallback(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("微信回调接口方法 start");
        String inputLine = "";
        String notityXml = "";
        try {
            while ((inputLine = request.getReader().readLine()) != null) {
                notityXml += inputLine;
            }
            //关闭流
            request.getReader().close();
            logger.info("微信回调内容信息:" + notityXml);
            //解析成json
            JSONObject data = JSONObject.parseObject(notityXml);
            //判断 支付是否成功
            if ("TRANSACTION.SUCCESS".equals(data.get("event_type"))) {
                // 在这里处理接收到的支付结果通知数据
                Map<String, Object> resourceMap = handlePaymentNotification(data);

                // 获取 trade_state 值
                String tradeState = resourceMap.get("trade_state").toString();
                if (tradeState.equals("SUCCESS")) {
                    // 获取 amount 字段
                    Map<String, Object> amountMap = (Map<String, Object>) resourceMap.get("amount");
                    //获得 返回的商户订单号
                    String outTradeNo = resourceMap.get("out_trade_no").toString();
                    String attach = resourceMap.get("attach").toString();
                    String total_fee = amountMap.get("payer_total").toString();
                   //业务逻辑------------
                        // 返回成功响应
                        response.setStatus(HttpServletResponse.SC_OK);
                        response.getWriter().write("OK");
                  
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
handlePaymentNotification:
private Map<String, Object> handlePaymentNotification(JSONObject data) {
        logger.info("进入解析resource:" + data.toString());
        Map<String, Object> resourceMap = null;
        // 在这里解析和处理接收到的支付结果通知数据
        System.out.println("接收到支付结果通知:" + data.toJSONString());
        //解密resource中的通知数据
        String resource = data.get("resource").toString();
        resourceMap = WechatPayApiV3Util.decryptFromResource(resource, "e10adc3949ba59abbe56e057f20f883e", 1);
        logger.info("解析resource结果:" + resourceMap);
        return resourceMap;
    }
getDeviceType:
public static String getDeviceType() {
        HttpServletRequest request = getRequest();
        String sign = request.getHeader("user-agent");
        if (sign.contains("Android")) {
            return "android";
        } else if (sign.contains("iPhone")) {
            return "ios";
        }
        return "Wap";
    }
WebUtils:没有用到的方法可以删除
package com.yys.szcp.utils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class WebUtils {
	private static final Logger log = LoggerFactory.getLogger(WebUtils.class);

	 	/**
	 	 * 获取系统上下文路径
	 	 * @param request
	 	 * @return
	 	 */
	 	public static String getContextPath(HttpServletRequest request) {
	 		return request.getSession().getServletContext().getContextPath();
	 	}

	 	/**
	 	 * 获取用户的真正IP地址
	 	 * @param request
	 	 * @return
	 	 */
	 	public static String getRemoteAddr(HttpServletRequest request) {
	 		String ip = request.getHeader("X-Forwarded-For");
	 		if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase(ip, "unknown")) {
	 			ip = request.getHeader("Proxy-Client-IP");
	 		}
	 		if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase(ip, "unknown")) {
	 			ip = request.getHeader("WL-Proxy-Client-IP");
	 		}
	 		if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase(ip, "unknown")) {
	 			ip = request.getHeader("HTTP_CLIENT_IP");
	 		}

	 		// 民安赖波经理提供的从head的什么值获取IP地址
	 		if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase(ip, "unknown")) {
	 			ip = request.getHeader("X-Real-IP");
	 		}

	 		if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase(ip, "unknown")) {
	 			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
	 		}
	 		if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase(ip, "unknown")) {
	 			ip = request.getRemoteAddr();
	 		}
	 		if (StringUtils.isNotBlank(ip) && StringUtils.indexOf(ip, ",") > 0) {
	 			String[] ipArray = StringUtils.split(ip, ",");
	 			ip = ipArray[0];
	 		}
	 		return ip;
	 	}

	 	/**
	 	 * 获取本地IP地址
	 	 */
	 	public static String getLocalAddr() throws UnknownHostException {
	 		InetAddress addr = InetAddress.getLocalHost();
	 		return addr.getHostAddress();
	 	}

	 	/**
	 	 * 处理乱码
	 	 */
	 	public static String encodingHelp(String s) throws Exception {
	 		return new String(s.getBytes("ISO-8859-1"), "UTF-8");
	 	}

	 	/**
	 	 * 对ajax提交过来的参数进行解码
	 	 * @param s
	 	 * @return
	 	 * @throws Exception
	 	 */
	 	public static String ajaxDecode(String s) throws Exception {
	 		return URLDecoder.decode(s, "UTF8");
	 	}

	 	/**
	 	 * 页面弹出提示信息
	 	 * @param response
	 	 * @param msg
	 	 * @throws Exception
	 	 */
	 	public static void alertMsg(HttpServletResponse response, String msg) throws Exception {
	 		response.setCharacterEncoding("UTF-8");
	 		response.setContentType("text/html");

	 		StringBuilder sb = new StringBuilder();
	 		sb.append("<script type='text/javascript'>");
	 		sb.append("alert(\""+msg+"\");");
	 		sb.append("</script>");
	 		PrintWriter out = response.getWriter();
	 		out.print(sb.toString());
	 		out.close();
	 	}

	/**
	 * 获取request
	 */
	public static HttpServletRequest getRequest()
	{
		try
		{
			return getRequestAttributes().getRequest();
		}
		catch (Exception e)
		{
			return null;
		}
	}

	public static ServletRequestAttributes getRequestAttributes()
	{
		try
		{
			RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
			return (ServletRequestAttributes) attributes;
		}
		catch (Exception e)
		{
			return null;
		}
	}

//	 	public static HttpServletRequest getRequest() {
//	 		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//	 	}

	 	/**
	 	 * 判断是否为ajax请求
	 	 * @param request
	 	 * @return
	 	 */
	 	public static boolean isAjaxReqest(HttpServletRequest request) {
	 		if ((request.getHeader("accept") != null && (request.getHeader("accept").indexOf("application/json") > -1) || (request.getHeader("X-Requested-With") != null && request.getHeader(
	 				"X-Requested-With").indexOf("XMLHttpRequest") > -1))) {
	 			return true;
	 		}
	 		return false;
	 	}

	public static String MD5(String sourceStr) {
		String result = "";
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			md.update(sourceStr.getBytes());
			byte b[] = md.digest();
			int i;
			StringBuffer buf = new StringBuffer("");
			for (int offset = 0; offset < b.length; offset++) {
				i = b[offset];
				if (i < 0) {
					i += 256;

				}
				if (i < 16) {
					buf.append("0");
				}
				buf.append(Integer.toHexString(i));
			}
			result = buf.toString();

		} catch (NoSuchAlgorithmException e) {
			System.out.println(e);
		}
		return result;
	}
	@SuppressWarnings("rawtypes")
	public static Map<String, String> doXMLParse(String strxml) throws Exception {
		if (null == strxml || "".equals(strxml)) {
			return null;
		}

		Map<String, String> m = new HashMap<String, String>();
		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;
	}
	@SuppressWarnings("rawtypes")
	private 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 InputStream String2Inputstream(String str) {
		return new ByteArrayInputStream(str.getBytes());
	}
	/**
	 * 3      * 方法名: getRemotePortData
	 * 4      * 描述: 发送远程请求 获得代码示例
	 * 5      * 参数:  @param urls 访问路径
	 * 6      * 参数:  @param param 访问参数-字符串拼接格式, 例:port_d=10002&port_g=10007&country_a=
	 * 7      * 创建人: Xia ZhengWei
	 * 8      * 创建时间: 2017年3月6日 下午3:20:32
	 * 9      * 版本号: v1.0
	 * 10      * 返回类型: String
	 * 11
	 */
	public static String getRemotePortData(String urls, String param) {

		try {
			URL url = new URL(urls);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			// 设置连接超时时间
			conn.setConnectTimeout(30000);
			// 设置读取超时时间
			conn.setReadTimeout(30000);
			conn.setRequestMethod("POST");
			if (!StringUtils.isBlank(param)) {
				conn.setRequestProperty("Origin", "https://sirius.searates.com");// 主要参数
				conn.setRequestProperty("Referer", "https://sirius.searates.com/cn/port?A=ChIJP1j2OhRahjURNsllbOuKc3Y&D=567&G=16959&shipment=1&container=20st&weight=1&product=0&request=&weightcargo=1&");
				conn.setRequestProperty("X-Requested-With", "XMLHttpRequest");// 主要参数
			}
			// 需要输出
			conn.setDoInput(true);
			// 需要输入
			conn.setDoOutput(true);
			// 设置是否使用缓存
			conn.setUseCaches(false);
			// 设置请求属性
			conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			conn.setRequestProperty("Connection", "Keep-Alive");// 维持长连接
			conn.setRequestProperty("Charset", "UTF-8");

			if (!StringUtils.isBlank(param)) {
				// 建立输入流,向指向的URL传入参数
				DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
				dos.writeBytes(param);
				dos.flush();
				dos.close();
			}
			// 输出返回结果
			InputStream input = conn.getInputStream();
			int resLen = 0;
			byte[] res = new byte[1024];
			StringBuilder sb = new StringBuilder();
			while ((resLen = input.read(res)) != -1) {
				sb.append(new String(res, 0, resLen));
			}
			return sb.toString();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "";
	}
	 	/**
	 	 * 发送ajax
	 	 * @param response
	 	 * @param code
	 	 * @param msg
	 	 * @param data
	 	 * @throws Exception
	 	 */
	 	/*public static void ajaxJson(HttpServletResponse response, Integer code, String msg) throws Exception {
	 		String json = JsonUtils.objectToJson(new ResultUtil(code, msg));
	 		response.setContentType("text/json; charset=utf-8");
	 		response.setHeader("Cache-Control", "no-cache"); // 取消浏览器缓存
	 		PrintWriter out = response.getWriter();
	 		out.print(json);
	 		out.flush();
	 		out.close();
	 	}*/

	/**
	 2      *
	 3      * 方法用途: 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序),并且生成url参数串<br>
	 4      * 实现步骤: <br>
	 5      *
	 6      * @param paraMap   要排序的Map对象
	 7      * @param urlEncode   是否需要URLENCODE
	 8      * @param keyToLower    是否需要将Key转换为全小写
	 9      *            true:key转化成小写,false:不转化
	 10      * @return
	 11      */
	public static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower) {
		String buff = "";
		 Map<String, String> tmpMap = paraMap;
		 try
		 {
			 List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
			            // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
			 Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>()
					 {
				 @Override
				 public int compare (Map.Entry < String, String > o1, Map.Entry < String, String > o2)
				 {
					 return (o1.getKey()).toString().compareTo(o2.getKey());

				}

			});
			             // 构造URL 键值对的格式
			 StringBuilder buf = new StringBuilder();
			for (Map.Entry<String, String> item : infoIds)
				{
				 if (StringUtils.isNotBlank(item.getKey()))
					{
					 String key = item.getKey();
					 String val = item.getValue();
					 if (urlEncode)
						{
						 val = URLEncoder.encode(val, "utf-8");

					}
					 if (keyToLower)
						 {
						 buf.append(key.toLowerCase() + "=" + val);

					} else
					 {
						 buf.append(key + "=" + val);

					}
					 buf.append("&");

				}

			}
			 buff = buf.toString();
			 if (buff.isEmpty() == false)
				 {
				 buff = buff.substring(0, buff.length() - 1);

			}

		} catch(Exception e)
		 {
			 return null;

		}
		return buff;
	}


	public static String md5(String msg) {
		if (null == msg) {
			return null;
		}
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			byte[] out = md.digest(msg.getBytes());
			String str = org.apache.commons.codec.binary.Base64.encodeBase64String(out);
			return str;
		} catch (Exception e) {
			return "";
		}
	}

	public static Map<String, String>  sendPay(String url, Map<String, Object> params) throws Exception {
		try {
//			String payload = objectMapper.writeValueAsString(params);

			// 创建ObjectMapper实例
			ObjectMapper objectMapper = new ObjectMapper();

			// 将"pay_amount"和"valid_time"字段的值从字符串转换为数字
			params.put("total_amount", Integer.parseInt((String) params.get("total_amount")));
			params.put("valid_time", Integer.parseInt((String) params.get("valid_time")));

			// 将Map转换回JSON字符串
			String modifiedJsonStr = objectMapper.writeValueAsString(params);

			// 打印修改后的JSON字符串
			System.out.println(modifiedJsonStr);

			URL apiUrl = new URL(url);
			HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
			connection.setRequestMethod("POST");
			connection.setRequestProperty("Content-Type", "application/json");
			connection.setDoOutput(true);

			OutputStream outputStream = connection.getOutputStream();
			outputStream.write(modifiedJsonStr.getBytes(StandardCharsets.UTF_8));
			outputStream.flush();
			outputStream.close();

			int responseCode = connection.getResponseCode();
			if (responseCode == HttpURLConnection.HTTP_OK) {
				JsonNode jsonResponse = objectMapper.readTree(connection.getInputStream());
				// 解析响应JSON数据并返回一个Map<String, String>对象

				// 将JsonNode转换为Map对象
				Map<String, String> responseMap = objectMapper.convertValue(jsonResponse, Map.class);

				return responseMap;
			} else {
				String errorMessage = responseCode + " - " + connection.getResponseMessage();
				return Collections.singletonMap("err_no", errorMessage);
			}
		} catch (IOException e) {
			// 处理异常并返回适当的错误信息
			e.printStackTrace();
			return Collections.singletonMap("error", "请求失败:" + e.getMessage());
		}
	}

	public static Map<String, String>  send(String url, Map<String, Object> params) throws Exception {
		try {
			// 创建ObjectMapper实例
			ObjectMapper objectMapper = new ObjectMapper();
			// 将"withdraw_amount"字段的值从字符串转换为数字
//			params.put("withdraw_amount", Integer.parseInt((String) params.get("withdraw_amount")));
			// 将Map转换回JSON字符串
			String modifiedJsonStr = objectMapper.writeValueAsString(params);
			// 打印修改后的JSON字符串
			System.out.println(modifiedJsonStr);

			URL apiUrl = new URL(url);
			HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
			connection.setRequestMethod("POST");
			connection.setRequestProperty("Content-Type", "application/json");
			connection.setDoOutput(true);

			OutputStream outputStream = connection.getOutputStream();
			outputStream.write(modifiedJsonStr.getBytes(StandardCharsets.UTF_8));
			outputStream.flush();
			outputStream.close();

			int responseCode = connection.getResponseCode();
			if (responseCode == HttpURLConnection.HTTP_OK) {
				JsonNode jsonResponse = objectMapper.readTree(connection.getInputStream());
				// 解析响应JSON数据并返回一个Map<String, String>对象
				Map<String, String> responseMap = objectMapper.convertValue(jsonResponse, Map.class);
				return responseMap;
			} else {
				String errorMessage = responseCode + " - " + connection.getResponseMessage();
				return Collections.singletonMap("err_no", errorMessage);
			}
		} catch (IOException e) {
			// 处理异常并返回适当的错误信息
			e.printStackTrace();
			return Collections.singletonMap("error", "请求失败:" + e.getMessage());
		}
	}
}
WechatPayApiV3Util:
package com.yys.szcp.utils;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;

/**
 * @Author sanji
 * @Date 2023/11/27 19:00
 * @Version 1.0
 * @description
 */
@Slf4j
public class WechatPayApiV3Util {

    /**
     * 获取微信支付平台证书  注意不是商户api证书
     */
    private static final String WechatPaySerial = "https://api.mch.weixin.qq.com/v3/certificates";


    /**
     * 微信支付API v3 签名
     *
     * @param method       请求类型GET、POST
     * @param url          请求地址
     * @param body         请求数据 GET: 传"" POST: json串
     * @param merchantId   商户号
     * @param certSerialNo 商户证书(Api证书)序列号
     * @param filename     商户证书(Api证书) 私钥
     * @return
     * @throws Exception
     */
    public static String getToken(String method, String url, String body, String merchantId, String certSerialNo, String filename) throws Exception {
        String signStr = "";
        HttpUrl httpurl = HttpUrl.parse(url);
        // 随机字符串
        String nonceStr = getRandomString(32);
        // 时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        if (StringUtils.isEmpty(body)) {
            body = "";
        }
        String message = buildMessage(method, httpurl, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"), filename);
        signStr = "mchid=\"" + merchantId + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + certSerialNo + "\","
                + "signature=\"" + signature + "\"";
        log.info("Authorization Token:" + signStr);
        return signStr;
    }

    public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }


    public static String sign(byte[] message, String filename) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(filename));
        sign.update(message);
        return Base64.encodeBase64String(sign.sign());
    }

    /**
     * 获取私钥。
     *
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {
        // 编译后的相对路径
        ClassPathResource classPathResource = new ClassPathResource(filename);
        InputStream inputStream = classPathResource.getInputStream();
        Scanner scanner = new Scanner(inputStream, "UTF-8");
        String content = scanner.useDelimiter("\\A").next();
        // 绝对路径
//        String content = new String(Files.readAllBytes(Paths.get("F:\\key\\publicKey.pem")), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            log.info("异常:" + e);
            throw new RuntimeException("无效的密钥格式");
        }
    }


    /**
     * 获取微信支付平台证书
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static String getWechatPaySerial(String token) throws IOException {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        try {
            HttpGet httpGet = new HttpGet(WechatPaySerial);
            // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
            httpGet.addHeader("Content-Type", "application/json");
            // 添加认证信息
            httpGet.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + token);
            httpGet.addHeader("Accept", "application/json");
            //httpGet.addHeader("User-Agent", " ");
            response = httpclient.execute(httpGet);
            entity = response.getEntity();//获取返回的数据
            log.info("返回结果:{}", EntityUtils.toString(entity));
            return EntityUtils.toString(entity);
        } catch (Exception e) {
            log.info("提现失败:{}", e.getMessage());
        } finally {
            // 关闭流
            response.close();
        }
        return null;
    }

    /**
     * 获取商户证书。
     *
     * @return X509证书
     */
    public static X509Certificate getCertificate() throws IOException {
        String APICLIENT_CERT = "pem/apiclient_cert.pem";
        InputStream fis = WechatPayApiV3Util.class.getClassLoader().getResourceAsStream(APICLIENT_CERT);
        try (BufferedInputStream bis = new BufferedInputStream(fis)) {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书文件", e);
        }
    }

    /**
     * 获取商户证书序列号
     *
     * @return
     * @throws IOException
     */
    public static String getSerialNo() throws IOException {
        X509Certificate certificate = getCertificate();
        return certificate.getSerialNumber().toString(16).toUpperCase();
    }

    /**
     *  生成随机数
     * @param length
     * @return
     */
    public static String getRandomString(int length) {
//      创建一个随机数生成器。
        SecureRandom random = new SecureRandom();
        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < length; ++i) {
//          返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。
            int number = random.nextInt(3);
            long result = 0;
//          Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。
//          Math.random()  返回带正号的 double 值,该值大于等于 0.0 且小于 1.0。
//          Math.round(Math.random() * 25 + 97)  返回最接近参数的 long。
            switch (number) {
                case 0:
                    result = Math.round(Math.random() * 25 + 65);
                    sb.append(String.valueOf((char) result));
                    break;
                case 1:
                    result = Math.round(Math.random() * 25 + 97);
                    sb.append(String.valueOf((char) result));
                    break;
                case 2:
                    sb.append(String.valueOf(new Random().nextInt(10)));
                    break;
            }
        }

        return sb.toString();
    }

     /**
     * 对称解密,异步通知的加密数据
     * @param resource 加密数据
     * @param apiV3Key apiV3密钥
     * @param type 1-支付,2-退款
     * @return
     */
    public static Map<String, Object> decryptFromResource(String resource, String apiV3Key, Integer type) {

        String msg = type==1?"支付成功":"退款成功";
        log.info(msg+",回调通知,密文解密");
        try {
        //通知数据
        Map<String, String> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, String>>() {});
        //数据密文
        String ciphertext = resourceMap.get("ciphertext");
        //随机串
        String nonce = resourceMap.get("nonce");
        //附加数据
        String associatedData = resourceMap.get("associated_data");

        log.info("密文: {}", ciphertext);
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
        String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        log.info(msg+",回调通知,解密结果 : {}", resourceStr);
        return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>(){});
    }catch (Exception e){
        throw new RuntimeException("回调参数,解密失败!");
        }
    }
}

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值