Skip to content

Commit 214b969

Browse files
committed
Merge pull request #135 from oohusl/develop
公众号支付 @oohusl
2 parents b311099 + 5aba724 commit 214b969

File tree

8 files changed

+298
-27
lines changed

8 files changed

+298
-27
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ target
1919
sw-pom.xml
2020
*.iml
2121
test-config.xml
22+
.idea

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package me.chanjar.weixin.common.util.crypto;
22

3+
import org.apache.commons.codec.digest.DigestUtils;
4+
35
import java.security.MessageDigest;
46
import java.security.NoSuchAlgorithmException;
57
import java.util.Arrays;
@@ -21,7 +23,7 @@ public static String gen(String... arr) throws NoSuchAlgorithmException {
2123
for (String a : arr) {
2224
sb.append(a);
2325
}
24-
return genStr(sb.toString());
26+
return DigestUtils.sha1Hex(sb.toString());
2527
}
2628

2729
/**
@@ -40,25 +42,6 @@ public static String genWithAmple(String... arr) throws NoSuchAlgorithmException
4042
sb.append('&');
4143
}
4244
}
43-
return genStr(sb.toString());
44-
}
45-
46-
public static String genStr(String str) throws NoSuchAlgorithmException {
47-
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
48-
sha1.update(str.getBytes());
49-
byte[] output = sha1.digest();
50-
return bytesToHex(output);
45+
return DigestUtils.sha1Hex(sb.toString());
5146
}
52-
53-
protected static String bytesToHex(byte[] b) {
54-
char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7',
55-
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
56-
StringBuffer buf = new StringBuffer();
57-
for (int j = 0; j < b.length; j++) {
58-
buf.append(hexDigit[(b[j] >> 4) & 0x0f]);
59-
buf.append(hexDigit[b[j] & 0x0f]);
60-
}
61-
return buf.toString();
62-
}
63-
64-
}
47+
}

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package me.chanjar.weixin.common.util.crypto;
1515

1616
import org.apache.commons.codec.binary.Base64;
17+
import org.apache.commons.codec.digest.DigestUtils;
1718
import org.w3c.dom.Document;
1819
import org.w3c.dom.Element;
1920
import org.xml.sax.InputSource;
@@ -27,8 +28,7 @@
2728
import java.io.StringReader;
2829
import java.nio.charset.Charset;
2930
import java.security.NoSuchAlgorithmException;
30-
import java.util.Arrays;
31-
import java.util.Random;
31+
import java.util.*;
3232

3333
public class WxCryptUtil {
3434

@@ -224,6 +224,36 @@ public String decrypt(String cipherText) {
224224

225225
}
226226

227+
/**
228+
* 微信公众号支付签名算法(详见:http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=4_3)
229+
* @param packageParams 原始参数
230+
* @param signKey 加密Key(即 商户Key)
231+
* @param charset 编码
232+
* @return 签名字符串
233+
*/
234+
public static String createSign(Map<String, String> packageParams, String signKey) {
235+
SortedMap<String, String> sortedMap = new TreeMap<String, String>();
236+
sortedMap.putAll(packageParams);
237+
238+
List<String> keys = new ArrayList<String>(packageParams.keySet());
239+
Collections.sort(keys);
240+
241+
242+
StringBuffer toSign = new StringBuffer();
243+
for (String key : keys) {
244+
String value = packageParams.get(key);
245+
if (null != value && !"".equals(value) && !"sign".equals(key)
246+
&& !"key".equals(key)) {
247+
toSign.append(key + "=" + value + "&");
248+
}
249+
}
250+
toSign.append("key=" + signKey);
251+
System.out.println(toSign.toString());
252+
String sign = DigestUtils.md5Hex(toSign.toString())
253+
.toUpperCase();
254+
return sign;
255+
}
256+
227257
/**
228258
* 将一个数字转换成生成4个字节的网络字节序bytes数组
229259
*

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public interface WxMpConfigStorage {
5050

5151
public String getSecret();
5252

53+
public String getPartnerId();
54+
55+
public String getPartnerKey();
56+
5357
public String getToken();
5458

5559
public String getAesKey();

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
1111

1212
protected volatile String appId;
1313
protected volatile String secret;
14+
protected volatile String partnerId;
15+
protected volatile String partnerKey;
1416
protected volatile String token;
1517
protected volatile String accessToken;
1618
protected volatile String aesKey;
@@ -168,6 +170,8 @@ public String toString() {
168170
"appId='" + appId + '\'' +
169171
", secret='" + secret + '\'' +
170172
", token='" + token + '\'' +
173+
", partnerId='" + partnerId + '\'' +
174+
", partnerKey='" + partnerKey + '\'' +
171175
", accessToken='" + accessToken + '\'' +
172176
", aesKey='" + aesKey + '\'' +
173177
", expiresTime=" + expiresTime +
@@ -179,4 +183,22 @@ public String toString() {
179183
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' +
180184
'}';
181185
}
186+
187+
@Override
188+
public String getPartnerId() {
189+
return partnerId;
190+
}
191+
192+
public void setPartnerId(String partnerId) {
193+
this.partnerId = partnerId;
194+
}
195+
196+
@Override
197+
public String getPartnerKey() {
198+
return partnerKey;
199+
}
200+
201+
public void setPartnerKey(String partnerKey) {
202+
this.partnerKey = partnerKey;
203+
}
182204
}

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
import java.io.File;
1212
import java.io.IOException;
1313
import java.io.InputStream;
14+
import java.math.BigDecimal;
1415
import java.text.SimpleDateFormat;
1516
import java.util.Date;
1617
import java.util.List;
18+
import java.util.Map;
1719

1820
/**
1921
* 微信API的Service
@@ -332,6 +334,18 @@ public interface WxMpService {
332334
*/
333335
public WxMpQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException;
334336

337+
/**
338+
* <pre>
339+
* 换取永久字符串二维码ticket
340+
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=生成带参数的二维码
341+
* </pre>
342+
*
343+
* @param scene_str 参数。字符串类型长度现在为1到64
344+
* @return
345+
* @throws WxErrorException
346+
*/
347+
public WxMpQrCodeTicket qrCodeCreateLastTicket(String scene_str) throws WxErrorException;
348+
335349
/**
336350
* <pre>
337351
* 换取二维码图片文件,jpg格式
@@ -528,4 +542,32 @@ public interface WxMpService {
528542
*/
529543
void setMaxRetryTimes(int maxRetryTimes);
530544

545+
/**
546+
* 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=9_1)
547+
* 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"
548+
* @param openId 支付人openId
549+
* @param outTradeNo 商户端对应订单号
550+
* @param amt 金额(单位元)
551+
* @param body 商品描述
552+
* @param tradeType 交易类型 JSAPI,NATIVE,APP,WAP
553+
* @param ip 发起支付的客户端IP
554+
* @param notifyUrl 通知地址
555+
* @return
556+
*/
557+
WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String notifyUrl);
558+
559+
/**
560+
* 该接口调用“统一下单”接口,并拼装JSSDK发起支付请求需要的参数
561+
* 详见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
562+
* @param openId 支付人openId
563+
* @param outTradeNo 商户端对应订单号
564+
* @param amt 金额(单位元)
565+
* @param body 商品描述
566+
* @param tradeType 交易类型 JSAPI,NATIVE,APP,WAP
567+
* @param ip 发起支付的客户端IP
568+
* @param notifyUrl 通知地址
569+
* @return
570+
*/
571+
Map<String, String> getJSSDKPayInfo(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String notifyUrl);
572+
531573
}

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.google.gson.internal.Streams;
77
import com.google.gson.reflect.TypeToken;
88
import com.google.gson.stream.JsonReader;
9+
import com.thoughtworks.xstream.XStream;
910
import me.chanjar.weixin.common.bean.WxAccessToken;
1011
import me.chanjar.weixin.common.bean.WxMenu;
1112
import me.chanjar.weixin.common.bean.WxJsapiSignature;
@@ -17,13 +18,16 @@
1718
import me.chanjar.weixin.common.util.RandomUtils;
1819
import me.chanjar.weixin.common.util.StringUtils;
1920
import me.chanjar.weixin.common.util.crypto.SHA1;
21+
import me.chanjar.weixin.common.util.crypto.WxCryptUtil;
2022
import me.chanjar.weixin.common.util.fs.FileUtils;
2123
import me.chanjar.weixin.common.util.http.*;
2224
import me.chanjar.weixin.common.util.json.GsonHelper;
25+
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
2326
import me.chanjar.weixin.mp.bean.*;
2427
import me.chanjar.weixin.mp.bean.result.*;
2528
import me.chanjar.weixin.mp.util.http.QrCodeRequestExecutor;
2629
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
30+
import org.apache.http.Consts;
2731
import org.apache.http.HttpHost;
2832
import org.apache.http.auth.AuthScope;
2933
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -32,6 +36,8 @@
3236
import org.apache.http.client.config.RequestConfig;
3337
import org.apache.http.client.methods.CloseableHttpResponse;
3438
import org.apache.http.client.methods.HttpGet;
39+
import org.apache.http.client.methods.HttpPost;
40+
import org.apache.http.entity.StringEntity;
3541
import org.apache.http.impl.client.BasicCredentialsProvider;
3642
import org.apache.http.impl.client.BasicResponseHandler;
3743
import org.apache.http.impl.client.CloseableHttpClient;
@@ -43,10 +49,9 @@
4349
import java.io.IOException;
4450
import java.io.InputStream;
4551
import java.io.StringReader;
52+
import java.math.BigDecimal;
4653
import java.security.NoSuchAlgorithmException;
47-
import java.util.Date;
48-
import java.util.List;
49-
import java.util.UUID;
54+
import java.util.*;
5055

5156
public class WxMpServiceImpl implements WxMpService {
5257

@@ -332,6 +337,19 @@ public WxMpQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorExcep
332337
String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString());
333338
return WxMpQrCodeTicket.fromJson(responseContent);
334339
}
340+
341+
public WxMpQrCodeTicket qrCodeCreateLastTicket(String scene_str) throws WxErrorException {
342+
String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create";
343+
JsonObject json = new JsonObject();
344+
json.addProperty("action_name", "QR_LIMIT_STR_SCENE");
345+
JsonObject actionInfo = new JsonObject();
346+
JsonObject scene = new JsonObject();
347+
scene.addProperty("scene_str", scene_str);
348+
actionInfo.add("scene", scene);
349+
json.add("action_info", actionInfo);
350+
String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString());
351+
return WxMpQrCodeTicket.fromJson(responseContent);
352+
}
335353

336354
public File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException {
337355
String url = "https://mp.weixin.qq.com/cgi-bin/showqrcode";
@@ -613,4 +631,77 @@ public void setMaxRetryTimes(int maxRetryTimes) {
613631
this.maxRetryTimes = maxRetryTimes;
614632
}
615633

634+
@Override
635+
public WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String callbackUrl) {
636+
String nonce_str = System.currentTimeMillis() + "";
637+
638+
SortedMap<String, String> packageParams = new TreeMap<String, String>();
639+
packageParams.put("appid", wxMpConfigStorage.getAppId());
640+
packageParams.put("mch_id", wxMpConfigStorage.getPartnerId());
641+
packageParams.put("nonce_str", nonce_str);
642+
packageParams.put("body", body);
643+
packageParams.put("out_trade_no", outTradeNo);
644+
645+
packageParams.put("total_fee", (int)(amt*100) + "");
646+
packageParams.put("spbill_create_ip", ip);
647+
packageParams.put("notify_url", callbackUrl);
648+
packageParams.put("trade_type", tradeType);
649+
packageParams.put("openid", openId);
650+
651+
String sign = WxCryptUtil.createSign(packageParams, wxMpConfigStorage.getPartnerKey());
652+
String xml = "<xml>" +
653+
"<appid>" + wxMpConfigStorage.getAppId() + "</appid>" +
654+
"<mch_id>" + wxMpConfigStorage.getPartnerId() + "</mch_id>" +
655+
"<nonce_str>" + nonce_str + "</nonce_str>" +
656+
"<sign>" + sign + "</sign>" +
657+
"<body><![CDATA[" + body + "]]></body>" +
658+
"<out_trade_no>" + outTradeNo + "</out_trade_no>" +
659+
"<total_fee>" + packageParams.get("total_fee") + "</total_fee>" +
660+
"<spbill_create_ip>" + ip + "</spbill_create_ip>" +
661+
"<notify_url>" + callbackUrl + "</notify_url>" +
662+
"<trade_type>" + tradeType + "</trade_type>" +
663+
"<openid>" + openId + "</openid>" +
664+
"</xml>";
665+
666+
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
667+
if (httpProxy != null) {
668+
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
669+
httpPost.setConfig(config);
670+
}
671+
672+
StringEntity entity = new StringEntity(xml, Consts.UTF_8);
673+
httpPost.setEntity(entity);
674+
try {
675+
CloseableHttpResponse response = httpClient.execute(httpPost);
676+
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
677+
XStream xstream = XStreamInitializer.getInstance();
678+
xstream.alias("xml", WxMpPrepayIdResult.class);
679+
WxMpPrepayIdResult wxMpPrepayIdResult = (WxMpPrepayIdResult) xstream.fromXML(responseContent);
680+
return wxMpPrepayIdResult;
681+
} catch (IOException e) {
682+
e.printStackTrace();
683+
}
684+
return new WxMpPrepayIdResult();
685+
}
686+
687+
@Override
688+
public Map<String, String> getJSSDKPayInfo(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String callbackUrl) {
689+
WxMpPrepayIdResult wxMpPrepayIdResult = getPrepayId(openId, outTradeNo, amt, body, tradeType, ip, callbackUrl);
690+
String prepayId = wxMpPrepayIdResult.getPrepay_id();
691+
if (prepayId == null || prepayId.equals("")) {
692+
throw new RuntimeException("get prepayid error");
693+
}
694+
695+
Map<String, String> payInfo = new HashMap<String, String>();
696+
payInfo.put("appId", wxMpConfigStorage.getAppId());
697+
// 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
698+
payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
699+
payInfo.put("nonceStr", System.currentTimeMillis() + "");
700+
payInfo.put("package", "prepay_id=" + prepayId);
701+
payInfo.put("signType", "MD5");
702+
703+
String finalSign = WxCryptUtil.createSign(payInfo, wxMpConfigStorage.getPartnerKey());
704+
payInfo.put("sign", finalSign);
705+
return payInfo;
706+
}
616707
}

0 commit comments

Comments
 (0)