@@ -19,3 +19,4 @@ target | |||||
sw-pom.xml | sw-pom.xml | ||||
*.iml | *.iml | ||||
test-config.xml | test-config.xml | ||||
.idea |
@@ -1,5 +1,7 @@ | |||||
package me.chanjar.weixin.common.util.crypto; | package me.chanjar.weixin.common.util.crypto; | ||||
import org.apache.commons.codec.digest.DigestUtils; | |||||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||||
import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
@@ -21,7 +23,7 @@ public class SHA1 { | |||||
for (String a : arr) { | for (String a : arr) { | ||||
sb.append(a); | sb.append(a); | ||||
} | } | ||||
return genStr(sb.toString()); | |||||
return DigestUtils.sha1Hex(sb.toString()); | |||||
} | } | ||||
/** | /** | ||||
@@ -40,25 +42,6 @@ public class SHA1 { | |||||
sb.append('&'); | sb.append('&'); | ||||
} | } | ||||
} | } | ||||
return genStr(sb.toString()); | |||||
} | |||||
public static String genStr(String str) throws NoSuchAlgorithmException { | |||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1"); | |||||
sha1.update(str.getBytes()); | |||||
byte[] output = sha1.digest(); | |||||
return bytesToHex(output); | |||||
return DigestUtils.sha1Hex(sb.toString()); | |||||
} | } | ||||
protected static String bytesToHex(byte[] b) { | |||||
char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', | |||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; | |||||
StringBuffer buf = new StringBuffer(); | |||||
for (int j = 0; j < b.length; j++) { | |||||
buf.append(hexDigit[(b[j] >> 4) & 0x0f]); | |||||
buf.append(hexDigit[b[j] & 0x0f]); | |||||
} | |||||
return buf.toString(); | |||||
} | |||||
} | |||||
} |
@@ -14,6 +14,7 @@ | |||||
package me.chanjar.weixin.common.util.crypto; | package me.chanjar.weixin.common.util.crypto; | ||||
import org.apache.commons.codec.binary.Base64; | import org.apache.commons.codec.binary.Base64; | ||||
import org.apache.commons.codec.digest.DigestUtils; | |||||
import org.w3c.dom.Document; | import org.w3c.dom.Document; | ||||
import org.w3c.dom.Element; | import org.w3c.dom.Element; | ||||
import org.xml.sax.InputSource; | import org.xml.sax.InputSource; | ||||
@@ -27,8 +28,7 @@ import javax.xml.parsers.ParserConfigurationException; | |||||
import java.io.StringReader; | import java.io.StringReader; | ||||
import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||
import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||
import java.util.Arrays; | |||||
import java.util.Random; | |||||
import java.util.*; | |||||
public class WxCryptUtil { | public class WxCryptUtil { | ||||
@@ -224,6 +224,36 @@ public class WxCryptUtil { | |||||
} | } | ||||
/** | |||||
* 微信公众号支付签名算法(详见:http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=4_3) | |||||
* @param packageParams 原始参数 | |||||
* @param signKey 加密Key(即 商户Key) | |||||
* @param charset 编码 | |||||
* @return 签名字符串 | |||||
*/ | |||||
public static String createSign(Map<String, String> packageParams, String signKey) { | |||||
SortedMap<String, String> sortedMap = new TreeMap<String, String>(); | |||||
sortedMap.putAll(packageParams); | |||||
List<String> keys = new ArrayList<String>(packageParams.keySet()); | |||||
Collections.sort(keys); | |||||
StringBuffer toSign = new StringBuffer(); | |||||
for (String key : keys) { | |||||
String value = packageParams.get(key); | |||||
if (null != value && !"".equals(value) && !"sign".equals(key) | |||||
&& !"key".equals(key)) { | |||||
toSign.append(key + "=" + value + "&"); | |||||
} | |||||
} | |||||
toSign.append("key=" + signKey); | |||||
System.out.println(toSign.toString()); | |||||
String sign = DigestUtils.md5Hex(toSign.toString()) | |||||
.toUpperCase(); | |||||
return sign; | |||||
} | |||||
/** | /** | ||||
* 将一个数字转换成生成4个字节的网络字节序bytes数组 | * 将一个数字转换成生成4个字节的网络字节序bytes数组 | ||||
* | * | ||||
@@ -50,6 +50,10 @@ public interface WxMpConfigStorage { | |||||
public String getSecret(); | public String getSecret(); | ||||
public String getPartnerId(); | |||||
public String getPartnerKey(); | |||||
public String getToken(); | public String getToken(); | ||||
public String getAesKey(); | public String getAesKey(); | ||||
@@ -11,6 +11,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
protected volatile String appId; | protected volatile String appId; | ||||
protected volatile String secret; | protected volatile String secret; | ||||
protected volatile String partnerId; | |||||
protected volatile String partnerKey; | |||||
protected volatile String token; | protected volatile String token; | ||||
protected volatile String accessToken; | protected volatile String accessToken; | ||||
protected volatile String aesKey; | protected volatile String aesKey; | ||||
@@ -168,6 +170,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
"appId='" + appId + '\'' + | "appId='" + appId + '\'' + | ||||
", secret='" + secret + '\'' + | ", secret='" + secret + '\'' + | ||||
", token='" + token + '\'' + | ", token='" + token + '\'' + | ||||
", partnerId='" + partnerId + '\'' + | |||||
", partnerKey='" + partnerKey + '\'' + | |||||
", accessToken='" + accessToken + '\'' + | ", accessToken='" + accessToken + '\'' + | ||||
", aesKey='" + aesKey + '\'' + | ", aesKey='" + aesKey + '\'' + | ||||
", expiresTime=" + expiresTime + | ", expiresTime=" + expiresTime + | ||||
@@ -179,4 +183,22 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | ", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | ||||
'}'; | '}'; | ||||
} | } | ||||
@Override | |||||
public String getPartnerId() { | |||||
return partnerId; | |||||
} | |||||
public void setPartnerId(String partnerId) { | |||||
this.partnerId = partnerId; | |||||
} | |||||
@Override | |||||
public String getPartnerKey() { | |||||
return partnerKey; | |||||
} | |||||
public void setPartnerKey(String partnerKey) { | |||||
this.partnerKey = partnerKey; | |||||
} | |||||
} | } |
@@ -11,9 +11,11 @@ import me.chanjar.weixin.mp.bean.result.*; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.math.BigDecimal; | |||||
import java.text.SimpleDateFormat; | import java.text.SimpleDateFormat; | ||||
import java.util.Date; | import java.util.Date; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | |||||
/** | /** | ||||
* 微信API的Service | * 微信API的Service | ||||
@@ -332,6 +334,18 @@ public interface WxMpService { | |||||
*/ | */ | ||||
public WxMpQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException; | public WxMpQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException; | ||||
/** | |||||
* <pre> | |||||
* 换取永久字符串二维码ticket | |||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=生成带参数的二维码 | |||||
* </pre> | |||||
* | |||||
* @param scene_str 参数。字符串类型长度现在为1到64 | |||||
* @return | |||||
* @throws WxErrorException | |||||
*/ | |||||
public WxMpQrCodeTicket qrCodeCreateLastTicket(String scene_str) throws WxErrorException; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 换取二维码图片文件,jpg格式 | * 换取二维码图片文件,jpg格式 | ||||
@@ -528,4 +542,32 @@ public interface WxMpService { | |||||
*/ | */ | ||||
void setMaxRetryTimes(int maxRetryTimes); | void setMaxRetryTimes(int maxRetryTimes); | ||||
/** | |||||
* 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=9_1) | |||||
* 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" | |||||
* @param openId 支付人openId | |||||
* @param outTradeNo 商户端对应订单号 | |||||
* @param amt 金额(单位元) | |||||
* @param body 商品描述 | |||||
* @param tradeType 交易类型 JSAPI,NATIVE,APP,WAP | |||||
* @param ip 发起支付的客户端IP | |||||
* @param notifyUrl 通知地址 | |||||
* @return | |||||
*/ | |||||
WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String notifyUrl); | |||||
/** | |||||
* 该接口调用“统一下单”接口,并拼装JSSDK发起支付请求需要的参数 | |||||
* 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82 | |||||
* @param openId 支付人openId | |||||
* @param outTradeNo 商户端对应订单号 | |||||
* @param amt 金额(单位元) | |||||
* @param body 商品描述 | |||||
* @param tradeType 交易类型 JSAPI,NATIVE,APP,WAP | |||||
* @param ip 发起支付的客户端IP | |||||
* @param notifyUrl 通知地址 | |||||
* @return | |||||
*/ | |||||
Map<String, String> getJSSDKPayInfo(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String notifyUrl); | |||||
} | } |
@@ -6,6 +6,7 @@ import com.google.gson.JsonObject; | |||||
import com.google.gson.internal.Streams; | import com.google.gson.internal.Streams; | ||||
import com.google.gson.reflect.TypeToken; | import com.google.gson.reflect.TypeToken; | ||||
import com.google.gson.stream.JsonReader; | import com.google.gson.stream.JsonReader; | ||||
import com.thoughtworks.xstream.XStream; | |||||
import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
import me.chanjar.weixin.common.bean.WxMenu; | import me.chanjar.weixin.common.bean.WxMenu; | ||||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | import me.chanjar.weixin.common.bean.WxJsapiSignature; | ||||
@@ -17,13 +18,16 @@ import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.common.util.RandomUtils; | import me.chanjar.weixin.common.util.RandomUtils; | ||||
import me.chanjar.weixin.common.util.StringUtils; | import me.chanjar.weixin.common.util.StringUtils; | ||||
import me.chanjar.weixin.common.util.crypto.SHA1; | import me.chanjar.weixin.common.util.crypto.SHA1; | ||||
import me.chanjar.weixin.common.util.crypto.WxCryptUtil; | |||||
import me.chanjar.weixin.common.util.fs.FileUtils; | import me.chanjar.weixin.common.util.fs.FileUtils; | ||||
import me.chanjar.weixin.common.util.http.*; | import me.chanjar.weixin.common.util.http.*; | ||||
import me.chanjar.weixin.common.util.json.GsonHelper; | import me.chanjar.weixin.common.util.json.GsonHelper; | ||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||||
import me.chanjar.weixin.mp.bean.*; | import me.chanjar.weixin.mp.bean.*; | ||||
import me.chanjar.weixin.mp.bean.result.*; | import me.chanjar.weixin.mp.bean.result.*; | ||||
import me.chanjar.weixin.mp.util.http.QrCodeRequestExecutor; | import me.chanjar.weixin.mp.util.http.QrCodeRequestExecutor; | ||||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | ||||
import org.apache.http.Consts; | |||||
import org.apache.http.HttpHost; | import org.apache.http.HttpHost; | ||||
import org.apache.http.auth.AuthScope; | import org.apache.http.auth.AuthScope; | ||||
import org.apache.http.auth.UsernamePasswordCredentials; | import org.apache.http.auth.UsernamePasswordCredentials; | ||||
@@ -32,6 +36,8 @@ import org.apache.http.client.CredentialsProvider; | |||||
import org.apache.http.client.config.RequestConfig; | import org.apache.http.client.config.RequestConfig; | ||||
import org.apache.http.client.methods.CloseableHttpResponse; | import org.apache.http.client.methods.CloseableHttpResponse; | ||||
import org.apache.http.client.methods.HttpGet; | import org.apache.http.client.methods.HttpGet; | ||||
import org.apache.http.client.methods.HttpPost; | |||||
import org.apache.http.entity.StringEntity; | |||||
import org.apache.http.impl.client.BasicCredentialsProvider; | import org.apache.http.impl.client.BasicCredentialsProvider; | ||||
import org.apache.http.impl.client.BasicResponseHandler; | import org.apache.http.impl.client.BasicResponseHandler; | ||||
import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
@@ -43,10 +49,9 @@ import java.io.File; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.io.StringReader; | import java.io.StringReader; | ||||
import java.math.BigDecimal; | |||||
import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||
import java.util.Date; | |||||
import java.util.List; | |||||
import java.util.UUID; | |||||
import java.util.*; | |||||
public class WxMpServiceImpl implements WxMpService { | public class WxMpServiceImpl implements WxMpService { | ||||
@@ -332,6 +337,19 @@ public class WxMpServiceImpl implements WxMpService { | |||||
String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString()); | String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString()); | ||||
return WxMpQrCodeTicket.fromJson(responseContent); | return WxMpQrCodeTicket.fromJson(responseContent); | ||||
} | } | ||||
public WxMpQrCodeTicket qrCodeCreateLastTicket(String scene_str) throws WxErrorException { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create"; | |||||
JsonObject json = new JsonObject(); | |||||
json.addProperty("action_name", "QR_LIMIT_STR_SCENE"); | |||||
JsonObject actionInfo = new JsonObject(); | |||||
JsonObject scene = new JsonObject(); | |||||
scene.addProperty("scene_str", scene_str); | |||||
actionInfo.add("scene", scene); | |||||
json.add("action_info", actionInfo); | |||||
String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString()); | |||||
return WxMpQrCodeTicket.fromJson(responseContent); | |||||
} | |||||
public File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException { | public File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException { | ||||
String url = "https://mp.weixin.qq.com/cgi-bin/showqrcode"; | String url = "https://mp.weixin.qq.com/cgi-bin/showqrcode"; | ||||
@@ -613,4 +631,77 @@ public class WxMpServiceImpl implements WxMpService { | |||||
this.maxRetryTimes = maxRetryTimes; | this.maxRetryTimes = maxRetryTimes; | ||||
} | } | ||||
@Override | |||||
public WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String callbackUrl) { | |||||
String nonce_str = System.currentTimeMillis() + ""; | |||||
SortedMap<String, String> packageParams = new TreeMap<String, String>(); | |||||
packageParams.put("appid", wxMpConfigStorage.getAppId()); | |||||
packageParams.put("mch_id", wxMpConfigStorage.getPartnerId()); | |||||
packageParams.put("nonce_str", nonce_str); | |||||
packageParams.put("body", body); | |||||
packageParams.put("out_trade_no", outTradeNo); | |||||
packageParams.put("total_fee", (int)(amt*100) + ""); | |||||
packageParams.put("spbill_create_ip", ip); | |||||
packageParams.put("notify_url", callbackUrl); | |||||
packageParams.put("trade_type", tradeType); | |||||
packageParams.put("openid", openId); | |||||
String sign = WxCryptUtil.createSign(packageParams, wxMpConfigStorage.getPartnerKey()); | |||||
String xml = "<xml>" + | |||||
"<appid>" + wxMpConfigStorage.getAppId() + "</appid>" + | |||||
"<mch_id>" + wxMpConfigStorage.getPartnerId() + "</mch_id>" + | |||||
"<nonce_str>" + nonce_str + "</nonce_str>" + | |||||
"<sign>" + sign + "</sign>" + | |||||
"<body><![CDATA[" + body + "]]></body>" + | |||||
"<out_trade_no>" + outTradeNo + "</out_trade_no>" + | |||||
"<total_fee>" + packageParams.get("total_fee") + "</total_fee>" + | |||||
"<spbill_create_ip>" + ip + "</spbill_create_ip>" + | |||||
"<notify_url>" + callbackUrl + "</notify_url>" + | |||||
"<trade_type>" + tradeType + "</trade_type>" + | |||||
"<openid>" + openId + "</openid>" + | |||||
"</xml>"; | |||||
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder"); | |||||
if (httpProxy != null) { | |||||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
httpPost.setConfig(config); | |||||
} | |||||
StringEntity entity = new StringEntity(xml, Consts.UTF_8); | |||||
httpPost.setEntity(entity); | |||||
try { | |||||
CloseableHttpResponse response = httpClient.execute(httpPost); | |||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.alias("xml", WxMpPrepayIdResult.class); | |||||
WxMpPrepayIdResult wxMpPrepayIdResult = (WxMpPrepayIdResult) xstream.fromXML(responseContent); | |||||
return wxMpPrepayIdResult; | |||||
} catch (IOException e) { | |||||
e.printStackTrace(); | |||||
} | |||||
return new WxMpPrepayIdResult(); | |||||
} | |||||
@Override | |||||
public Map<String, String> getJSSDKPayInfo(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String callbackUrl) { | |||||
WxMpPrepayIdResult wxMpPrepayIdResult = getPrepayId(openId, outTradeNo, amt, body, tradeType, ip, callbackUrl); | |||||
String prepayId = wxMpPrepayIdResult.getPrepay_id(); | |||||
if (prepayId == null || prepayId.equals("")) { | |||||
throw new RuntimeException("get prepayid error"); | |||||
} | |||||
Map<String, String> payInfo = new HashMap<String, String>(); | |||||
payInfo.put("appId", wxMpConfigStorage.getAppId()); | |||||
// 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 | |||||
payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); | |||||
payInfo.put("nonceStr", System.currentTimeMillis() + ""); | |||||
payInfo.put("package", "prepay_id=" + prepayId); | |||||
payInfo.put("signType", "MD5"); | |||||
String finalSign = WxCryptUtil.createSign(payInfo, wxMpConfigStorage.getPartnerKey()); | |||||
payInfo.put("sign", finalSign); | |||||
return payInfo; | |||||
} | |||||
} | } |
@@ -0,0 +1,98 @@ | |||||
package me.chanjar.weixin.mp.bean.result; | |||||
import java.io.Serializable; | |||||
/** | |||||
* <pre> | |||||
* 群发消息一发送就返回的结果 | |||||
* | |||||
* 真正的群发消息是否发送成功要看 | |||||
* http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口#.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81.E7.BE.A4.E5.8F.91.E7.BB.93.E6.9E.9C | |||||
* | |||||
* </pre> | |||||
* | |||||
* @author chanjarster | |||||
*/ | |||||
public class WxMpPrepayIdResult implements Serializable { | |||||
private String return_code; | |||||
private String return_msg; | |||||
private String appid; | |||||
private String mch_id; | |||||
private String nonce_str; | |||||
private String sign; | |||||
private String result_code; | |||||
private String prepay_id; | |||||
private String trade_type; | |||||
public String getReturn_code() { | |||||
return return_code; | |||||
} | |||||
public void setReturn_code(String return_code) { | |||||
this.return_code = return_code; | |||||
} | |||||
public String getReturn_msg() { | |||||
return return_msg; | |||||
} | |||||
public void setReturn_msg(String return_msg) { | |||||
this.return_msg = return_msg; | |||||
} | |||||
public String getAppid() { | |||||
return appid; | |||||
} | |||||
public void setAppid(String appid) { | |||||
this.appid = appid; | |||||
} | |||||
public String getMch_id() { | |||||
return mch_id; | |||||
} | |||||
public void setMch_id(String mch_id) { | |||||
this.mch_id = mch_id; | |||||
} | |||||
public String getNonce_str() { | |||||
return nonce_str; | |||||
} | |||||
public void setNonce_str(String nonce_str) { | |||||
this.nonce_str = nonce_str; | |||||
} | |||||
public String getSign() { | |||||
return sign; | |||||
} | |||||
public void setSign(String sign) { | |||||
this.sign = sign; | |||||
} | |||||
public String getResult_code() { | |||||
return result_code; | |||||
} | |||||
public void setResult_code(String result_code) { | |||||
this.result_code = result_code; | |||||
} | |||||
public String getPrepay_id() { | |||||
return prepay_id; | |||||
} | |||||
public void setPrepay_id(String prepay_id) { | |||||
this.prepay_id = prepay_id; | |||||
} | |||||
public String getTrade_type() { | |||||
return trade_type; | |||||
} | |||||
public void setTrade_type(String trade_type) { | |||||
this.trade_type = trade_type; | |||||
} | |||||
} |