|
|
@@ -0,0 +1,309 @@
|
|
|
+package com.jzg.organization.component;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.jzg.commons.exception.SystemException;
|
|
|
+import com.jzg.commons.util.StringUtils;
|
|
|
+import com.jzg.commons.util.UnionPayUtils;
|
|
|
+import com.jzg.commons.util.idgen.IdGenerate;
|
|
|
+import com.jzg.commons.util.sm.SM2;
|
|
|
+import com.jzg.organization.component.request.UnionPayRequest;
|
|
|
+import com.jzg.organization.component.response.UnionPayResponse;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
+import org.springframework.http.HttpEntity;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.util.ObjectUtils;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import java.net.InetAddress;
|
|
|
+import java.net.UnknownHostException;
|
|
|
+import java.security.InvalidParameterException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description: 银联Api
|
|
|
+ **/
|
|
|
+@Component
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class UnionPayRequestApiComponent {
|
|
|
+
|
|
|
+ private final static Logger LOGGER = LoggerFactory.getLogger(UnionPayRequestApiComponent.class);
|
|
|
+
|
|
|
+ private final UnionPayApiConfigurationProperties unionPayApiConfigurationProperties;
|
|
|
+
|
|
|
+ private final RedissonClient redissonClient;
|
|
|
+
|
|
|
+ private final StringRedisTemplate redisTemplate;
|
|
|
+
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+
|
|
|
+ private final static String UNION_PAY_MAP = "unionPay";
|
|
|
+
|
|
|
+ private final static String UNION_PAY_ACCESS_TOKEN_KEY = "unionPayAccessToken";
|
|
|
+
|
|
|
+ private final static String UNION_PAY_ACCESS_TOKEN_LOCK = "unionPayAccessTokenLock";
|
|
|
+
|
|
|
+ private final static int UNION_PAY_LOCK_TIME = 10000;
|
|
|
+
|
|
|
+ private final static int UNION_PAY_SLEEP_TIME = 5000;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 运营商二要素验证接口
|
|
|
+ *
|
|
|
+ * @param request
|
|
|
+ */
|
|
|
+ public UnionPayResponse twoFactorVerifyRequest(UnionPayRequest request) {
|
|
|
+ String url = unionPayApiConfigurationProperties.getTwoFactorVerifyUrl();
|
|
|
+ JSONObject params = new JSONObject();
|
|
|
+ if (StringUtils.isBlank(request.getPhoneNo())) {
|
|
|
+ throw new InvalidParameterException("手机号不允许为空!");
|
|
|
+ }
|
|
|
+ params.put("phoneNo", request.getPhoneNo());
|
|
|
+
|
|
|
+ //01:验证姓名+手机号,此时用户姓名必输 02:身份证+手机号,此时证件类型、证件
|
|
|
+ if (StringUtils.isNotBlank(request.getCertNo())) {
|
|
|
+ params.put("verifyType", "02");
|
|
|
+ params.put("certType", "01");//证件类型 01:身份证
|
|
|
+ params.put("certNo", request.getCertNo());
|
|
|
+ } else if (StringUtils.isNotBlank(request.getName())) {
|
|
|
+ params.put("verifyType", "01");
|
|
|
+ params.put("name", request.getName());
|
|
|
+ } else {
|
|
|
+ throw new InvalidParameterException("身份证、用户姓名至少传值一个!");
|
|
|
+ }
|
|
|
+ return commonRequest(url, params);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 运营商三要素验证接口
|
|
|
+ *
|
|
|
+ * @param request
|
|
|
+ */
|
|
|
+ public UnionPayResponse threeFactorVerifyRequest(UnionPayRequest request) {
|
|
|
+ String url = unionPayApiConfigurationProperties.getThreeFactorVerifyUrl();
|
|
|
+
|
|
|
+ JSONObject params = new JSONObject();
|
|
|
+ if (StringUtils.isBlank(request.getPhoneNo()) || StringUtils.isBlank(request.getCertNo()) ||
|
|
|
+ StringUtils.isBlank(request.getName())) {
|
|
|
+ throw new InvalidParameterException("手机号、身份证、用户姓名不允许为空!");
|
|
|
+ }
|
|
|
+ params.put("phoneNo", request.getPhoneNo());
|
|
|
+ params.put("certType", "01");//证件类型 01:身份证
|
|
|
+ params.put("certNo", request.getCertNo());
|
|
|
+ params.put("name", request.getName());
|
|
|
+ return commonRequest(url, params);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 银行卡验证接口
|
|
|
+ *
|
|
|
+ * @param request
|
|
|
+ */
|
|
|
+ public UnionPayResponse bankCardVerifyRequest(UnionPayRequest request) {
|
|
|
+ String url = unionPayApiConfigurationProperties.getBankCardVerifyUrl();
|
|
|
+
|
|
|
+ JSONObject params = new JSONObject();
|
|
|
+ if (StringUtils.isBlank(request.getCardNo())) {
|
|
|
+ throw new InvalidParameterException("卡号不允许为空!");
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(request.getCertNo()) && StringUtils.isBlank(request.getName()) &&
|
|
|
+ StringUtils.isBlank(request.getPhoneNo())) {
|
|
|
+ throw new InvalidParameterException("证件号、用户姓名、手机号至少传值一个!");
|
|
|
+ }
|
|
|
+ params.put("cardNo", request.getCardNo());
|
|
|
+ params.put("personalMandate", "1");//是否取得个人授权,字段取值0或1。1:是 0:否
|
|
|
+ params.put("sceneId", "13"); // 业务场景 13:车险
|
|
|
+ if (StringUtils.isNotBlank(request.getCertNo())) {
|
|
|
+ params.put("certType", "01");//证件类型 01:身份证
|
|
|
+ params.put("certNo", request.getCertNo());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(request.getName())) {
|
|
|
+ params.put("name", request.getName());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(request.getPhoneNo())) {
|
|
|
+ params.put("phoneNo", request.getPhoneNo());
|
|
|
+ }
|
|
|
+ params.put("appName", "01晋掌柜");// 交易发起应用名称
|
|
|
+ params.put("ipType", "04");//IP版本号 04:IPV4 06:IPV6
|
|
|
+ try {
|
|
|
+ params.put("sourceIp", ipFormat(InetAddress.getLocalHost().getHostAddress())); // ip
|
|
|
+ } catch (UnknownHostException e) {
|
|
|
+ LOGGER.error("获取ipv4地址异常", e);
|
|
|
+ throw new RuntimeException("获取ipv4地址异常:", e);
|
|
|
+ }
|
|
|
+ params.put("protocolVersion", "V1.0"); // 用户授权协议版本号
|
|
|
+ params.put("protocolNo", IdGenerate.nextId());
|
|
|
+ return commonRequest(url, params);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private String ipFormat(String ip) {
|
|
|
+ String[] ips = ip.split("\\.");
|
|
|
+ StringBuffer br = new StringBuffer();
|
|
|
+ for (int i = 0; i < ips.length; i++) {
|
|
|
+ br.append(StringUtils.leftPad(ips[i], 3, "0"));
|
|
|
+ if (i < ips.length - 1) {
|
|
|
+ br.append(".");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return br.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调用银联接口
|
|
|
+ *
|
|
|
+ * @param url 地址
|
|
|
+ * @param params 入参
|
|
|
+ * @return 返参
|
|
|
+ */
|
|
|
+ private UnionPayResponse commonRequest(String url, JSONObject params) {
|
|
|
+ try {
|
|
|
+ String publicKey = unionPayApiConfigurationProperties.getPublicKey();
|
|
|
+
|
|
|
+ JSONObject data = new JSONObject();
|
|
|
+ String dataValue = SM2.sm2Encrypt(params.toString(), publicKey);
|
|
|
+ data.put("data", dataValue);
|
|
|
+
|
|
|
+ HttpHeaders httpHeaders = new HttpHeaders();
|
|
|
+ httpHeaders.set("Content-Type", "application/json;charset=UTF-8");
|
|
|
+ httpHeaders.set("Accept", "application/json");
|
|
|
+ httpHeaders.set("Authorization", getAccessToken());
|
|
|
+ // 请求
|
|
|
+ LOGGER.error("银联接口url:" + url);
|
|
|
+ LOGGER.error("银联接口请求报文:" + params);
|
|
|
+ LOGGER.error("银联接口加密请求报文:" + data);
|
|
|
+ HttpEntity<String> httpEntity = new HttpEntity<>(data.toString(), httpHeaders);
|
|
|
+ ResponseEntity<String> response = restTemplate.postForEntity(url, httpEntity, String.class);
|
|
|
+ JSONObject resp = JSONObject.parseObject(response.getBody());
|
|
|
+ LOGGER.error("银联接口响应报文:" + resp);
|
|
|
+ if ("20000000".equals(resp.getString("errCode"))) {
|
|
|
+ String privateKey = unionPayApiConfigurationProperties.getPrivateKey();
|
|
|
+ String respData = SM2.sm2Decrypt(resp.getString("data"),
|
|
|
+ privateKey);
|
|
|
+ LOGGER.error("银联接口解密响应报文:" + respData);
|
|
|
+ return JSONObject.parseObject(respData, UnionPayResponse.class);
|
|
|
+ } else {
|
|
|
+ throw new SystemException("银联信息验证异常[" + resp.getString("errCode") + ":" +
|
|
|
+ resp.getString("errInfo") + "]");
|
|
|
+ }
|
|
|
+ } catch (SystemException e) {
|
|
|
+ throw new SystemException(e.getMessage());
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOGGER.error("银联接口调用异常", e);
|
|
|
+ throw new RuntimeException("银联接口调用异常:", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取银联token
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String getAccessToken() {
|
|
|
+
|
|
|
+ Object token = redisTemplate.opsForHash().get(UNION_PAY_MAP, UNION_PAY_ACCESS_TOKEN_KEY);
|
|
|
+ // 缓存中不存在就去请求token
|
|
|
+ if (!ObjectUtils.isEmpty(token)) {
|
|
|
+ return (String) token;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用分布式锁 只有一个人可以访问登录
|
|
|
+ RLock lock = redissonClient.getLock(UNION_PAY_ACCESS_TOKEN_LOCK);
|
|
|
+ try {
|
|
|
+ boolean isLock = lock.tryLock(1, UNION_PAY_LOCK_TIME, TimeUnit.MILLISECONDS);
|
|
|
+ if (isLock) {
|
|
|
+ try {
|
|
|
+ return this.invokeToken();
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ LOGGER.error("getAccessToken InterruptedException", e);
|
|
|
+ lock.unlock();
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new RuntimeException("获取银联Token异常,请重试!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 睡眠一段时间 自调用 重新获取缓存中的 header
|
|
|
+ try {
|
|
|
+ Thread.sleep(UNION_PAY_SLEEP_TIME);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ LOGGER.error("getAccessToken InterruptedException", e);
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new RuntimeException("获取银联Token异常,请重试!");
|
|
|
+ }
|
|
|
+ return getAccessToken();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调用银联接口获取token
|
|
|
+ *
|
|
|
+ * @return token
|
|
|
+ */
|
|
|
+ public String invokeToken() {
|
|
|
+ String appId = unionPayApiConfigurationProperties.getAppId();
|
|
|
+ String appKey = unionPayApiConfigurationProperties.getAppKey();
|
|
|
+ String accessTokenUrl = unionPayApiConfigurationProperties.getAccessTokenUrl();
|
|
|
+
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
|
|
|
+ String timestamp = sdf.format(new Date());
|
|
|
+ String randNumber = UnionPayUtils.createData(3);
|
|
|
+
|
|
|
+ String signature = UnionPayUtils.SHA256(appId + timestamp + randNumber + appKey);
|
|
|
+
|
|
|
+ JSONObject params = new JSONObject();
|
|
|
+ params.put("appId", appId);
|
|
|
+ params.put("timestamp", timestamp);
|
|
|
+ params.put("nonce", randNumber);
|
|
|
+ params.put("signMethod", "SHA256");
|
|
|
+ params.put("signature", signature);
|
|
|
+
|
|
|
+ try {
|
|
|
+ HttpHeaders httpHeaders = new HttpHeaders();
|
|
|
+ httpHeaders.set("Content-Type", "application/json;charset=UTF-8");
|
|
|
+ httpHeaders.set("Accept", "application/json");
|
|
|
+ // 请求
|
|
|
+ LOGGER.error("银联获取token-url:" + accessTokenUrl);
|
|
|
+ LOGGER.error("银联获取token-入参:" + params);
|
|
|
+ HttpEntity<JSONObject> httpEntity = new HttpEntity<>(params, httpHeaders);
|
|
|
+ ResponseEntity<String> response = restTemplate.postForEntity(accessTokenUrl, httpEntity, String.class);
|
|
|
+ JSONObject resp = JSONObject.parseObject(response.getBody());
|
|
|
+ LOGGER.error("银联获取token-返参:" + resp);
|
|
|
+ if ("0000".equals(resp.getString("errCode"))) {
|
|
|
+ String token = resp.getString("accessToken");
|
|
|
+ int expiresIn = resp.getIntValue("expiresIn"); //失效时间
|
|
|
+ Map<String, String> map = new HashMap<>();
|
|
|
+ String tokenValue = "OPEN-ACCESS-TOKEN AccessToken=" + token + ", AppId=" + appId;
|
|
|
+ map.put(UNION_PAY_ACCESS_TOKEN_KEY, tokenValue);
|
|
|
+ redisTemplate.opsForHash().putAll(UNION_PAY_MAP, map);
|
|
|
+ redisTemplate.expire(UNION_PAY_MAP, expiresIn - 30, TimeUnit.SECONDS);
|
|
|
+ return tokenValue;
|
|
|
+ } else {
|
|
|
+ throw new SystemException("获取银联Token异常[" + resp.getString("errCode") + ":" +
|
|
|
+ resp.getString("errInfo") + "]");
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (SystemException e) {
|
|
|
+ throw new SystemException(e.getMessage());
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOGGER.error("获取银联Token异常", e);
|
|
|
+ throw new SystemException("获取银联Token异常,请重试!");
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|