17 changed files with 1653 additions and 29 deletions
@ -0,0 +1,14 @@ |
|||||
|
package com.ccsens.wechatutil.exception; |
||||
|
|
||||
|
public class PayException extends RuntimeException{ |
||||
|
private int code; |
||||
|
public PayException(int code,String message){ |
||||
|
super(message); |
||||
|
this.code = code; |
||||
|
} |
||||
|
|
||||
|
public PayException(String message){ |
||||
|
super(message); |
||||
|
this.code = -1; |
||||
|
} |
||||
|
} |
@ -0,0 +1,67 @@ |
|||||
|
package com.ccsens.wechatutil.payutil; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.security.GeneralSecurityException; |
||||
|
import java.security.InvalidAlgorithmParameterException; |
||||
|
import java.security.InvalidKeyException; |
||||
|
import java.security.NoSuchAlgorithmException; |
||||
|
import java.util.Base64; |
||||
|
import javax.crypto.Cipher; |
||||
|
import javax.crypto.NoSuchPaddingException; |
||||
|
import javax.crypto.spec.GCMParameterSpec; |
||||
|
import javax.crypto.spec.SecretKeySpec; |
||||
|
|
||||
|
/** |
||||
|
* @author :mr.zhangsan |
||||
|
* @date :Created in 2021/6/21 21:17 |
||||
|
* @version v1.0: |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class AesUtil { |
||||
|
|
||||
|
static final int KEY_LENGTH_BYTE = 32; |
||||
|
static final int TAG_LENGTH_BIT = 128; |
||||
|
private final byte[] aesKey; |
||||
|
|
||||
|
public AesUtil(byte[] key) { |
||||
|
if (key.length != KEY_LENGTH_BYTE) { |
||||
|
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); |
||||
|
} |
||||
|
this.aesKey = key; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 对加密的授权/解除授权结果进行解密 |
||||
|
* @param associatedData |
||||
|
* @param nonce |
||||
|
* @param ciphertext |
||||
|
* @return |
||||
|
* @throws GeneralSecurityException |
||||
|
* @throws IOException |
||||
|
*/ |
||||
|
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) |
||||
|
throws GeneralSecurityException, IOException { |
||||
|
try { |
||||
|
log.info("---------1111"); |
||||
|
log.info("cipher" + Cipher.getInstance("AES/GCM/NoPadding")); |
||||
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); |
||||
|
|
||||
|
log.info("key" + new SecretKeySpec(aesKey, "AES")); |
||||
|
SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); |
||||
|
log.info("spec" + new GCMParameterSpec(TAG_LENGTH_BIT, nonce)); |
||||
|
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); |
||||
|
log.info("------------1"); |
||||
|
cipher.init(Cipher.DECRYPT_MODE, key, spec); |
||||
|
log.info("------------2"); |
||||
|
cipher.updateAAD(associatedData); |
||||
|
log.info("------------3"); |
||||
|
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); |
||||
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
||||
|
throw new IllegalStateException(e); |
||||
|
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) { |
||||
|
throw new IllegalArgumentException(e); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,132 @@ |
|||||
|
package com.ccsens.wechatutil.payutil; |
||||
|
|
||||
|
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.conn.ssl.SSLContexts; |
||||
|
import org.apache.http.conn.ssl.TrustSelfSignedStrategy; |
||||
|
import org.apache.http.entity.StringEntity; |
||||
|
import org.apache.http.impl.client.CloseableHttpClient; |
||||
|
import org.apache.http.impl.client.HttpClients; |
||||
|
import org.apache.http.util.EntityUtils; |
||||
|
|
||||
|
import javax.net.ssl.*; |
||||
|
import java.io.*; |
||||
|
import java.net.MalformedURLException; |
||||
|
import java.net.URL; |
||||
|
import java.net.URLConnection; |
||||
|
import java.security.KeyStore; |
||||
|
|
||||
|
/** |
||||
|
* @author :mr.zhangsan |
||||
|
* @date :Created in 2021/6/21 23:21 |
||||
|
* @version v1.0: |
||||
|
*/ |
||||
|
public class HttpsUtil { |
||||
|
|
||||
|
/** |
||||
|
* 发送https请求 使用PKCS12类型证书 |
||||
|
* |
||||
|
* @param requestUrl 请求地址 |
||||
|
* @param requestMethod 请求方式(GET、POST) |
||||
|
* @param postStr 提交的数据 |
||||
|
* @return String(Json) |
||||
|
*/ |
||||
|
public static String httpsRequest(String requestUrl, String requestMethod, String postStr,String pKCS12Path,String pKCS12Pwd) throws Exception { |
||||
|
SSLContext sc = null; |
||||
|
FileInputStream instream = null; |
||||
|
KeyStore keyStore = null; |
||||
|
|
||||
|
keyStore = KeyStore.getInstance("PKCS12"); |
||||
|
instream = new FileInputStream(new File(pKCS12Path)); |
||||
|
keyStore.load(instream,pKCS12Pwd.toCharArray()); |
||||
|
instream.close(); |
||||
|
|
||||
|
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial( |
||||
|
keyStore, pKCS12Pwd.toCharArray()).build(); |
||||
|
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( |
||||
|
sslcontext, |
||||
|
new String[] { "TLSv1" }, |
||||
|
null, |
||||
|
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER |
||||
|
); |
||||
|
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf) .build(); |
||||
|
HttpPost httpost = new HttpPost(requestUrl); |
||||
|
httpost.addHeader("Connection", "keep-alive"); |
||||
|
httpost.addHeader("Accept", "*/*"); |
||||
|
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); |
||||
|
httpost.addHeader("Host", "api.mch.weixin.qq.com"); |
||||
|
httpost.addHeader("X-Requested-With", "XMLHttpRequest"); |
||||
|
httpost.addHeader("Cache-Control", "max-age=0"); |
||||
|
httpost.addHeader("User-Agent", |
||||
|
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); |
||||
|
httpost.setEntity(new StringEntity(postStr, "UTF-8")); |
||||
|
CloseableHttpResponse response = httpclient.execute(httpost); |
||||
|
HttpEntity entity = response.getEntity(); |
||||
|
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8"); |
||||
|
EntityUtils.consume(entity); |
||||
|
|
||||
|
return jsonStr; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置信任自签名证书 |
||||
|
* |
||||
|
* @param keyStorePath 密钥库路径 |
||||
|
* @param keyStorepass 密钥库密码 |
||||
|
* @return |
||||
|
*/ |
||||
|
|
||||
|
public static SSLContext custom(String keyStorePath, String keyStorepass) { |
||||
|
SSLContext sc = null; |
||||
|
FileInputStream instream = null; |
||||
|
KeyStore trustStore = null; |
||||
|
try { |
||||
|
trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
||||
|
instream = new FileInputStream(new File(keyStorePath)); |
||||
|
trustStore.load(instream, keyStorepass.toCharArray()); |
||||
|
// 相信自己的CA和所有自签名的证书
|
||||
|
sc = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build(); |
||||
|
// 构造 javax.net.ssl.TrustManager 对象
|
||||
|
TrustManagerFactory tmf = |
||||
|
TrustManagerFactory.getInstance("SunX509", "SunJSSE"); |
||||
|
tmf.init(trustStore); |
||||
|
TrustManager tms [] = tmf.getTrustManagers(); |
||||
|
// 使用构造好的 TrustManager 访问相应的 https 站点
|
||||
|
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); |
||||
|
sslContext.init(null, tms, new java.security.SecureRandom()); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} finally { |
||||
|
try { |
||||
|
instream.close(); |
||||
|
} catch (IOException e) { |
||||
|
} |
||||
|
} |
||||
|
return sc; |
||||
|
} |
||||
|
|
||||
|
public static String sendGet(String url, String charset, int timeout) { |
||||
|
String result = ""; |
||||
|
try { |
||||
|
URL u = new URL(url); |
||||
|
try { |
||||
|
URLConnection conn = u.openConnection(); |
||||
|
conn.connect(); |
||||
|
conn.setConnectTimeout(timeout); |
||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset)); |
||||
|
String line = ""; |
||||
|
while ((line = in.readLine()) != null) { |
||||
|
result = result + line; |
||||
|
} |
||||
|
in.close(); |
||||
|
} catch (IOException e) { |
||||
|
return result; |
||||
|
} |
||||
|
} catch (MalformedURLException e) { |
||||
|
return result; |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
@ -0,0 +1,163 @@ |
|||||
|
package com.ccsens.wechatutil.payutil; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonInclude; |
||||
|
import com.fasterxml.jackson.core.JsonParser; |
||||
|
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
|
import com.fasterxml.jackson.core.type.TypeReference; |
||||
|
import com.fasterxml.jackson.databind.*; |
||||
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStream; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* @author :mr.zhangsan |
||||
|
* @date :Created in 2021/6/21 22:41 |
||||
|
* @version v1.0: |
||||
|
*/ |
||||
|
public class JacksonUtil { |
||||
|
private static ObjectMapper getDefaultObjectMapper() { |
||||
|
ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); |
||||
|
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); |
||||
|
return objectMapper; |
||||
|
} |
||||
|
|
||||
|
private static XmlMapper getDefaultXmlMapper() { |
||||
|
XmlMapper xmlMapper = new XmlMapper(); |
||||
|
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
||||
|
xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); |
||||
|
|
||||
|
return xmlMapper; |
||||
|
} |
||||
|
|
||||
|
public static String getJsonValue(String json,String key) throws IOException{ |
||||
|
JsonNode jsonNode = getDefaultObjectMapper().readTree(json); |
||||
|
return jsonNode.get(key).toString(); |
||||
|
} |
||||
|
|
||||
|
public static <T> T jsonToBean(String json, Class<T> clazz, boolean isSet) throws IOException { |
||||
|
T t = null; |
||||
|
if (!isSet) { |
||||
|
t = getDefaultObjectMapper().readValue(json, clazz); |
||||
|
} |
||||
|
else { |
||||
|
t = getDefaultObjectMapper().readValue(json, new TypeReference<T>() { |
||||
|
}); |
||||
|
} |
||||
|
return t; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 获取泛型的Collection Type |
||||
|
* @param collectionClass 泛型的Collection |
||||
|
* @param elementClasses 元素类 |
||||
|
* @return JavaType Java类型 |
||||
|
* @since 1.0 |
||||
|
*/ |
||||
|
public static JavaType getCollectionType(ObjectMapper mapper, Class<?> collectionClass, Class<?>... elementClasses) { |
||||
|
return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); |
||||
|
} |
||||
|
|
||||
|
//------------------------------------ Json and Bean ----------------------------0
|
||||
|
public static <T> List<T> jsonToBean(String json, Class<T> clazz, Class<?> setClazz) throws IOException { |
||||
|
List<T> tList = null; |
||||
|
ObjectMapper mapper = getDefaultObjectMapper(); |
||||
|
JavaType javaType = getCollectionType(mapper,setClazz,clazz); |
||||
|
tList = getDefaultObjectMapper().readValue(json, javaType); |
||||
|
return tList; |
||||
|
} |
||||
|
|
||||
|
public static <T> String beanToJson(T bean) throws JsonProcessingException { |
||||
|
String json = null; |
||||
|
json = getDefaultObjectMapper().writeValueAsString(bean); |
||||
|
return json; |
||||
|
} |
||||
|
|
||||
|
public static <T> String beanToJson(T bean, boolean pretty) throws JsonProcessingException { |
||||
|
String json = null; |
||||
|
if (!pretty) { |
||||
|
json = getDefaultObjectMapper().writeValueAsString(bean); |
||||
|
} else { |
||||
|
json = getDefaultObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(bean); |
||||
|
} |
||||
|
return json; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------ Json and Map ----------------------------0
|
||||
|
public static Map<String,String> jsonToMap(String json) throws IOException { |
||||
|
Map<String,String> map = null; |
||||
|
ObjectMapper mapper = getDefaultObjectMapper(); |
||||
|
map = getDefaultObjectMapper().readValue(json,Map.class); |
||||
|
return map; |
||||
|
} |
||||
|
|
||||
|
public static String mapToJson(Map<String,String> map) throws JsonProcessingException { |
||||
|
String json = null; |
||||
|
json = getDefaultObjectMapper().writeValueAsString(map); |
||||
|
return json; |
||||
|
} |
||||
|
|
||||
|
public static <T> T mapToBean(Map<String,String> map,Class<T> clazz) { |
||||
|
|
||||
|
try { |
||||
|
return jsonToBean(mapToJson(map),clazz,false); |
||||
|
} catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------ Xml and Bean ----------------------------0
|
||||
|
public static <T> T xmlToBean(InputStream xmlInStream, Class<T> clazz) throws IOException { |
||||
|
T t = null; |
||||
|
t = getDefaultXmlMapper().readValue(xmlInStream, clazz); |
||||
|
return t; |
||||
|
} |
||||
|
|
||||
|
public static <T> String beanToXml(T bean) throws JsonProcessingException { |
||||
|
String xmlString = null; |
||||
|
xmlString = getDefaultXmlMapper().writeValueAsString(bean); |
||||
|
return xmlString; |
||||
|
} |
||||
|
|
||||
|
//这个方法可能有点问题,必须配合其他jar包才能使用
|
||||
|
// <dependency>
|
||||
|
// <groupId>org.codehaus.woodstox</groupId>
|
||||
|
// <artifactId>woodstox-core-asl</artifactId>
|
||||
|
// <version>4.4.1</version>
|
||||
|
// </dependency>
|
||||
|
// public static <T> String beanToXml(T bean, boolean pretty) throws JsonProcessingException {
|
||||
|
//
|
||||
|
// String xmlString = null;
|
||||
|
// if (!pretty)
|
||||
|
// xmlString = getDefaultXmlMapper().writeValueAsString(bean);
|
||||
|
// else
|
||||
|
// xmlString = getDefaultXmlMapper().writerWithDefaultPrettyPrinter().writeValueAsString(bean);
|
||||
|
// return xmlString;
|
||||
|
// }
|
||||
|
|
||||
|
public static <T> String beanToXml(T bean,String root) throws JsonProcessingException { |
||||
|
String xmlString = null; |
||||
|
xmlString = getDefaultXmlMapper().writer().with(SerializationFeature.WRAP_ROOT_VALUE) |
||||
|
.withRootName(root).writeValueAsString(bean); |
||||
|
return xmlString; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------ Xml and Map ----------------------------
|
||||
|
public static Map<String,Object> xmlToMap(InputStream xmlInStream) throws IOException { |
||||
|
Map<String,Object> map = getDefaultXmlMapper().readValue(xmlInStream, Map.class); |
||||
|
return map; |
||||
|
} |
||||
|
|
||||
|
public static String mapToXml(Map<String,String> map,String root) throws JsonProcessingException { |
||||
|
String xmlString = null; |
||||
|
xmlString = getDefaultXmlMapper().writer().with(SerializationFeature.WRAP_ROOT_VALUE) |
||||
|
.withRootName(root).writeValueAsString(map); |
||||
|
return xmlString; |
||||
|
} |
||||
|
} |
@ -0,0 +1,151 @@ |
|||||
|
package com.ccsens.wechatutil.payutil; |
||||
|
|
||||
|
import cn.hutool.core.collection.CollectionUtil; |
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @author :mr.zhangsan |
||||
|
* @date :Created in 2021/4/15 21:11 |
||||
|
* @version v1.0: |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class WxMessageUtil { |
||||
|
/** |
||||
|
* 微信发送订阅消息 |
||||
|
*/ |
||||
|
private static final String URL_SEND_SUBSCRIBE_MESSAGE |
||||
|
= "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%1$s"; |
||||
|
|
||||
|
private static String SUBSCRIBE_MSG_ID_BUY = "bqdx4FW51G-JLU6HjL6LyvUbjpo4rFK6CPor2-w3VwM"; |
||||
|
private static String SUBSCRIBE_MSG_ID_ORDERSEND = "5-rhM8h3x1W82tHCt18g9iXyGELyUKQtBDZiFjgItOo"; |
||||
|
|
||||
|
public enum Packet_Notify_Type { |
||||
|
Buy(1, "购买"), |
||||
|
ReceiveSubscriBuyData(2, "订单发货"); |
||||
|
|
||||
|
public int value; |
||||
|
public String phase; |
||||
|
|
||||
|
Packet_Notify_Type(int value, String thePhase) { |
||||
|
this.value = value; |
||||
|
this.phase = thePhase; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
private static class CommonEntity { |
||||
|
private String value; |
||||
|
} |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
public static class SubscribeEntity<T> { |
||||
|
private String touser; |
||||
|
private String template_id; |
||||
|
private String page; |
||||
|
private T data; |
||||
|
} |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
public static class ReceiveSubscriBuyData { |
||||
|
public ReceiveSubscriBuyData(List<String> params) { |
||||
|
if (CollectionUtil.isNotEmpty(params) && params.size() >= 2) { |
||||
|
character_string1 = new CommonEntity(); |
||||
|
amount3 = new CommonEntity(); |
||||
|
thing4 = new CommonEntity(); |
||||
|
time2 = new CommonEntity(); |
||||
|
number11 = new CommonEntity(); |
||||
|
character_string1.setValue(params.get(0)); |
||||
|
amount3.setValue(params.get(1)); |
||||
|
thing4.setValue(params.get(2)); |
||||
|
time2.setValue(params.get(3)); |
||||
|
number11.setValue(params.get(4)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private CommonEntity character_string1;//订单编号
|
||||
|
private CommonEntity amount3;//订单金额
|
||||
|
private CommonEntity thing4;//商品名称
|
||||
|
private CommonEntity time2;//支付时间
|
||||
|
private CommonEntity number11;//购买数量
|
||||
|
} |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
public static class ReceiveSubscriOrderSendData { |
||||
|
public ReceiveSubscriOrderSendData(List<String> params) { |
||||
|
if (CollectionUtil.isNotEmpty(params) && params.size() >= 2) { |
||||
|
thing1 = new CommonEntity(); |
||||
|
character_string2 = new CommonEntity(); |
||||
|
date3 = new CommonEntity(); |
||||
|
thing4 = new CommonEntity(); |
||||
|
character_string5 = new CommonEntity(); |
||||
|
thing1.setValue(params.get(0)); |
||||
|
character_string2.setValue(params.get(1)); |
||||
|
date3.setValue(params.get(2)); |
||||
|
thing4.setValue(params.get(3)); |
||||
|
character_string5.setValue(params.get(4)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private CommonEntity thing1;//商品名称
|
||||
|
private CommonEntity character_string2;//订单号
|
||||
|
private CommonEntity date3;//发货时间
|
||||
|
private CommonEntity thing4;//快递公司
|
||||
|
private CommonEntity character_string5;//快递单号
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送微信订阅消息 |
||||
|
* |
||||
|
* @param notifyType 通知类型 |
||||
|
* @param toUser 用户openid |
||||
|
* @param page 小程序页面路径 |
||||
|
* @param params 小程序模板数据 |
||||
|
* @return |
||||
|
* @throws Exception |
||||
|
// */
|
||||
|
// public static boolean sendSubcribeMsg(Packet_Notify_Type notifyType, String toUser, String page, List<String> params) throws Exception {
|
||||
|
// String ret = null;
|
||||
|
// String url = String.format(URL_SEND_SUBSCRIBE_MESSAGE, WxTokenUtil.getToken());
|
||||
|
// switch (notifyType) {
|
||||
|
// //订单支付成功通知
|
||||
|
// case Buy: {
|
||||
|
// SubscribeEntity<ReceiveSubscriBuyData> t = new SubscribeEntity<>();
|
||||
|
// t.setTouser(toUser);
|
||||
|
// t.setTemplate_id(SUBSCRIBE_MSG_ID_BUY);
|
||||
|
// t.setPage(page);
|
||||
|
// t.setData(new ReceiveSubscriBuyData(params));
|
||||
|
// System.out.println(JacksonUtil.beanToJson(t));
|
||||
|
// ret = HttpsUtil.httpsRequest(url, "POST", pers.wei.wx.pay.util.JacksonUtil.beanToJson(t));
|
||||
|
// break;
|
||||
|
// }
|
||||
|
// //订单发货
|
||||
|
// case ReceiveSubscriBuyData: {
|
||||
|
// SubscribeEntity<ReceiveSubscriOrderSendData> t = new SubscribeEntity<>();
|
||||
|
// t.setTouser(toUser);
|
||||
|
// t.setTemplate_id(SUBSCRIBE_MSG_ID_ORDERSEND);
|
||||
|
// t.setData(new ReceiveSubscriOrderSendData(params));
|
||||
|
// System.out.println(JacksonUtil.beanToJson(t));
|
||||
|
// ret = httpsRequest(url, "POST", JacksonUtil.beanToJson(t));
|
||||
|
// break;
|
||||
|
// }
|
||||
|
// default: {
|
||||
|
// break;
|
||||
|
// }
|
||||
|
// }
|
||||
|
// //ok: {"errcode":0,"errmsg":"ok"}
|
||||
|
// //err: {"errcode":43101,"errmsg":"user refuse to accept the msg hint: [V_GEdA02503942]"}
|
||||
|
// System.out.println("Send Subscribe Message:" + ret);
|
||||
|
// String errCode = pers.wei.wx.pay.util.JacksonUtil.getJsonValue(ret, "errcode");
|
||||
|
// System.out.println(errCode);
|
||||
|
// return errCode.equals("0");
|
||||
|
// }
|
||||
|
} |
@ -0,0 +1,322 @@ |
|||||
|
package com.ccsens.wechatutil.payutil; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.ccsens.wechatutil.exception.PayException; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.http.client.HttpClient; |
||||
|
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
|
import org.apache.http.client.methods.HttpGet; |
||||
|
import org.apache.http.client.methods.HttpPost; |
||||
|
import org.apache.http.client.utils.URIBuilder; |
||||
|
import org.apache.http.entity.StringEntity; |
||||
|
import org.apache.http.util.EntityUtils; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import java.io.*; |
||||
|
import java.math.BigInteger; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.security.PrivateKey; |
||||
|
import java.security.Signature; |
||||
|
import java.util.Base64; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
/** |
||||
|
* 微信直连商户支付 |
||||
|
* @author :mr.zhangsan |
||||
|
* @date :Created in 2021/6/18 23:58 |
||||
|
* @version v1.0: |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class WxPayDirectUtils { |
||||
|
|
||||
|
//商户支付URL
|
||||
|
private static final String URL_TRANSACTIONS |
||||
|
= "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; |
||||
|
//测试小程序号appid
|
||||
|
public static final String TK_APPID = "wxa26bb3e3f1d2d998"; |
||||
|
//测试商户号
|
||||
|
public static final String TK_MCHID = "1603930405"; |
||||
|
//测试小程序秘钥secret
|
||||
|
public static final String TK_SECRET = "162cc6eb70bc341bc67fea210796be21"; |
||||
|
//商户私钥
|
||||
|
private static PrivateKey privateKey; |
||||
|
//版本>=0.1.5可使用 AutoUpdateCertificatesVerifier 类替代默认的验签器。
|
||||
|
// 它会在构造时自动下载商户对应的微信支付平台证书,并每隔一段时间(默认为1个小时)更新证书。
|
||||
|
private static AutoUpdateCertificatesVerifier verifier = null; |
||||
|
//WechatPayHttpClient
|
||||
|
private static HttpClient httpClient = null; |
||||
|
//测试商户证书存放路径
|
||||
|
private static final String Client_Key_Path = "C:\\home\\fxxt\\cert_tk\\apiclient_key.pem"; |
||||
|
//测试商户v3key
|
||||
|
private static final String DIRECT_V3_Key = "5IDIy173oyjSGPZHuP0lc2V3UqFBO55M"; |
||||
|
//测试证书序列号
|
||||
|
private static final String DIRECT_CERT_SERIAL_NO = "368344414C13EA2ABF3A59A8CF3F31E99ACE0058"; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 支付订单状态 |
||||
|
*/ |
||||
|
public enum PayOrderStatus { |
||||
|
SUCCESS, NOTPAY, CLOSED, REVOKED, USERPAYING, PAYERROR, FAILED, UNKNOWN |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 商户统一下单支付 |
||||
|
* |
||||
|
* 应用ID appid string[1,32] 是 body 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的APPID |
||||
|
* 示例值:wxd678efh567hg6787 |
||||
|
* 直连商户号 mchid string[1,32] 是 body 直连商户的商户号,由微信支付生成并下发。 |
||||
|
* 示例值:1230000109 |
||||
|
* 商品描述 description string[1,127] 是 body 商品描述 |
||||
|
* 示例值:Image形象店-深圳腾大-QQ公仔 |
||||
|
* 商户订单号 out_trade_no string[6,32] 是 body 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 |
||||
|
* 示例值:1217752501201407033233368018 |
||||
|
* 交易结束时间 time_expire string[1,64] 否 body 订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。 |
||||
|
* 示例值:2018-06-08T10:34:56+08:00 |
||||
|
* 附加数据 attach string[1,128] 否 body 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 |
||||
|
* 示例值:自定义数据 |
||||
|
* 通知地址 notify_url string[1,256] 是 |
||||
|
*/ |
||||
|
public static JSONObject transactions(String openid, String outTradeNo, String description, String notifyUrl, int totalFee) throws Exception { |
||||
|
HttpPost httpPost = new HttpPost(URL_TRANSACTIONS); |
||||
|
httpPost.addHeader("Accept", "application/json"); |
||||
|
httpPost.addHeader("Content-type", "application/json; charset=utf-8"); |
||||
|
|
||||
|
JSONObject params = new JSONObject(); |
||||
|
JSONObject amount = new JSONObject(); |
||||
|
JSONObject payer = new JSONObject(); |
||||
|
payer.put("openid", openid); |
||||
|
|
||||
|
amount.put("total", totalFee); |
||||
|
amount.put("currency", "CNY"); |
||||
|
|
||||
|
params.put("appid", TK_APPID); |
||||
|
params.put("mchid", TK_MCHID); |
||||
|
params.put("out_trade_no", outTradeNo); |
||||
|
params.put("description", description); |
||||
|
params.put("notify_url", notifyUrl); |
||||
|
params.put("amount", amount); |
||||
|
params.put("payer", payer); |
||||
|
|
||||
|
httpPost.setEntity(new StringEntity(params.toJSONString(), "UTF-8")); |
||||
|
CloseableHttpResponse response = (CloseableHttpResponse) getWechatPayHttpClient().execute(httpPost); |
||||
|
String bodyAsString = EntityUtils.toString(response.getEntity()); |
||||
|
JSONObject respJson = JSONObject.parseObject(bodyAsString); |
||||
|
|
||||
|
Object prepayId = respJson.get("prepay_id"); |
||||
|
Object code = respJson.get("code"); |
||||
|
|
||||
|
if (code != null) { |
||||
|
throw new Exception(respJson.get("message").toString()); |
||||
|
} |
||||
|
JSONObject result = new JSONObject(); |
||||
|
//根据返回的prepay_id生成发起支付需要的参数签名返回前端
|
||||
|
|
||||
|
result.put("appId", TK_APPID); |
||||
|
result.put("timeStamp", System.currentTimeMillis() / 1000); |
||||
|
result.put("nonceStr", createNonceStr()); |
||||
|
result.put("package", "prepay_id=" + prepayId); |
||||
|
result.put("signType", "RSA"); |
||||
|
|
||||
|
String message = TK_APPID + "\n" |
||||
|
+ result.get("timeStamp") + "\n" |
||||
|
+ result.get("nonceStr") + "\n" |
||||
|
+ result.get("package") + "\n"; |
||||
|
String sign = wxPayReqSign(message.getBytes("utf-8")); |
||||
|
result.put("paySign", sign); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析支付完毕微信通知参数 |
||||
|
* |
||||
|
* signature 头部签名 Wechatpay-Signature |
||||
|
* body json消息 |
||||
|
* @return |
||||
|
* @throws IOException 参数名 变量 类型[长度限制] 必填 描述 |
||||
|
*/ |
||||
|
public static JSONObject rechargeResult(HttpServletRequest request) throws Exception { |
||||
|
log.info("解析支付完毕微信通知参数"); |
||||
|
//一、验证签名
|
||||
|
//1.1获取平台证书
|
||||
|
getVerifier(); |
||||
|
//1.2检查平台证书序列号
|
||||
|
String wepaySerial = request.getHeader("Wechatpay-Serial"); |
||||
|
if (wepaySerial == null) { |
||||
|
throw new PayException(-9, "平台证书序列号为空"); |
||||
|
} |
||||
|
log.info("获取平台证书序列号"); |
||||
|
//获取平台证书序列号
|
||||
|
BigInteger currentSerial = getVerifier().getValidCertificate().getSerialNumber(); |
||||
|
if (StrUtil.isEmpty(wepaySerial) || !wepaySerial.equalsIgnoreCase(currentSerial.toString(16))) { |
||||
|
throw new PayException(-9, "证书序列号不一致"); |
||||
|
} |
||||
|
log.info("3构造验签名串"); |
||||
|
//1.3构造验签名串
|
||||
|
String wepayNonce = request.getHeader("Wechatpay-Nonce"); |
||||
|
String wepayTimestamp = request.getHeader("Wechatpay-Timestamp"); |
||||
|
String wepayBody = null; |
||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream())); |
||||
|
String line = null; |
||||
|
StringBuilder body = new StringBuilder(); |
||||
|
while ((line = br.readLine()) != null) { |
||||
|
body.append(line); |
||||
|
} |
||||
|
if (StrUtil.isEmpty(body.toString())) { |
||||
|
throw new PayException(-9, "body is null"); |
||||
|
} |
||||
|
wepayBody = body.toString(); |
||||
|
|
||||
|
|
||||
|
String message = wepayTimestamp + "\n" |
||||
|
+ wepayNonce + "\n" |
||||
|
+ body.toString() + "\n"; |
||||
|
log.info("获取应答签名"); |
||||
|
//1.4获取应答签名
|
||||
|
String wepySignature = request.getHeader("Wechatpay-Signature"); |
||||
|
|
||||
|
//1.5验证签名
|
||||
|
boolean rr = getVerifier().verify(wepaySerial, |
||||
|
message.getBytes(StandardCharsets.UTF_8),wepySignature); |
||||
|
if (!rr){ |
||||
|
//TODO 签名验证失败抛异常
|
||||
|
} |
||||
|
// String sign = wxPayRespSign(message.getBytes("utf-8"));
|
||||
|
// if (!wepySignature.equals(sign)) {
|
||||
|
// throw new PayException(-9, "验证签名不一致");
|
||||
|
// }
|
||||
|
log.info("解密数据"); |
||||
|
//二、解密数据
|
||||
|
JSONObject jsonObject = JSONObject.parseObject(wepayBody); |
||||
|
JSONObject resource = (JSONObject) jsonObject.get("resource"); |
||||
|
AesUtil aesUtil = new AesUtil(DIRECT_V3_Key.getBytes("utf-8")); |
||||
|
//解密
|
||||
|
String decryptToString = aesUtil.decryptToString( |
||||
|
resource.get("associated_data").toString().getBytes(), |
||||
|
resource.get("nonce").toString().getBytes(), |
||||
|
resource.get("ciphertext").toString()); |
||||
|
JSONObject parseObject = JSONObject.parseObject(decryptToString); |
||||
|
return parseObject; |
||||
|
} |
||||
|
|
||||
|
private static PayOrderStatus getPayOrderStatus(URIBuilder uriBuilder) throws URISyntaxException, IOException { |
||||
|
HttpGet httpGet = new HttpGet(uriBuilder.build()); |
||||
|
httpGet.addHeader("Accept", "application/json"); |
||||
|
|
||||
|
CloseableHttpResponse response = (CloseableHttpResponse) getWechatPayHttpClient().execute(httpGet); |
||||
|
|
||||
|
String bodyAsString = EntityUtils.toString(response.getEntity()); |
||||
|
|
||||
|
JSONObject jsonObject = JSONObject.parseObject(bodyAsString); |
||||
|
log.info("json:{}", jsonObject); |
||||
|
Object trade_state = jsonObject.get("trade_state"); |
||||
|
if (trade_state != null) { |
||||
|
String tradeState = trade_state.toString(); |
||||
|
log.info("PayOrderStatus:{}",tradeState); |
||||
|
if ("SUCCESS".equals(tradeState)) { |
||||
|
return PayOrderStatus.SUCCESS; |
||||
|
} else if ("USERPAYING".equals(tradeState)) { |
||||
|
return PayOrderStatus.USERPAYING; |
||||
|
} else { |
||||
|
return PayOrderStatus.FAILED; |
||||
|
} |
||||
|
} |
||||
|
return PayOrderStatus.UNKNOWN; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 商户订单号查询 |
||||
|
* |
||||
|
* @param transactionId 微信订单号 |
||||
|
* @return |
||||
|
* @throws IOException |
||||
|
* @throws URISyntaxException |
||||
|
*/ |
||||
|
public static PayOrderStatus wechatOrder(String transactionId) throws IOException, URISyntaxException { |
||||
|
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + transactionId + "?mchid=" + TK_MCHID); |
||||
|
return getPayOrderStatus(uriBuilder); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据商户证书,获取私钥 |
||||
|
* |
||||
|
* @return |
||||
|
* @throws FileNotFoundException |
||||
|
*/ |
||||
|
public static PrivateKey getPrivateKey() throws FileNotFoundException { |
||||
|
if (privateKey == null) { |
||||
|
privateKey = PemUtil.loadPrivateKey( |
||||
|
new FileInputStream(Client_Key_Path)); |
||||
|
} |
||||
|
return privateKey; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 依据商户证书私钥进行签名 |
||||
|
* |
||||
|
* @param message |
||||
|
* @return |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public static String wxPayReqSign(byte[] message) throws Exception { |
||||
|
Signature sign = Signature.getInstance("SHA256withRSA"); |
||||
|
sign.initSign(getPrivateKey()); |
||||
|
sign.update(message); |
||||
|
|
||||
|
return Base64.getEncoder().encodeToString(sign.sign()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据微信平台证书获取httpclient |
||||
|
* |
||||
|
* @return |
||||
|
* @throws FileNotFoundException |
||||
|
* @throws UnsupportedEncodingException |
||||
|
*/ |
||||
|
public static HttpClient getWechatPayHttpClient() throws FileNotFoundException, UnsupportedEncodingException { |
||||
|
if (httpClient == null) { |
||||
|
//构造WechatPayHttpClientBuilder
|
||||
|
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() |
||||
|
.withMerchant(TK_MCHID, DIRECT_CERT_SERIAL_NO, getPrivateKey()) |
||||
|
.withValidator(new WechatPay2Validator(getVerifier())); |
||||
|
|
||||
|
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
|
||||
|
httpClient = builder.build(); |
||||
|
} |
||||
|
return httpClient; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 自动获取微信平台证书 |
||||
|
* |
||||
|
* @throws UnsupportedEncodingException |
||||
|
*/ |
||||
|
public static AutoUpdateCertificatesVerifier getVerifier() throws FileNotFoundException { |
||||
|
//不需要传入微信支付证书了
|
||||
|
if (verifier == null) { |
||||
|
verifier = new AutoUpdateCertificatesVerifier( |
||||
|
new WechatPay2Credentials(TK_MCHID, new PrivateKeySigner(DIRECT_CERT_SERIAL_NO, getPrivateKey())), |
||||
|
DIRECT_V3_Key.getBytes(StandardCharsets.UTF_8)); |
||||
|
} |
||||
|
return verifier; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 生成随机字符串 |
||||
|
* |
||||
|
* @return |
||||
|
*/ |
||||
|
public static String createNonceStr() { |
||||
|
return UUID.randomUUID().toString().replace("-", ""); |
||||
|
} |
||||
|
} |
@ -0,0 +1,687 @@ |
|||||
|
package com.ccsens.wechatutil.payutil; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.ccsens.util.HttpsUtil; |
||||
|
import com.ccsens.wechatutil.exception.PayException; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; |
||||
|
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.codec.digest.DigestUtils; |
||||
|
import org.apache.http.client.HttpClient; |
||||
|
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
|
import org.apache.http.client.methods.HttpGet; |
||||
|
import org.apache.http.client.methods.HttpPost; |
||||
|
import org.apache.http.client.utils.URIBuilder; |
||||
|
import org.apache.http.entity.StringEntity; |
||||
|
import org.apache.http.util.EntityUtils; |
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import java.io.*; |
||||
|
import java.math.BigInteger; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.security.PrivateKey; |
||||
|
import java.security.Signature; |
||||
|
import java.util.*; |
||||
|
|
||||
|
/** |
||||
|
* 微信服务商支付 |
||||
|
* @author :mr.zhangsan |
||||
|
* @date :Created in 2021/5/15 22:24 |
||||
|
* @version v1.0: |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class WxPayProviderUtils { |
||||
|
|
||||
|
/** |
||||
|
* 支付订单状态 |
||||
|
*/ |
||||
|
public enum PayOrderStatus { |
||||
|
SUCCESS, NOTPAY, CLOSED, REVOKED, USERPAYING, PAYERROR, FAILED, UNKNOWN |
||||
|
} |
||||
|
|
||||
|
private static final String URL_MERCHANT_PAY |
||||
|
= "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi"; |
||||
|
//退款url
|
||||
|
private static final String URL_REFUND_PAY |
||||
|
= "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; |
||||
|
//查询退款url
|
||||
|
private static final String SEL_URL_REFUND_PAY |
||||
|
= "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; |
||||
|
//付款到零钱url
|
||||
|
private static final String URL_PAYTO_USER |
||||
|
= "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; |
||||
|
//商户支付URL
|
||||
|
private static final String URL_TRANSACTIONS |
||||
|
= "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; |
||||
|
//测试小程序APPID
|
||||
|
private static final String appid = "wxf30402e727f0840a"; |
||||
|
//测试小程序secret
|
||||
|
private static final String secret = "70402290ac83857c7324b0ee881b544a"; |
||||
|
//商户支付
|
||||
|
//服务商API秘钥key
|
||||
|
private static final String KEY = "5a689a2d6b8c4ff499c23d998fde0941"; |
||||
|
// 服务商应用ID(公众号appid)
|
||||
|
private static final String SPAPPID = "wx65d45856040e9a4a"; |
||||
|
// 服务商户号
|
||||
|
private static final String SPMECHID = "1610985721"; |
||||
|
//子商户应用ID
|
||||
|
private static final String SUBAPPID = appid; |
||||
|
// 子商户号
|
||||
|
private static final String SUBMCHID = "1611259929"; |
||||
|
//服务商证书序列号
|
||||
|
private static final String CERT_SERIAL_NO = "1251E728C5B765190B641C9B45B2A18A4DDEC553"; |
||||
|
//微信提现url
|
||||
|
private static final String URL_PAY_TO_USR_WX |
||||
|
= "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; |
||||
|
//服务商证书存放路径
|
||||
|
private static final String Client_Key_Path = "C:\\home\\fxxt\\cert_fws\\apiclient_key.pem"; |
||||
|
//服务商APIv3Key
|
||||
|
private static final String API_V3_Key = "gRcJ8bpi5imIm4qbbRch5iSSD77DFCKY"; |
||||
|
//测试商户证书存放路径
|
||||
|
public static final String PATH_WX_CRET = "C:\\home\\fxxt\\cert_zyc\\apiclient_key.pem"; |
||||
|
//测试商户私钥
|
||||
|
private static PrivateKey privateKey; |
||||
|
//版本>=0.1.5可使用 AutoUpdateCertificatesVerifier 类替代默认的验签器。
|
||||
|
// 它会在构造时自动下载商户对应的微信支付平台证书,并每隔一段时间(默认为1个小时)更新证书。
|
||||
|
private static AutoUpdateCertificatesVerifier verifier = null; |
||||
|
//WechatPayHttpClient
|
||||
|
private static HttpClient httpClient = null; |
||||
|
//加密方式
|
||||
|
private static final String schema = "WECHATPAY2-SHA256-RSA2048"; |
||||
|
|
||||
|
/** |
||||
|
* 私有构造 不允许生成实例, 只允许调用静态方法 |
||||
|
*/ |
||||
|
private WxPayProviderUtils() { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成随机字符串 |
||||
|
* |
||||
|
* @return |
||||
|
*/ |
||||
|
public static String createNonceStr() { |
||||
|
return UUID.randomUUID().toString().replace("-", ""); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 依据商户证书私钥进行签名 |
||||
|
* |
||||
|
* @param message |
||||
|
* @return |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
private static String wxPayReqSign(byte[] message) throws Exception { |
||||
|
Signature sign = Signature.getInstance("SHA256withRSA"); |
||||
|
sign.initSign(getPrivateKey()); |
||||
|
sign.update(message); |
||||
|
|
||||
|
return Base64.getEncoder().encodeToString(sign.sign()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 依据微信支付平台证书(公钥)进行签名 |
||||
|
* |
||||
|
* @param message |
||||
|
* @return |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
private static boolean wxPayRespSign(byte[] message,String signature) throws Exception { |
||||
|
Signature sign = Signature.getInstance("SHA256withRSA"); |
||||
|
sign.initVerify(getVerifier().getValidCertificate()); |
||||
|
sign.update(message); |
||||
|
return sign.verify(Base64.getDecoder().decode(signature)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据商户证书,获取私钥 |
||||
|
* |
||||
|
* @return |
||||
|
* @throws FileNotFoundException |
||||
|
*/ |
||||
|
public static PrivateKey getPrivateKey() throws FileNotFoundException { |
||||
|
if (privateKey == null) { |
||||
|
privateKey = PemUtil.loadPrivateKey( |
||||
|
new FileInputStream(Client_Key_Path)); |
||||
|
} |
||||
|
return privateKey; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 自动获取微信平台证书 |
||||
|
* |
||||
|
* @throws UnsupportedEncodingException |
||||
|
*/ |
||||
|
public static AutoUpdateCertificatesVerifier getVerifier() throws FileNotFoundException { |
||||
|
//不需要传入微信支付证书了
|
||||
|
if (verifier == null) { |
||||
|
verifier = new AutoUpdateCertificatesVerifier( |
||||
|
new WechatPay2Credentials(SPMECHID, new PrivateKeySigner(CERT_SERIAL_NO, getPrivateKey())), |
||||
|
API_V3_Key.getBytes(StandardCharsets.UTF_8)); |
||||
|
} |
||||
|
return verifier; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据微信平台证书获取httpclient |
||||
|
* |
||||
|
* @return |
||||
|
* @throws FileNotFoundException |
||||
|
* @throws UnsupportedEncodingException |
||||
|
*/ |
||||
|
public static HttpClient getWechatPayHttpClient() throws FileNotFoundException, UnsupportedEncodingException { |
||||
|
if (httpClient == null) { |
||||
|
//构造WechatPayHttpClientBuilder
|
||||
|
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() |
||||
|
.withMerchant(SPMECHID, CERT_SERIAL_NO, getPrivateKey()) |
||||
|
.withValidator(new WechatPay2Validator(getVerifier())); |
||||
|
|
||||
|
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
|
||||
|
httpClient = builder.build(); |
||||
|
} |
||||
|
return httpClient; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 服务商统一下单支付 |
||||
|
*/ |
||||
|
public static JSONObject merchantPreparyPay(String openid, String outTradeNo, String description, String notifyUrl, int totalFee) throws Exception { |
||||
|
HttpPost httpPost = new HttpPost(URL_MERCHANT_PAY); |
||||
|
httpPost.addHeader("Accept", "application/json"); |
||||
|
httpPost.addHeader("Content-type", "application/json; charset=utf-8"); |
||||
|
|
||||
|
JSONObject params = new JSONObject(); |
||||
|
JSONObject amount = new JSONObject(); |
||||
|
JSONObject payer = new JSONObject(); |
||||
|
payer.put("sub_openid", openid); |
||||
|
|
||||
|
amount.put("total", totalFee); |
||||
|
amount.put("currency", "CNY"); |
||||
|
|
||||
|
params.put("sp_mchid", SPMECHID); |
||||
|
params.put("sub_mchid", SUBMCHID); |
||||
|
log.info("out_trade_no:{}", outTradeNo); |
||||
|
params.put("out_trade_no", outTradeNo); |
||||
|
params.put("sp_appid", SPAPPID); |
||||
|
params.put("sub_appid", SUBAPPID); |
||||
|
params.put("description", description); |
||||
|
params.put("notify_url", notifyUrl); |
||||
|
params.put("amount", amount); |
||||
|
params.put("payer", payer); |
||||
|
|
||||
|
httpPost.setEntity(new StringEntity(params.toJSONString(), "UTF-8")); |
||||
|
CloseableHttpResponse response = (CloseableHttpResponse) getWechatPayHttpClient().execute(httpPost); |
||||
|
String bodyAsString = EntityUtils.toString(response.getEntity()); |
||||
|
JSONObject respJson = JSONObject.parseObject(bodyAsString); |
||||
|
Object prepayId = respJson.get("prepay_id"); |
||||
|
Object code = respJson.get("code"); |
||||
|
|
||||
|
if (code != null) { |
||||
|
throw new Exception(respJson.get("message").toString()); |
||||
|
} |
||||
|
JSONObject result = new JSONObject(); |
||||
|
//根据返回的prepay_id生成发起支付需要的参数签名返回前端
|
||||
|
|
||||
|
result.put("appId", SUBAPPID); |
||||
|
result.put("timeStamp", System.currentTimeMillis() / 1000); |
||||
|
result.put("nonceStr", createNonceStr()); |
||||
|
result.put("package", "prepay_id=" + prepayId); |
||||
|
result.put("signType", "RSA"); |
||||
|
|
||||
|
log.info("准备签名"); |
||||
|
String message = SUBAPPID + "\n" |
||||
|
+ result.get("timeStamp") + "\n" |
||||
|
+ result.get("nonceStr") + "\n" |
||||
|
+ result.get("package") + "\n"; |
||||
|
String sign = wxPayReqSign(message.getBytes(StandardCharsets.UTF_8)); |
||||
|
log.info("sign:{}", sign); |
||||
|
result.put("paySign", sign); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析支付完毕微信通知参数 |
||||
|
* <p> |
||||
|
* signature 头部签名 Wechatpay-Signature |
||||
|
* body json消息 |
||||
|
* |
||||
|
* @return |
||||
|
* @throws IOException 参数名 变量 类型[长度限制] 必填 描述 |
||||
|
*/ |
||||
|
public JSONObject rechargeResult(HttpServletRequest request) throws Exception { |
||||
|
//一、验证签名
|
||||
|
//1.1获取平台证书
|
||||
|
getVerifier(); |
||||
|
//1.2检查平台证书序列号
|
||||
|
String wepaySerial = request.getHeader("Wechatpay-Serial"); |
||||
|
if (wepaySerial == null) { |
||||
|
throw new PayException(-9, "平台证书序列号为空"); |
||||
|
} |
||||
|
//获取平台证书序列号
|
||||
|
BigInteger currentSerial = getVerifier().getValidCertificate().getSerialNumber(); |
||||
|
log.info("currentSerial:{} " + currentSerial.toString(16)); |
||||
|
log.info("wepaySerial:{} " + wepaySerial); |
||||
|
if (StrUtil.isEmpty(wepaySerial) || !wepaySerial.equalsIgnoreCase(currentSerial.toString(16))) { |
||||
|
throw new PayException(-9, "证书序列号不一致"); |
||||
|
} |
||||
|
//1.3构造验签名串
|
||||
|
String wepayNonce = request.getHeader("Wechatpay-Nonce"); |
||||
|
String wepayTimestamp = request.getHeader("Wechatpay-Timestamp"); |
||||
|
String wepayBody = null; |
||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)); |
||||
|
String line = null; |
||||
|
StringBuilder body = new StringBuilder(); |
||||
|
while ((line = br.readLine()) != null) { |
||||
|
body.append(line); |
||||
|
} |
||||
|
if (StrUtil.isEmpty(body.toString())) { |
||||
|
throw new PayException(-9, "body is null"); |
||||
|
} |
||||
|
wepayBody = body.toString(); |
||||
|
|
||||
|
String message = wepayTimestamp + "\n" |
||||
|
+ wepayNonce + "\n" |
||||
|
+ body.toString() + "\n"; |
||||
|
//1.4获取应答签名
|
||||
|
String wepySignature = request.getHeader("Wechatpay-Signature"); |
||||
|
log.info("----------------1,{}\n----{}", wepySignature, message); |
||||
|
|
||||
|
|
||||
|
//1.5验证签名
|
||||
|
boolean rr = getVerifier().verify(wepaySerial, |
||||
|
message.getBytes(StandardCharsets.UTF_8), wepySignature); |
||||
|
|
||||
|
// boolean rr = wxPayRespSign(message.getBytes("utf-8"), wepySignature);
|
||||
|
if (!rr) { |
||||
|
//TODO 签名验证失败抛异常
|
||||
|
throw new PayException(-9, "验证签名不一致"); |
||||
|
} |
||||
|
log.info("+++++++++++zzc 签名调用+++++++++++++++++++++ "); |
||||
|
//二、解密数据
|
||||
|
JSONObject jsonObject = JSONObject.parseObject(wepayBody); |
||||
|
JSONObject resource = (JSONObject) jsonObject.get("resource"); |
||||
|
AesUtil aesUtil = new AesUtil(API_V3_Key.getBytes("utf-8")); |
||||
|
//解密
|
||||
|
String decryptToString = aesUtil.decryptToString( |
||||
|
resource.get("associated_data").toString().getBytes(), |
||||
|
resource.get("nonce").toString().getBytes(), |
||||
|
resource.get("ciphertext").toString()); |
||||
|
JSONObject parseObject = JSONObject.parseObject(decryptToString); |
||||
|
return parseObject; |
||||
|
} |
||||
|
|
||||
|
private static PayOrderStatus getPayOrderStatus(URIBuilder uriBuilder) throws URISyntaxException, IOException { |
||||
|
HttpGet httpGet = new HttpGet(uriBuilder.build()); |
||||
|
httpGet.addHeader("Accept", "application/json"); |
||||
|
|
||||
|
CloseableHttpResponse response = (CloseableHttpResponse) getWechatPayHttpClient().execute(httpGet); |
||||
|
|
||||
|
String bodyAsString = EntityUtils.toString(response.getEntity()); |
||||
|
|
||||
|
JSONObject jsonObject = JSONObject.parseObject(bodyAsString); |
||||
|
log.info("json:{}", jsonObject); |
||||
|
Object trade_state = jsonObject.get("trade_state"); |
||||
|
if (trade_state != null) { |
||||
|
String tradeState = trade_state.toString(); |
||||
|
if ("SUCCESS".equals(tradeState)) { |
||||
|
return PayOrderStatus.SUCCESS; |
||||
|
} else if ("USERPAYING".equals(tradeState)) { |
||||
|
return PayOrderStatus.USERPAYING; |
||||
|
} else { |
||||
|
return PayOrderStatus.FAILED; |
||||
|
} |
||||
|
} |
||||
|
return PayOrderStatus.UNKNOWN; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 服务商查询订单 |
||||
|
* |
||||
|
* @param orderNo |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public static PayOrderStatus queryOrder(String orderNo) throws IOException, URISyntaxException { |
||||
|
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/id/" + orderNo + "?sp_mchid=" + SPMECHID + "&sub_mchid=" + SUBMCHID); |
||||
|
return getPayOrderStatus(uriBuilder); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 商户订单号查询 |
||||
|
* |
||||
|
* @param transactionId 微信订单号 |
||||
|
* @return |
||||
|
* @throws IOException |
||||
|
* @throws URISyntaxException |
||||
|
*/ |
||||
|
public static PayOrderStatus wechatOrder(String transactionId) throws IOException, URISyntaxException { |
||||
|
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/" + transactionId + "?sp_mchid=" + SPMECHID + "&sub_mchid=" + SUBMCHID); |
||||
|
return getPayOrderStatus(uriBuilder); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 服务商关闭订单 |
||||
|
* |
||||
|
* @param orderNo 商户订单号 |
||||
|
*/ |
||||
|
public static void closeOrder(String orderNo) throws IOException { |
||||
|
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/" + orderNo + "/close"); |
||||
|
httpPost.addHeader("Accept", "application/json"); |
||||
|
httpPost.addHeader("Content-type", "application/json; charset=utf-8"); |
||||
|
|
||||
|
JSONObject params = new JSONObject(); |
||||
|
params.put("sp_mchid", SPMECHID); |
||||
|
params.put("sub_mchid", SUBMCHID); |
||||
|
|
||||
|
httpPost.setEntity(new StringEntity(params.toJSONString(), "UTF-8")); |
||||
|
getWechatPayHttpClient().execute(httpPost); |
||||
|
//接口响应204,无内容
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 服务商退款 |
||||
|
* |
||||
|
* @param refund 退款金额 |
||||
|
* @param outTradeNo 商户订单号 |
||||
|
* @param outRefundNo 商户退款单号 |
||||
|
* @param notifyUrl 退款回调url |
||||
|
* @param total 原订单金额 |
||||
|
* @return |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public static String refunds(int refund, String outTradeNo, String outRefundNo, String notifyUrl, int total) throws Exception { |
||||
|
HttpPost httpPost = new HttpPost(URL_REFUND_PAY); |
||||
|
httpPost.addHeader("Accept", "application/json"); |
||||
|
httpPost.addHeader("Content-type", "application/json; charset=utf-8"); |
||||
|
|
||||
|
JSONObject params = new JSONObject(); |
||||
|
JSONObject amount = new JSONObject(); |
||||
|
|
||||
|
//金额信息
|
||||
|
amount.put("refund", refund); |
||||
|
//原订单金额
|
||||
|
amount.put("total", total); |
||||
|
//退款币种
|
||||
|
amount.put("currency", "CNY"); |
||||
|
//子商户号 否
|
||||
|
params.put("sub_mchid", SUBMCHID); |
||||
|
//// 微信支付订单号 (二选一)
|
||||
|
// params.put("transaction_id", SUBMCHID);
|
||||
|
//商户订单号 (二选一)
|
||||
|
params.put("out_trade_no", outTradeNo); |
||||
|
//商户退款单号
|
||||
|
log.info("商户退款单号----------------------------------------out_trade_no:{}", outRefundNo); |
||||
|
params.put("out_refund_no", outRefundNo); |
||||
|
//退款结果回调url 否
|
||||
|
params.put("notify_url", notifyUrl); |
||||
|
//金额信息
|
||||
|
params.put("amount", amount); |
||||
|
|
||||
|
httpPost.setEntity(new StringEntity(params.toJSONString(), "UTF-8")); |
||||
|
CloseableHttpResponse response = (CloseableHttpResponse) getWechatPayHttpClient().execute(httpPost); |
||||
|
String bodyAsString = EntityUtils.toString(response.getEntity()); |
||||
|
JSONObject respJson = JSONObject.parseObject(bodyAsString); |
||||
|
|
||||
|
log.info("respJson:{}", respJson); |
||||
|
Object result = respJson.get("status"); |
||||
|
String status = result.toString(); |
||||
|
if ("SUCCESS".equals(status) || "PROCESSING".equals(status)) { |
||||
|
return status; |
||||
|
} else { |
||||
|
throw new PayException("[" + respJson.get("err_code") + "]" |
||||
|
+ respJson.get("err_code_des")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询单笔退款 |
||||
|
*/ |
||||
|
public static JSONObject selRechargeResult(String out_refund_no) throws Exception { |
||||
|
HttpGet httpGet = new HttpGet(SEL_URL_REFUND_PAY + "/" + out_refund_no + "?sub_mchid=" + SUBMCHID); |
||||
|
httpGet.addHeader("Accept", "application/json"); |
||||
|
httpGet.addHeader("Content-type", "application/json; charset=utf-8"); |
||||
|
httpGet.addHeader("Wechatpay-Serial", getVerifier().getValidCertificate().getSerialNumber().toString(16)); |
||||
|
|
||||
|
CloseableHttpResponse response = (CloseableHttpResponse) getWechatPayHttpClient().execute(httpGet); |
||||
|
String bodyAsString = EntityUtils.toString(response.getEntity()); |
||||
|
return JSONObject.parseObject(bodyAsString); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 微信退款结果通知 |
||||
|
*/ |
||||
|
public static JSONObject tuiKuanResult(HttpServletRequest request) throws Exception { |
||||
|
//一、验证签名
|
||||
|
//1.1获取平台证书
|
||||
|
getVerifier(); |
||||
|
//1.2检查平台证书序列号
|
||||
|
String wepaySerial = request.getHeader("Wechatpay-Serial"); |
||||
|
if (wepaySerial == null) { |
||||
|
throw new PayException(-9, "平台证书序列号为空"); |
||||
|
} |
||||
|
//获取平台证书序列号
|
||||
|
BigInteger currentSerial = getVerifier().getValidCertificate().getSerialNumber(); |
||||
|
if (StrUtil.isEmpty(wepaySerial) || !wepaySerial.equalsIgnoreCase(currentSerial.toString(16))) { |
||||
|
throw new PayException(-9, "证书序列号不一致"); |
||||
|
} |
||||
|
String wepayNonce = request.getHeader("Wechatpay-Nonce"); |
||||
|
String wepayTimestamp = request.getHeader("Wechatpay-Timestamp"); |
||||
|
String wepayBody = null; |
||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(),StandardCharsets.UTF_8)); |
||||
|
String line = null; |
||||
|
StringBuilder body = new StringBuilder(); |
||||
|
while ((line = br.readLine()) != null) { |
||||
|
body.append(line); |
||||
|
} |
||||
|
if (StrUtil.isEmpty(body.toString())) { |
||||
|
throw new PayException(-9, "body is null"); |
||||
|
} |
||||
|
wepayBody = body.toString(); |
||||
|
|
||||
|
String message = wepayTimestamp + "\n" |
||||
|
+ wepayNonce + "\n" |
||||
|
+ body.toString() + "\n"; |
||||
|
//1.4获取应答签名
|
||||
|
String wepySignature = request.getHeader("Wechatpay-Signature"); |
||||
|
|
||||
|
//1.5验证签名
|
||||
|
boolean rr = getVerifier().verify(wepaySerial, |
||||
|
message.getBytes(StandardCharsets.UTF_8),wepySignature); |
||||
|
if (!rr){ |
||||
|
} |
||||
|
JSONObject jsonObject = JSONObject.parseObject(wepayBody); |
||||
|
JSONObject resource = (JSONObject) jsonObject.get("resource"); |
||||
|
AesUtil aesUtil = new AesUtil(API_V3_Key.getBytes("utf-8")); |
||||
|
//解密
|
||||
|
String decryptToString = aesUtil.decryptToString( |
||||
|
resource.get("associated_data").toString().getBytes(),//附加数据
|
||||
|
resource.get("nonce").toString().getBytes(),//随机串
|
||||
|
resource.get("ciphertext").toString());//数据密文
|
||||
|
JSONObject parseObject = JSONObject.parseObject(decryptToString); |
||||
|
return parseObject; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 企业付款到零钱 |
||||
|
* |
||||
|
* @param partner_trade_no 商户订单号 |
||||
|
* @param openid 用户openid |
||||
|
* @param total_fee 金额 |
||||
|
* @param desc 付款备注 |
||||
|
* @param spbill_create_ip Ip地址 |
||||
|
* @return |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public static Map<String, Object> payToUsrWx( |
||||
|
String partner_trade_no, String openid, int total_fee, String desc, String spbill_create_ip) |
||||
|
throws Exception { |
||||
|
//1.构造请求字符串
|
||||
|
Map<String, Object> reqMap = new HashMap(); |
||||
|
reqMap.put("mch_appid", SUBAPPID); |
||||
|
reqMap.put("mchid", SUBMCHID); |
||||
|
//reqMap.put("device_info",null); //不能添加空字段,Jackson会生成闭包xml <device_info/>导致签名失败
|
||||
|
reqMap.put("nonce_str", createNonceStr()); |
||||
|
reqMap.put("partner_trade_no", partner_trade_no); |
||||
|
reqMap.put("openid", openid); |
||||
|
reqMap.put("check_name", "NO_CHECK"); |
||||
|
//reqMap.put("re_user_name",null);
|
||||
|
reqMap.put("amount", total_fee + ""); |
||||
|
reqMap.put("desc", desc); |
||||
|
reqMap.put("spbill_create_ip", spbill_create_ip); |
||||
|
reqMap.put("sign", createSign(reqMap)); |
||||
|
|
||||
|
//2.发送付款请求
|
||||
|
String reqrXml = JacksonUtil.mapToXml(paraFilterEmpty(reqMap), "xml"); |
||||
|
String respXml = HttpsUtil.httpsRequest(URL_PAY_TO_USR_WX, "POST", reqrXml, |
||||
|
PATH_WX_CRET, SUBMCHID); |
||||
|
System.out.println("---------------PayToUsrWx Request Xml-----------------"); |
||||
|
System.out.println(reqrXml); |
||||
|
System.out.println("---------------PayToUsrWx Response Xml-----------------"); |
||||
|
System.out.println(respXml); |
||||
|
System.out.println("---------------PayToUsrWx end-----------------"); |
||||
|
|
||||
|
//3.判断成功失败
|
||||
|
Map<String, Object> respMap = JacksonUtil.xmlToMap(new ByteArrayInputStream(respXml.getBytes(StandardCharsets.UTF_8))); |
||||
|
String return_code, result_code; |
||||
|
return_code = String.valueOf(respMap.get("return_code")); |
||||
|
result_code = String.valueOf(respMap.get("result_code")); |
||||
|
|
||||
|
if (!StrUtil.isEmpty(return_code) && !StrUtil.isEmpty(result_code)) { |
||||
|
if (return_code.equals("SUCCESS") && return_code.equals(result_code)) { |
||||
|
return respMap; |
||||
|
} else { |
||||
|
throw new PayException("[" + respMap.get("err_code") + "]" |
||||
|
+ respMap.get("err_code_des")); |
||||
|
} |
||||
|
} else { |
||||
|
throw new PayException(String.valueOf(respMap.get("return_msg"))); |
||||
|
} |
||||
|
//return null;
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create Sign |
||||
|
* |
||||
|
* @param sParaTemp |
||||
|
* @return |
||||
|
*/ |
||||
|
private static String createSign(Map<String, Object> sParaTemp) throws FileNotFoundException { |
||||
|
// 除去数组中的空值和签名参数
|
||||
|
Map<String, String> sPara = paraFilter(sParaTemp); |
||||
|
String prestr = createLinkString(sPara); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
|
||||
|
//MD5运算生成签名
|
||||
|
String sign = sign(prestr, getPrivateKey().toString(), "utf-8").toUpperCase(); |
||||
|
// return sign;
|
||||
|
return sign; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 签名字符串 |
||||
|
* |
||||
|
* @param text 需要签名的字符串 |
||||
|
* @param key 密钥 |
||||
|
* @param input_charset 编码格式 |
||||
|
* @return 签名结果 |
||||
|
*/ |
||||
|
private static String sign(String text, String key, String input_charset) { |
||||
|
text = text + "&key=" + key; |
||||
|
return DigestUtils.md5Hex(getContentBytes(text, input_charset)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param content |
||||
|
* @param charset |
||||
|
* @return |
||||
|
* @throws |
||||
|
* @throws UnsupportedEncodingException |
||||
|
*/ |
||||
|
private static byte[] getContentBytes(String content, String charset) { |
||||
|
if (charset == null || "".equals(charset)) { |
||||
|
return content.getBytes(); |
||||
|
} |
||||
|
try { |
||||
|
return content.getBytes(charset); |
||||
|
} catch (UnsupportedEncodingException e) { |
||||
|
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 除去数组中的空值和签名参数 |
||||
|
* |
||||
|
* @param sArray 签名参数组 |
||||
|
* @return 去掉空值与签名参数后的新签名参数组 |
||||
|
*/ |
||||
|
private static Map<String, String> paraFilter(Map<String, Object> sArray) { |
||||
|
Map<String, String> result = new HashMap(); |
||||
|
if (sArray == null || sArray.size() <= 0) { |
||||
|
return result; |
||||
|
} |
||||
|
for (String key : sArray.keySet()) { |
||||
|
Object objVal = sArray.get(key); |
||||
|
if (objVal == null) { |
||||
|
continue; |
||||
|
} |
||||
|
String value = String.valueOf(objVal); |
||||
|
if (StrUtil.isEmpty(value) || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { |
||||
|
continue; |
||||
|
} |
||||
|
result.put(key, value); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 |
||||
|
* |
||||
|
* @param params 需要排序并参与字符拼接的参数组 |
||||
|
* @return 拼接后字符串 |
||||
|
*/ |
||||
|
private static String createLinkString(Map<String, String> params) { |
||||
|
List<String> keys = new ArrayList(params.keySet()); |
||||
|
Collections.sort(keys); |
||||
|
String prestr = ""; |
||||
|
for (int i = 0; i < keys.size(); i++) { |
||||
|
String key = keys.get(i); |
||||
|
String value = params.get(key); |
||||
|
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
|
||||
|
prestr = prestr + key + "=" + value; |
||||
|
} else { |
||||
|
prestr = prestr + key + "=" + value + "&"; |
||||
|
} |
||||
|
} |
||||
|
return prestr; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 除去数组中的空值和签名参数 |
||||
|
* |
||||
|
* @param sArray 签名参数组 |
||||
|
* @return 去掉空值与签名参数后的新签名参数组 |
||||
|
*/ |
||||
|
private static Map<String, String> paraFilterEmpty(Map<String, Object> sArray) { |
||||
|
Map<String, String> result = new HashMap(); |
||||
|
if (sArray == null || sArray.size() <= 0) { |
||||
|
return result; |
||||
|
} |
||||
|
for (String key : sArray.keySet()) { |
||||
|
Object objVal = sArray.get(key); |
||||
|
if (objVal == null) { |
||||
|
continue; |
||||
|
} |
||||
|
String value = String.valueOf(objVal); |
||||
|
result.put(key, value); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
@ -0,0 +1,64 @@ |
|||||
|
package com.ccsens.wechatutil.payutil; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
|
import org.apache.http.client.methods.HttpGet; |
||||
|
import org.apache.http.util.EntityUtils; |
||||
|
|
||||
|
import java.io.FileNotFoundException; |
||||
|
import java.io.IOException; |
||||
|
|
||||
|
/** |
||||
|
* @author :mr.zhangsan |
||||
|
* @date :Created in 2021/4/15 21:11 |
||||
|
* @version v1.0: |
||||
|
*/ |
||||
|
|
||||
|
public class WxTokenUtil { |
||||
|
|
||||
|
//测试APPID
|
||||
|
private static final String appid = "wxf30402e727f0840a"; |
||||
|
//测试secret
|
||||
|
private static final String secret = "70402290ac83857c7324b0ee881b544a"; |
||||
|
// 测试商户号
|
||||
|
private static final String SUBMCHID = "1611259929"; |
||||
|
|
||||
|
/** |
||||
|
* 获取小程序全局唯一后台接口调用凭据(access_token) |
||||
|
*/ |
||||
|
private static final String URL_GETTOKEN_PAY |
||||
|
= "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret; |
||||
|
|
||||
|
public static String getToken() throws Exception{ |
||||
|
|
||||
|
HttpGet httpGet = new HttpGet(URL_GETTOKEN_PAY); |
||||
|
httpGet.addHeader("Accept", "application/json"); |
||||
|
httpGet.addHeader("Content-type", "application/json; charset=utf-8"); |
||||
|
try { |
||||
|
httpGet.addHeader("Wechatpay-Serial", WxPayProviderUtils.getVerifier().getValidCertificate().getSerialNumber().toString(16)); |
||||
|
}catch (FileNotFoundException e) { |
||||
|
e.printStackTrace(); |
||||
|
throw new FileNotFoundException("获取微信平台证书失败"); |
||||
|
} |
||||
|
String bodyAsString = ""; |
||||
|
try { |
||||
|
CloseableHttpResponse response = (CloseableHttpResponse) WxPayProviderUtils.getWechatPayHttpClient().execute(httpGet); |
||||
|
try { |
||||
|
bodyAsString = EntityUtils.toString(response.getEntity()); |
||||
|
}catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
throw new FileNotFoundException("bodyAsString转换错误"); |
||||
|
} |
||||
|
}catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
throw new FileNotFoundException("根据微信平台证书获取httpclient失败"); |
||||
|
} |
||||
|
JSONObject respJson = JSONObject.parseObject(bodyAsString); |
||||
|
Object access_token = respJson.get("access_token"); |
||||
|
if (access_token != null) { |
||||
|
return access_token.toString(); |
||||
|
}else { |
||||
|
throw new Exception("获取WxToken失败"); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,4 +1,12 @@ |
|||||
package com.ccsens.wechatutil.service; |
package com.ccsens.wechatutil.service; |
||||
|
|
||||
|
import com.ccsens.wechatutil.bean.dto.WxMessageDto; |
||||
|
|
||||
public interface IWxMessageService { |
public interface IWxMessageService { |
||||
|
/** |
||||
|
* 群机器人发送消息 |
||||
|
* @param robotMessage |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
void sendRobotInfo(WxMessageDto.RobotMessage robotMessage)throws Exception; |
||||
} |
} |
||||
|
Loading…
Reference in new issue