[关闭]
@TedZhou 2020-10-15T14:30:40.000000Z 字数 10506 阅读 368

java微信开发常用方法

java


记录java开发微信网页认证、微信分享、微信支付等用到的方法。

WeixinService.java

  1. public class WeixinSercice {
  2. final static String URL_SNS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
  3. final static String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
  4. final static String URL_JSAPI_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi";
  5. final static String URL_ORDER_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
  6. final static String URL_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  7. public static final String TRADE_TYPE_H5 = "MWEB";
  8. public static final String TRADE_TYPE_JS = "JSAPI";
  9. final static String KEY_SIGN = "sign";
  10. @Value("${weixin.appid}")
  11. private String appId;
  12. @Value("${weixin.secret}")
  13. private String appSecret;
  14. @Value("${weixin.mch_id}")
  15. private String mchId;
  16. @Value("${weixin.mch_key}")
  17. private String mchKey;
  18. @Value("${weixin.notify_url}")
  19. private String notifyUrl;
  20. @Autowired
  21. RestTemplate restTemplate;
  22. @Autowired
  23. OrderService orderService;
  24. @Resource
  25. private CacheManager cacheManager;
  26. private static final String CACHE_NAME_WEIXIN = "myapp:weixin";
  27. private static final String CACHE_KEY_ACCESS_TOKEN = "actoken";
  28. private static final String CACHE_KEY_JSAPI_TICKET = "jsticket";
  29. private Cache getCache() {
  30. return cacheManager.getCache(CACHE_NAME_WEIXIN);
  31. }
  32. // 微信网页认证:通过code获取token
  33. public WxWebToken fetchWebTokenByCode(String code) {
  34. String url = String.format(URL_SNS_TOKEN, appId, appSecret, code);
  35. String content = restTemplate.getForObject(url, String.class);
  36. WxWebToken token = JSON.parse(content, WxWebToken.class);
  37. return token;
  38. }
  39. // 微信JSSDK:获取指定url的config
  40. public WxJsdkConfig genJsdkConfig(String url) {
  41. WxJsdkConfig config = new WxJsdkConfig();
  42. config.setAppId(appId);
  43. config.setNonceStr(StringUtils.uuid());
  44. config.setTimestamp(DateTimeUtils.secondsOf(LocalDateTime.now()));
  45. String ticket = getJsapiTicket(url);
  46. if (StringUtils.isNotBlank(ticket)) {
  47. String string = "jsapi_ticket=" + ticket +
  48. "&noncestr=" + config.getNonceStr() +
  49. "&timestamp=" + config.getTimestamp() +
  50. "&url=" + url;
  51. String signature = StringUtils.SHA1(string);
  52. if (StringUtils.isNotBlank(signature)) {
  53. config.setSignature(signature);
  54. }
  55. } else {
  56. config.setErrcode(1);
  57. config.setErrmsg("invalid ticket");
  58. }
  59. log.debug("genJsdkConfig for {} return {}", url, config.getSignature());
  60. return config;
  61. }
  62. //get cached jsapi_ticket
  63. private String getJsapiTicket(String url) {
  64. String cacheKey = CACHE_KEY_JSAPI_TICKET+StringUtils.MD5(url);
  65. WxJsapiTicket ticket = getCache().get(cacheKey, WxJsapiTicket.class);
  66. if (ticket == null || ticket.getExpired()) {
  67. ticket = fetchJsapiTicket();
  68. if (ticket != null && !ticket.getExpired()) {
  69. getCache().put(cacheKey, ticket);
  70. }else {
  71. return null;
  72. }
  73. } else {
  74. log.debug("return cached {} for key {}", CACHE_NAME_WEIXIN, cacheKey);
  75. }
  76. return ticket.getTicket();
  77. }
  78. private WxJsapiTicket fetchJsapiTicket() {
  79. String accessToken = getAccessToken();
  80. if (StringUtils.isBlank(accessToken)) {
  81. return null;
  82. }
  83. String url = String.format(URL_JSAPI_TICKET, accessToken);
  84. String content = restTemplate.getForObject(url, String.class);
  85. WxJsapiTicket ticket = JSON.parse(content, WxJsapiTicket.class);
  86. if (ticket != null) {
  87. Long expires = ticket.getExpires_in();
  88. if (expires != null) {//把过期秒数转化为世纪秒
  89. expires += DateTimeUtils.secondsOf(LocalDateTime.now());
  90. }else{
  91. expires = 0L;
  92. }
  93. ticket.setExpires_in(expires);
  94. log.debug("fetchJsapiTicket return {}-{}", ticket.getErrcode(), ticket.getErrmsg());
  95. }
  96. return ticket;
  97. }
  98. // get cached access_token
  99. private String getAccessToken() {
  100. WxAccessToken token = getCache().get(CACHE_KEY_ACCESS_TOKEN, WxAccessToken.class);
  101. if (token == null || token.getExpired()) {
  102. token = fetchAccessToken();
  103. if (token != null && !token.getExpired()) {
  104. getCache().put(CACHE_KEY_ACCESS_TOKEN, token);
  105. }
  106. } else {
  107. log.debug("return cached {} for key {}", CACHE_NAME_WEIXIN, CACHE_KEY_ACCESS_TOKEN);
  108. }
  109. return token.getAccess_token();
  110. }
  111. private WxAccessToken fetchAccessToken() {
  112. String url = String.format(URL_ACCESS_TOKEN, appId, appSecret);
  113. String content = restTemplate.getForObject(url, String.class);
  114. WxAccessToken token = JSON.parse(content, WxAccessToken.class);
  115. if (token != null) {
  116. Long expires = token.getExpires_in();
  117. if (expires != null) {//把过期秒数转化为世纪秒
  118. expires += DateTimeUtils.secondsOf(LocalDateTime.now());
  119. }else{
  120. expires = 0L;
  121. }
  122. token.setExpires_in(expires);
  123. if (StringUtils.isNotBlank(token.getErrmsg())){
  124. log.debug("fetchAccessToken return {}-{}", token.getErrcode(), token.getErrmsg());
  125. }else{
  126. log.debug("fetchAccessToken return {}", token.getAccess_token());
  127. }
  128. } else {
  129. log.debug("fetchAccessToken return null");
  130. }
  131. return token;
  132. }
  133. //处理订单
  134. public Order processOrder(Order order) {
  135. if (StringUtils.isNotBlank(order.getId())) {
  136. Order dbOrder = orderService.findById(order.getId());
  137. if (dbOrder != null && dbOrder.getStatus()>=Order.STATUS_PAYED) {
  138. return dbOrder;//已支付
  139. }
  140. }
  141. order = orderService.upsert(order);
  142. Map<String, String> map = placeOrder(order);
  143. if (map != null) {
  144. String returnCode = map.get("return_code");
  145. order.setReturnCode(returnCode);
  146. if ("SUCCESS".equals(returnCode)) {
  147. order.setStatus(Order.STATUS_ORDER);
  148. }
  149. order.setReturnMsg(map.get("return_msg"));
  150. order.setMwebUrl(map.get("mweb_url"));
  151. order.setPrepayId(map.get("prepay_id"));
  152. orderService.save(order);
  153. orderService.sendNotify(order);
  154. if (TRADE_TYPE_JS.equals(order.getTradeType())) {
  155. Map<String, String> signs = new TreeMap<>();
  156. signs.put("appId", appId);
  157. signs.put("nonceStr", StringUtils.uuid());
  158. signs.put("package", "prepay_id="+map.get("prepay_id"));
  159. signs.put("signType", "MD5");
  160. signs.put("timeStamp", String.valueOf(DateTimeUtils.secondsOf(LocalDateTime.now())));
  161. signs.put("paySign", genSign(signs));
  162. order.setSigns(signs);
  163. }
  164. }
  165. return order;
  166. }
  167. //下单
  168. private Map<String, String> placeOrder(Order order) {
  169. Map<String, String> map = null;
  170. String tradeType = order.getTradeType();
  171. if (TRADE_TYPE_H5.equals(tradeType)) {
  172. map = prepareH5Order(order.getTradeNo(), order.getProductId(), order.getProductName(), order.getTotalFee(), order.getIpaddr());
  173. }else{
  174. map = prepareJsOrder(order.getTradeNo(), order.getProductId(), order.getProductName(), order.getTotalFee(), order.getIpaddr(), order.getOpenid());
  175. }
  176. HttpEntity<String> request = genXmlRequest(map);
  177. String res = restTemplate.postForObject(URL_UNIFIED_ORDER, request, String.class);
  178. log.debug(res);
  179. return XmlUtils.parse(res);
  180. }
  181. // H5支付下单数据
  182. private Map<String, String> prepareH5Order(String tradeNo, String productId, String productName, Long amount, String ip) {
  183. Map<String, String> order = newOrderMap();
  184. order.put("trade_type", TRADE_TYPE_H5);//H5支付的交易类型为MWEB
  185. order.put("notify_url", notifyUrl);//回调地址, 不能携带参数。
  186. order.put("scene_info", "{\"h5_info\": {\"type\":\"WAP\",\"wap_url\": \"\",\"wap_name\": \"\"}}");//用于上报支付的场景信息
  187. order.put("spbill_create_ip", ip);//用户端IP,支持ipv4、ipv6格式
  188. order.put("out_trade_no", tradeNo);//自定义交易单号
  189. order.put("product_id", productId);//自定义商品
  190. order.put("body", productName);//网页的主页title名-商品概述
  191. order.put("fee_type", "CNY");//境内只支持CNY,默认可不传
  192. order.put("total_fee", String.valueOf(amount));//订单总金额,单位为分
  193. //签名
  194. order.put(KEY_SIGN, genSign(order));
  195. return order;
  196. }
  197. // JSAPI支付下单数据
  198. private Map<String, String> prepareJsOrder(String tradeNo, String productId, String productName, Long amount, String ip, String openid) {
  199. Map<String, String> order = newOrderMap();
  200. order.put("trade_type", TRADE_TYPE_JS);//交易类型为JSAPI
  201. order.put("notify_url", notifyUrl);//回调地址, 不能携带参数。
  202. //order.put("scene_info", "{\"h5_info\": {\"type\":\"WAP\",\"wap_url\": \"\",\"wap_name\": \"\"}}");//用于上报支付的场景信息
  203. order.put("openid", openid);
  204. order.put("spbill_create_ip", ip);//用户端IP,支持ipv4、ipv6格式
  205. order.put("out_trade_no", tradeNo);//自定义交易单号
  206. order.put("product_id", productId);//自定义商品
  207. order.put("body", productName);//网页的主页title名-商品概述
  208. order.put("fee_type", "CNY");//境内只支持CNY,默认可不传
  209. order.put("total_fee", String.valueOf(amount));//订单总金额,单位为分
  210. //签名
  211. order.put(KEY_SIGN, genSign(order));
  212. return order;
  213. }
  214. // 下单数据准备:公用部分
  215. private Map<String, String> newOrderMap() {
  216. Map<String, String> order = new TreeMap<>();
  217. order.put("appid", appId);
  218. order.put("mch_id", mchId);
  219. order.put("nonce_str", StringUtils.uuid());
  220. return order;
  221. }
  222. // 构造xml request
  223. private HttpEntity<String> genXmlRequest(Object data) {
  224. HttpHeaders headers = new HttpHeaders();
  225. headers.setContentType(MediaType.APPLICATION_XML);
  226. String body = XmlUtils.stringify(data, "xml");
  227. return new HttpEntity<String>(body, headers);
  228. }
  229. // 生成签名
  230. private String genSign(Map<String, String> paramMap) {
  231. StringBuilder sb = new StringBuilder();
  232. paramMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach((a) -> {
  233. if (StringUtils.isBlank(a.getKey()) || KEY_SIGN.equals(a.getKey())) {
  234. return;
  235. }
  236. if (StringUtils.isBlank(a.getValue())) {
  237. return;
  238. }
  239. sb.append(a.getKey()); sb.append("="); sb.append(a.getValue()); sb.append("&");
  240. });
  241. sb.append("key="); sb.append(mchKey);
  242. String signStr = sb.toString();
  243. log.debug(signStr);
  244. return StringUtils.MD5(signStr).toUpperCase();
  245. }
  246. //微信支付结果回调处理
  247. @Synchronized // TODO: 避免重入仅这样不够,还需要锁定订单记录
  248. public String processCallback(String data) {
  249. Map<String, String> map = XmlUtils.parse(data);
  250. String sign = genSign(map);
  251. if (!sign.equals(map.get(KEY_SIGN))) {
  252. return returnCodeMsg("FAIL", "SIGNERROR");
  253. };
  254. String tradeNo = map.get("out_trade_no");
  255. Order order = orderService.findByTradeNo(tradeNo);
  256. if (order == null) {
  257. return returnCodeMsg("FAIL", "NOTFOUND");
  258. }
  259. if (order.getStatus() >= Order.STATUS_PAYED) {
  260. return returnCodeMsg("SUCCESS", "OK!");
  261. }
  262. String tradeType = map.get("trade_type");
  263. long totalFee = NumberUtils.parse(map.get("total_fee"), Long.class, 0L);
  264. if (totalFee != order.getTotalFee() || !StringUtils.equals(tradeType, order.getTradeType())) {
  265. return returnCodeMsg("FAIL", "TRADEINFOERROR");
  266. }
  267. order.setTransactionId(map.get("transaction_id"));
  268. order.setReturnCode(map.get("return_code"));
  269. order.setResultCode(map.get("result_code"));
  270. order.setBankType(map.get("bank_type"));
  271. order.setTimeEnd(map.get("time_end"));
  272. order.setStatus(Order.STATUS_PAYED);
  273. orderService.save(order);
  274. return returnCodeMsg("SUCCESS", "OK");
  275. }
  276. private String returnCodeMsg(String code, String msg) {
  277. return String.format("<xml><return_code><![CDATA[%s]]></return_code><return_msg><![CDATA[%s]]></return_msg></xml>", code, msg);
  278. }
  279. }
  280. ## WxResponse.java
  281. ```java
  282. @Data
  283. public class WxResponse implements Serializable {
  284. private Integer errcode;
  285. private String errmsg;
  286. }
  287. <div class="md-section-divider"></div>

WxResexpire.java

  1. @Data
  2. public class WxResexpire extends WxResponse implements Serializable {
  3. Long expires_in;
  4. public Boolean getExpired() {
  5. return expires_in == null || expires_in <= DateTimeUtils.secondsOf(LocalDateTime.now());
  6. }
  7. }
  8. <div class="md-section-divider"></div>

WxAccessToken.java

  1. @Data
  2. public class WxAccessToken extends WxResexpire implements Serializable {
  3. String access_token;
  4. }
  5. <div class="md-section-divider"></div>

WxWebToken.java

  1. @Data
  2. public class WxWebToken extends WxAccessToken implements Serializable {
  3. String refresh_token;
  4. String openid;
  5. String scope;
  6. }
  7. <div class="md-section-divider"></div>

WxJsapiTicket.java

  1. @Data
  2. public class WxJsapiTicket extends WxResexpire implements Serializable {
  3. String ticket;
  4. }
  5. <div class="md-section-divider"></div>

WxJsdkConfig.java

  1. @Data
  2. @JsonInclude(value = Include.NON_NULL)
  3. public class WxJsdkConfig extends WxResponse implements Serializable {
  4. String appId;
  5. Long timestamp;
  6. String nonceStr;
  7. String signature;
  8. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注