package com.kk.wechat.client;
import com.kk.util.SignUtils;
import com.kk.util.XmlUtil;
import com.kk.wechat.annotation.ApiRequestField;
import com.kk.wechat.annotation.ApiResponseField;
import com.kk.wechat.exception.WechatPayException;
import com.kk.wechat.model.SignatureItem;
import com.kk.wechat.model.WechatPayModel;
import com.kk.wechat.model.WechatPayTradeStatus;
import com.kk.wechat.request.WechatPayPrePayRequest;
import com.kk.wechat.request.WechatPayRequest;
import com.kk.wechat.response.WechatPayPrePayResponse;
import com.kk.wechat.response.WechatPayResponse;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.reflect.FieldUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import javax.net.ssl.SSLContext;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.KeyStore;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
/**
* 微信支付, 分装的客户端, 对request自动加上签名,转成xml, 发送请求到微信支付, 会返回结果做解析
*/
public class WechatPayClient {
private Log logger = LogFactory.getLog(this.getClass());
private static final String SERVER_URL = "https://api.mch.weixin.qq.com";
private String appId; // 微信为公众账号Id
private String mchId; // 微信支付 商户号Id
private String apiKey;// 秘钥,用于签名
private byte[] certFile;// 退款时候 数字证书
public WechatPayClient(String apiKey) {
this.apiKey = apiKey;
}
public WechatPayClient(String appId, String mchId, String apiKey) {
this.appId = appId;
this.mchId = mchId;
this.apiKey = apiKey;
}
public WechatPayClient(String appId, String mchId, String apiKey, byte[] certFile) {
this.appId = appId;
this.mchId = mchId;
this.apiKey = apiKey;
this.certFile = certFile;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getMchId() {
return mchId;
}
public void setMchId(String mchId) {
this.mchId = mchId;
}
public byte[] getCertFile() {
return certFile;
}
public void setCertFile(byte[] certFile) {
this.certFile = certFile;
}
public <T extends WechatPayResponse> T execute(WechatPayRequest<? extends WechatPayModel, T> request) throws WechatPayException {
String rsp = post(SERVER_URL + request.getApiAction(), request);
logger.info("wechat.pay.rsp=" + rsp);
T response = parse(request.getResponseClass(), rsp);
return response;
}
/**
* 获取 返回值中的sign,以及计算sign的字符串
*
* @param rsp
* @return
*/
private SignatureItem getSignatureItem(String rsp) {
Map<String, String> data = XmlUtil.parseXml(rsp);
if (data == null || data.isEmpty()) {
return null;
}
if (StringUtils.isBlank(data.get("sign"))) {
return null;
}
SignatureItem signatureItem = new SignatureItem();
signatureItem.setSign(data.get("sign"));
signatureItem.setSignContent(SignUtils.getSignContent(data, true));
return signatureItem;
}
/**
* 根据返回值 解析成responseModel,校验签名
*
* @param clazz
* @param rsp
* @param <T>
* @return
* @throws WechatPayException
*/
public <T extends WechatPayResponse> T parse(Class<T> clazz, String rsp) throws WechatPayException {
Map<String, String> data = XmlUtil.parseXml(rsp);
T response = convert(clazz, data);
if (response == null) {
throw new WechatPayException("微信支付 解析结果失败!");
}
SignatureItem signItem = getSignatureItem(rsp);
if (signItem == null) {
throw new WechatPayException(response.getReturnCode(), response.getReturnMsg());
}
if (response.isSuccess() || (!response.isSuccess() && !StringUtils.isEmpty(signItem.getSign()))) {
boolean checkContent = SignUtils.checkMd5Sign(signItem.getSignContent(), signItem.getSign(), this.apiKey);
if (!checkContent) {
throw new WechatPayException("sign check fail: check Sign and Data Fail!");
}
}
return response;
}
/**
* 根据返回值 解析成responseModel
*
* @param clazz
* @param data
* @param <T>
* @return
* @throws WechatPayException
*/
public static <T> T convert(Class<T> clazz, Map<String, String> data) throws WechatPayException {
T rsp = null;
try {
rsp = clazz.newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
Method writeMethod = pd.getWriteMethod();
if (writeMethod == null) { // ignore read-only fields
continue;
}
String itemName = pd.getName();
Field field = FieldUtils.getField(clazz, itemName, true);
ApiResponseField apiField = field.getAnnotation(ApiResponseField.class);
if (apiField == null) {
continue;
}
String value = data.get(apiField.value());
if (value == null) {
continue;
}
Class<?> typeClass = field.getType();
if (String.class.isAssignableFrom(typeClass)) {
writeMethod.invoke(rsp, value.toString());
} else if (Long.class.isAssignableFrom(typeClass)) {
if (StringUtils.isNumeric(value)) {
writeMethod.invoke(rsp, Long.valueOf(value.toString()));
}
} else if (Integer.class.isAssignableFrom(typeClass)) {
if (StringUtils.isNumeric(value)) {
writeMethod.invoke(rsp, Integer.valueOf(value.toString()));
}
} else if (Boolean.class.isAssignableFrom(typeClass)) {
if (value != null) {
writeMethod.invoke(rsp, Boolean.valueOf(value.toString()));
}
} else if (Date.class.isAssignableFrom(typeClass)) {
DateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
writeMethod.invoke(rsp, format.parse(value.toString()));
} else if (WechatPayTradeStatus.class.isAssignableFrom(typeClass)) {
if (StringUtils.isNotBlank(value)) {
writeMethod.invoke(rsp, WechatPayTradeStatus.valueOf(value));
}
} else {
if (StringUtils.isNotBlank(value)) {
writeMethod.invoke(rsp, value);
}
}
}
} catch (Exception e) {
throw new WechatPayException(e);
}
return rsp;
}
/**
* 发起微信支付请求
*
* @param url
* @param request
* @param <T>
* @return
* @throws WechatPayException
*/
private <T extends WechatPayResponse> String post(String url, WechatPayRequest<? extends WechatPayModel, T> request) throws WechatPayException {
WechatPayModel model = request.getModel();
model.setAppId(appId);
model.setMchId(mchId);
model.setNonceStr(RandomStringUtils.random(32, true, true));
Map<String, String> data = convert(model);
logger.info(String.format("wechat.pay.url=%s,request=%s", url, data));
String sign = SignUtils.md5(data, apiKey);
model.setSign(sign);
data.put("sign", sign);
validate(model);
String text = "";
CloseableHttpClient client = this.getClient(request); //判断是否需要带上 支付证书
try {
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
StringEntity payload = new StringEntity(XmlUtil.toXml(data), "UTF-8");
httpPost.setEntity(payload);
text = client.execute(httpPost, new ResponseHandler<String>() {
@Override
public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
StringBuilder builder = new StringBuilder();
HttpEntity entity = response.getEntity();
String text;
if (entity != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
while ((text = bufferedReader.readLine()) != null) {
builder.append(text);
}
}
return builder.toString();
}
});
} catch (Exception e) {
throw new WechatPayException(e);
} finally {
try {
client.close();
} catch (IOException e) {
throw new WechatPayException(e);
}
}
return text;
}
//判断是否需要带上 支付证书
private <T extends WechatPayResponse> CloseableHttpClient getClient(WechatPayRequest<? extends WechatPayModel, T> request) throws WechatPayException {
CloseableHttpClient client;
if (request.requireCert()) {
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
ByteArrayInputStream inputStream = new ByteArrayInputStream(certFile);
try {
keyStore.load(inputStream, this.mchId.toCharArray());
} finally {
inputStream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, this.mchId.toCharArray()).build();
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
client = HttpClients.custom().setSSLSocketFactory(factory).build();
} catch (Exception e) {
throw new WechatPayException(e);
}
} else {
client = HttpClients.createDefault();
}
return client;
}
// 校验 支付参数
private void validate(WechatPayModel model) throws WechatPayException {
try {
Class clazz = model.getClass();
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (pd.getReadMethod() == null || pd.getWriteMethod() == null) {
continue;
}
String fieldName = pd.getName();
Field field = FieldUtils.getField(clazz, fieldName, true);
ApiRequestField requestField = field.getAnnotation(ApiRequestField.class);
if (requestField == null) {
continue;
}
if (requestField.required()) {
Object value = pd.getReadMethod().invoke(model);
if (value == null) {
throw new WechatPayException(fieldName + " can not be empty");
}
if (value instanceof String) {
if (StringUtils.isBlank((String) value)) {
throw new WechatPayException(fieldName + " can not be empty");
}
}
}
}
} catch (Exception e) {
throw new WechatPayException(e);
}
}
// 将支付参数 class转成map格式
private Map<String, String> convert(WechatPayModel model) throws WechatPayException {
Map<String, String> data = new HashMap<String, String>();
try {
Class clazz = model.getClass();
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (pd.getReadMethod() == null || pd.getWriteMethod() == null) {
continue;
}
String fieldName = pd.getName();
Field field = FieldUtils.getField(clazz, fieldName, true);
ApiRequestField requestField = field.getAnnotation(ApiRequestField.class);
if (requestField == null) {
continue;
}
Object value = pd.getReadMethod().invoke(model);
String strValue;
if (value == null) {
continue;
} else if (value instanceof String) {
if (StringUtils.isBlank((String) value)) {
continue;
}
strValue = (String) value;
} else if (value instanceof Integer) {
strValue = ((Integer) value).toString();
} else if (value instanceof Long) {
strValue = ((Long) value).toString();
} else if (value instanceof Float) {
strValue = ((Float) value).toString();
} else if (value instanceof Double) {
strValue = ((Double) value).toString();
} else if (value instanceof Boolean) {
strValue = ((Boolean) value).toString();
} else if (value instanceof Date) {
DateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
strValue = format.format((Date) value);
} else {
strValue = value.toString();
}
data.put(requestField.value(), strValue);
}
} catch (Exception e) {
throw new WechatPayException(e);
}
return data;
}
}