UnionPayRequestApiComponent.java 11 KB

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