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; |
|||
|
|||
import com.ccsens.wechatutil.bean.dto.WxMessageDto; |
|||
|
|||
public interface IWxMessageService { |
|||
/** |
|||
* 群机器人发送消息 |
|||
* @param robotMessage |
|||
* @throws Exception |
|||
*/ |
|||
void sendRobotInfo(WxMessageDto.RobotMessage robotMessage)throws Exception; |
|||
} |
|||
|
Loading…
Reference in new issue