package com.ydtech.modules.admin.components; import com.alibaba.fastjson.JSONObject; import com.ydtech.config.properties.UnionPayApiConfigurationProperties; import com.ydtech.exception.SystemException; import com.ydtech.modules.admin.components.request.UnionPayRequest; import com.ydtech.modules.admin.components.response.UnionPayResponse; import com.ydtech.modules.admin.utils.UnionPayUtils; import com.ydtech.utils.SM2Utils; import com.ydtech.utils.StringUtils; 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.Base64; 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; private final static int RANDOM_MAX = 999999999; private final static int RANDOM_MIN = 100000000; /** * 运营商二要素验证接口 * * @param request */ public UnionPayResponse twoFactorVerifyRequest(UnionPayRequest request) { String url = unionPayApiConfigurationProperties.getFactorVerifyUrl(); 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 bankCardVerifyRequest(UnionPayRequest request) { String url = unionPayApiConfigurationProperties.getFactorVerifyUrl(); 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"); //TODO 业务场景 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晋掌柜");//TODO 交易发起应用名称 params.put("ipType", "04");//IP版本号 04:IPV4 06:IPV6 try { params.put("sourceIp", InetAddress.getLocalHost().getHostAddress()); // ip } catch (UnknownHostException e) { LOGGER.error("获取ipv4地址异常", e); throw new RuntimeException("获取ipv4地址异常:", e); } params.put("protocolVersion", ""); // 用户授权协议版本号 //TODO params.put("protocolNo", ""); // 用户授权协议流水号 return commonRequest(url, params); } /** * 调用银联接口 * * @param url 地址 * @param params 入参 * @return 返参 */ private UnionPayResponse commonRequest(String url, JSONObject params) { try { String publicKey = unionPayApiConfigurationProperties.getPublicKey(); JSONObject data = new JSONObject(); String dataValue = Base64.getEncoder().encodeToString(SM2Utils.encrypt(params.toString(), publicKey).getBytes()); 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()); // 请求 HttpEntity httpEntity = new HttpEntity<>(params.toString(), httpHeaders); ResponseEntity response = restTemplate.postForEntity(url, httpEntity, String.class); JSONObject resp = JSONObject.parseObject(response.getBody()); if ("20000000".equals(resp.getString("errCode"))) { String privateKey = unionPayApiConfigurationProperties.getPrivateKey(); String respData = SM2Utils.decrypt(new String(Base64.getDecoder().decode(resp.getString("data"))), privateKey); return JSONObject.parseObject(respData, UnionPayResponse.class); } else { throw new SystemException("银联信息验证异常[" + resp.getString("errCode") + ":" + resp.getString("errInfo") + "]"); } } 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"); // 请求 HttpEntity httpEntity = new HttpEntity<>(params, httpHeaders); ResponseEntity response = restTemplate.postForEntity(accessTokenUrl, httpEntity, String.class); JSONObject resp = JSONObject.parseObject(response.getBody()); if ("0000".equals(resp.getString("errCode"))) { String token = resp.getString("accessToken"); int expiresIn = resp.getIntValue("expiresIn"); //失效时间 Map map = new HashMap<>(); map.put(UNION_PAY_MAP, "OPEN-ACCESS-TOKEN AccessToken=\"" + token + "\", appId=\"" + appId + "\""); redisTemplate.opsForHash().putAll(UNION_PAY_ACCESS_TOKEN_KEY, map); redisTemplate.expire(UNION_PAY_ACCESS_TOKEN_KEY, expiresIn - 30, TimeUnit.SECONDS); return token; } else { throw new SystemException("获取银联Token异常[" + resp.getString("errCode") + ":" + resp.getString("errInfo") + "]"); } } catch (Exception e) { LOGGER.error("获取银联Token异常", e); throw new SystemException("获取银联Token异常,请重试!"); } } }