UnionPayRequestApiComponent.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package com.ydtech.modules.admin.components;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.ydtech.config.properties.UnionPayApiConfigurationProperties;
  4. import com.ydtech.exception.SystemException;
  5. import com.ydtech.modules.admin.components.request.UnionPayRequest;
  6. import com.ydtech.modules.admin.components.response.UnionPayResponse;
  7. import com.ydtech.modules.admin.utils.UnionPayUtils;
  8. import com.ydtech.utils.SM2;
  9. import com.ydtech.utils.StringUtils;
  10. import com.ydtech.utils.idgen.IdGenerate;
  11. import lombok.RequiredArgsConstructor;
  12. import org.redisson.api.RLock;
  13. import org.redisson.api.RedissonClient;
  14. import org.slf4j.Logger;
  15. import org.slf4j.LoggerFactory;
  16. import org.springframework.data.redis.core.StringRedisTemplate;
  17. import org.springframework.http.HttpEntity;
  18. import org.springframework.http.HttpHeaders;
  19. import org.springframework.http.ResponseEntity;
  20. import org.springframework.stereotype.Component;
  21. import org.springframework.util.ObjectUtils;
  22. import org.springframework.web.client.RestTemplate;
  23. import java.net.InetAddress;
  24. import java.net.UnknownHostException;
  25. import java.security.InvalidParameterException;
  26. import java.text.SimpleDateFormat;
  27. import java.util.Base64;
  28. import java.util.Date;
  29. import java.util.HashMap;
  30. import java.util.Map;
  31. import java.util.concurrent.TimeUnit;
  32. /**
  33. * @description: 银联Api
  34. **/
  35. @Component
  36. @RequiredArgsConstructor
  37. public class UnionPayRequestApiComponent {
  38. private final static Logger LOGGER = LoggerFactory.getLogger(UnionPayRequestApiComponent.class);
  39. private final UnionPayApiConfigurationProperties unionPayApiConfigurationProperties;
  40. private final RedissonClient redissonClient;
  41. private final StringRedisTemplate redisTemplate;
  42. private final RestTemplate restTemplate;
  43. private final static String UNION_PAY_MAP = "unionPay";
  44. private final static String UNION_PAY_ACCESS_TOKEN_KEY = "unionPayAccessToken";
  45. private final static String UNION_PAY_ACCESS_TOKEN_LOCK = "unionPayAccessTokenLock";
  46. private final static int UNION_PAY_LOCK_TIME = 10000;
  47. private final static int UNION_PAY_SLEEP_TIME = 5000;
  48. /**
  49. * 运营商二要素验证接口
  50. *
  51. * @param request
  52. */
  53. public UnionPayResponse threeFactorVerifyRequest(UnionPayRequest request) {
  54. String url = unionPayApiConfigurationProperties.getThreeFactorVerifyUrl();
  55. JSONObject params = new JSONObject();
  56. if (StringUtils.isBlank(request.getPhoneNo()) || StringUtils.isBlank(request.getCertNo()) ||
  57. StringUtils.isBlank(request.getName())) {
  58. throw new InvalidParameterException("手机号、身份证、用户姓名不允许为空!");
  59. }
  60. params.put("phoneNo", request.getPhoneNo());
  61. params.put("certType", "01");//证件类型 01:身份证
  62. params.put("certNo", request.getCertNo());
  63. params.put("name", request.getName());
  64. return commonRequest(url, params);
  65. }
  66. /**
  67. * 银行卡验证接口
  68. *
  69. * @param request
  70. */
  71. public UnionPayResponse bankCardVerifyRequest(UnionPayRequest request) {
  72. String url = unionPayApiConfigurationProperties.getBankCardVerifyUrl();
  73. JSONObject params = new JSONObject();
  74. if (StringUtils.isBlank(request.getCardNo())) {
  75. throw new InvalidParameterException("卡号不允许为空!");
  76. }
  77. if (StringUtils.isBlank(request.getCertNo()) && StringUtils.isBlank(request.getName()) &&
  78. StringUtils.isBlank(request.getPhoneNo())) {
  79. throw new InvalidParameterException("证件号、用户姓名、手机号至少传值一个!");
  80. }
  81. params.put("cardNo", request.getCardNo());
  82. params.put("personalMandate", "1");//是否取得个人授权,字段取值0或1。1:是 0:否
  83. params.put("sceneId", "13"); // 业务场景 13:车险
  84. if (StringUtils.isNotBlank(request.getCertNo())) {
  85. params.put("certType", "01");//证件类型 01:身份证
  86. params.put("certNo", request.getCertNo());
  87. }
  88. if (StringUtils.isNotBlank(request.getName())) {
  89. params.put("name", request.getName());
  90. }
  91. if (StringUtils.isNotBlank(request.getPhoneNo())) {
  92. params.put("phoneNo", request.getPhoneNo());
  93. }
  94. params.put("appName", "01晋掌柜");// 交易发起应用名称
  95. params.put("ipType", "04");//IP版本号 04:IPV4 06:IPV6
  96. try {
  97. params.put("sourceIp", ipFormat(InetAddress.getLocalHost().getHostAddress())); // ip
  98. } catch (UnknownHostException e) {
  99. LOGGER.error("获取ipv4地址异常", e);
  100. throw new RuntimeException("获取ipv4地址异常:", e);
  101. }
  102. params.put("protocolVersion", "V1.0"); // 用户授权协议版本号
  103. params.put("protocolNo", IdGenerate.nextId());
  104. return commonRequest(url, params);
  105. }
  106. private String ipFormat(String ip) {
  107. String[] ips = ip.split("\\.");
  108. StringBuffer br = new StringBuffer();
  109. for (int i = 0; i < ips.length; i++) {
  110. br.append(StringUtils.leftPad(ips[i], 3, "0"));
  111. if (i < ips.length - 1) {
  112. br.append(".");
  113. }
  114. }
  115. return br.toString();
  116. }
  117. /**
  118. * 调用银联接口
  119. *
  120. * @param url 地址
  121. * @param params 入参
  122. * @return 返参
  123. */
  124. private UnionPayResponse commonRequest(String url, JSONObject params) {
  125. try {
  126. String publicKey = unionPayApiConfigurationProperties.getPublicKey();
  127. JSONObject data = new JSONObject();
  128. String dataValue = SM2.sm2Encrypt(params.toString(), publicKey);
  129. data.put("data", dataValue);
  130. HttpHeaders httpHeaders = new HttpHeaders();
  131. httpHeaders.set("Content-Type", "application/json;charset=UTF-8");
  132. httpHeaders.set("Accept", "application/json");
  133. httpHeaders.set("Authorization", getAccessToken());
  134. // 请求
  135. LOGGER.error("银联接口url:"+url);
  136. LOGGER.error("银联接口请求报文:"+params);
  137. LOGGER.error("银联接口加密请求报文:"+data);
  138. HttpEntity<String> httpEntity = new HttpEntity<>(data.toString(), httpHeaders);
  139. ResponseEntity<String> response = restTemplate.postForEntity(url, httpEntity, String.class);
  140. JSONObject resp = JSONObject.parseObject(response.getBody());
  141. LOGGER.error("银联接口响应报文:"+resp);
  142. if ("20000000".equals(resp.getString("errCode"))) {
  143. String privateKey = unionPayApiConfigurationProperties.getPrivateKey();
  144. String respData = SM2.sm2Decrypt(resp.getString("data"),
  145. privateKey);
  146. LOGGER.error("银联接口解密响应报文:"+respData);
  147. return JSONObject.parseObject(respData, UnionPayResponse.class);
  148. } else {
  149. throw new SystemException("银联信息验证异常[" + resp.getString("errCode") + ":" +
  150. resp.getString("errInfo") + "]");
  151. }
  152. } catch (Exception e) {
  153. LOGGER.error("银联接口调用异常", e);
  154. throw new RuntimeException("银联接口调用异常:", e);
  155. }
  156. }
  157. /**
  158. * 获取银联token
  159. *
  160. * @return
  161. */
  162. private String getAccessToken() {
  163. Object token = redisTemplate.opsForHash().get(UNION_PAY_MAP, UNION_PAY_ACCESS_TOKEN_KEY);
  164. // 缓存中不存在就去请求token
  165. if (!ObjectUtils.isEmpty(token)) {
  166. return (String) token;
  167. }
  168. // 使用分布式锁 只有一个人可以访问登录
  169. RLock lock = redissonClient.getLock(UNION_PAY_ACCESS_TOKEN_LOCK);
  170. try {
  171. boolean isLock = lock.tryLock(1, UNION_PAY_LOCK_TIME, TimeUnit.MILLISECONDS);
  172. if (isLock) {
  173. try {
  174. return this.invokeToken();
  175. } finally {
  176. lock.unlock();
  177. }
  178. }
  179. } catch (InterruptedException e) {
  180. LOGGER.error("getAccessToken InterruptedException", e);
  181. lock.unlock();
  182. Thread.currentThread().interrupt();
  183. throw new RuntimeException("获取银联Token异常,请重试!");
  184. }
  185. // 睡眠一段时间 自调用 重新获取缓存中的 header
  186. try {
  187. Thread.sleep(UNION_PAY_SLEEP_TIME);
  188. } catch (InterruptedException e) {
  189. LOGGER.error("getAccessToken InterruptedException", e);
  190. Thread.currentThread().interrupt();
  191. throw new RuntimeException("获取银联Token异常,请重试!");
  192. }
  193. return getAccessToken();
  194. }
  195. /**
  196. * 调用银联接口获取token
  197. *
  198. * @return token
  199. */
  200. public String invokeToken() {
  201. String appId = unionPayApiConfigurationProperties.getAppId();
  202. String appKey = unionPayApiConfigurationProperties.getAppKey();
  203. String accessTokenUrl = unionPayApiConfigurationProperties.getAccessTokenUrl();
  204. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
  205. String timestamp = sdf.format(new Date());
  206. String randNumber = UnionPayUtils.createData(3);
  207. String signature = UnionPayUtils.SHA256(appId + timestamp + randNumber + appKey);
  208. JSONObject params = new JSONObject();
  209. params.put("appId", appId);
  210. params.put("timestamp", timestamp);
  211. params.put("nonce", randNumber);
  212. params.put("signMethod", "SHA256");
  213. params.put("signature", signature);
  214. try {
  215. HttpHeaders httpHeaders = new HttpHeaders();
  216. httpHeaders.set("Content-Type", "application/json;charset=UTF-8");
  217. httpHeaders.set("Accept", "application/json");
  218. // 请求
  219. LOGGER.error("银联获取token-url:"+accessTokenUrl);
  220. LOGGER.error("银联获取token-入参:"+params);
  221. HttpEntity<JSONObject> httpEntity = new HttpEntity<>(params, httpHeaders);
  222. ResponseEntity<String> response = restTemplate.postForEntity(accessTokenUrl, httpEntity, String.class);
  223. JSONObject resp = JSONObject.parseObject(response.getBody());
  224. LOGGER.error("银联获取token-返参:"+resp);
  225. if ("0000".equals(resp.getString("errCode"))) {
  226. String token = resp.getString("accessToken");
  227. int expiresIn = resp.getIntValue("expiresIn"); //失效时间
  228. Map<String, String> map = new HashMap<>();
  229. map.put(UNION_PAY_ACCESS_TOKEN_KEY, "OPEN-ACCESS-TOKEN AccessToken=" + token + ", AppId=" + appId );
  230. redisTemplate.opsForHash().putAll(UNION_PAY_MAP, map);
  231. redisTemplate.expire(UNION_PAY_ACCESS_TOKEN_KEY, expiresIn - 30, TimeUnit.SECONDS);
  232. return token;
  233. } else {
  234. throw new SystemException("获取银联Token异常[" + resp.getString("errCode") + ":" +
  235. resp.getString("errInfo") + "]");
  236. }
  237. } catch (Exception e) {
  238. LOGGER.error("获取银联Token异常", e);
  239. throw new SystemException("获取银联Token异常,请重试!");
  240. }
  241. }
  242. }