package com.webchat.pay.service; import com.webchat.common.bean.APIResponseBean; import com.webchat.common.enums.CommonStatusEnum; import com.webchat.common.enums.RedisKeyEnum; import com.webchat.common.service.RedisService; import com.webchat.common.service.SnowflakeIdGeneratorService; import com.webchat.common.util.AkSkGenerator; import com.webchat.common.util.IDGenerateUtil; import com.webchat.common.util.JsonUtil; import com.webchat.common.util.SignUtil; import com.webchat.domain.dto.payment.PaymentOrderCreateDTO; import com.webchat.domain.dto.payment.TransIdDTO; import com.webchat.domain.vo.dto.payment.AppAkSkDTO; import com.webchat.domain.vo.response.payment.AppBaseResponseVO; import com.webchat.pay.config.constants.PaymentConstant; import com.webchat.pay.config.exception.PaymentAccessAuthException; import com.webchat.pay.repository.entity.PaymentOrderEntity; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @Service public class PaymentApiService { @Autowired private PaymentAppService appService; @Autowired private RedisService redisService; @Autowired private SnowflakeIdGeneratorService snowflakeIdGeneratorService; /** * 交易凭证获取签名的有效期:5分钟 */ private static final Long SIGNATURE_EXPIRE_TIME = 5 * 60 * 1000L; /** * 生成交易凭证 * * @param appId * @param accessKey * @param timestamp * @param signature * @param logId * @return */ public String token(Long appId, String accessKey, String secretKey, Long timestamp, String signature, String logId) { /** * 这里三重校验 * ------------------------ * 1. 校验访问凭证 * 2. 校验校验有效 * 3. 校验签名 */ // 1. 校验访问凭证 AppBaseResponseVO app = appService.appInfo(appId); if (app == null || !CommonStatusEnum.PUBLISHED.getStatusCode().equals(app.getStatus())) { throw new PaymentAccessAuthException("应用不存在/未发布"); } AppAkSkDTO akSkDTO = appService.getAppAkSk(appId); if (akSkDTO == null) { throw new PaymentAccessAuthException("应用不存在"); } boolean checkAk = ObjectUtils.equals(akSkDTO.getAccessKey(), accessKey); boolean checkSk = AkSkGenerator.checkKey(secretKey, akSkDTO.getSecretHashKey()); if (!(checkAk && checkSk)) { throw new PaymentAccessAuthException("应用访问凭证校验失败"); } // 2. 校验校验有效 Long diffTime = System.currentTimeMillis() - timestamp; if (diffTime > SIGNATURE_EXPIRE_TIME) { throw new PaymentAccessAuthException("签名已过期"); } // 3. 校验签名 String validSignature = SignUtil.generateSignature(secretKey, String.valueOf(appId), accessKey, String.valueOf(timestamp)); if (!ObjectUtils.equals(signature, validSignature)) { throw new PaymentAccessAuthException("签名校验失败"); } /** * 生成并返回接入方access-token(交易凭证,用于后续的交易校验) */ return this.generateAccessToken(appId); } /** * 校验支付交易接口请求token的有效性 * * @param accessToken * @return */ public boolean validateAccessToken(String accessToken) { /** * token 由支付平台生成且未失效 ===> accToken是有效的 */ String cacheKey = this.accessTokenCacheKey(accessToken); return redisService.exists(cacheKey); } /** * 生成交易事务id * * @return */ public String transId(String accessToken) { Long appId = this.getAppIdFromAccessToken(accessToken); Assert.notNull(appId, "交易凭证失效"); AppBaseResponseVO appBaseResponse = appService.appInfo(appId); Assert.notNull(appBaseResponse, "应用不存在,联系客服"); Assert.isTrue(CommonStatusEnum.PUBLISHED.getStatusCode().equals(appBaseResponse.getStatus()), "应用未发布"); /** * 基于雪花算法生成分布式下唯一id,作为交易分布式事务id,用于后续异常交易快速回滚 */ String transId = snowflakeIdGeneratorService.generateId(); /** * 初始化TransId缓存,用于后续事务类操作校验 */ this.setPaymentTransIdCache(transId, appId, PaymentConstant.TrasnsIdStatusEnum.INIT); return transId; } /** * 走缓存获取分布式事务id详情 * * @param transId * @return */ public TransIdDTO getTransIdDTO(String transId) { String cacheKey = this.transIdCacheKey(transId); String cache = redisService.get(cacheKey); if (StringUtils.isBlank(cache)) { return null; } return JsonUtil.fromJson(cache, TransIdDTO.class); } /** * 刷新事务id状态 * * @param transId * @param status */ public void setPaymentTransIdCache(String transId, PaymentConstant.TrasnsIdStatusEnum status) { TransIdDTO transIdDTO = this.getTransIdDTO(transId); Assert.notNull(transIdDTO, "transId is null!"); this.setPaymentTransIdCache(transId, transIdDTO.getAppId(), status); } /** * 分布式交易事务id添加orderId信息 * * @param transId * @param orderId */ public void setPaymentTransOrderInfo(String transId, String orderId) { TransIdDTO transIdDTO = this.getTransIdDTO(transId); Assert.notNull(transIdDTO, "transId is null!"); transIdDTO.setOrderId(orderId); String cacheKey = this.transIdCacheKey(transId); redisService.set(cacheKey, JsonUtil.toJsonString(transIdDTO), RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getExpireTime()); } /** * 刷新分布式交易事务id缓存 * * @param transId 事务id * @param appId 应用id * @param status 状态 */ private void setPaymentTransIdCache(String transId, Long appId, PaymentConstant.TrasnsIdStatusEnum status) { String cacheKey = this.transIdCacheKey(transId); TransIdDTO transIdDTO = TransIdDTO.builder() .transId(transId) .appId(appId) .status(status.getStatus()) .build(); redisService.set(cacheKey, JsonUtil.toJsonString(transIdDTO), RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getExpireTime()); } private String transIdCacheKey(String transId) { return RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getKey(transId); } /** * 获取交易凭证背后的应用信息 * * @param accessToken * @return */ public Long getAppIdFromAccessToken(String accessToken) { String accessTokenCacheKey = this.accessTokenCacheKey(accessToken); String cache = redisService.get(accessTokenCacheKey); if (StringUtils.isNotBlank(cache)) { return Long.valueOf(cache); } return null; } private String generateAccessToken(Long appId) { /** * 生成交易凭证 */ String accessToken = IDGenerateUtil.uuid(); String accessTokenCacheKey = this.accessTokenCacheKey(accessToken); redisService.set(accessTokenCacheKey, String.valueOf(appId), RedisKeyEnum.PAYMENT_APP_ACCESS_TOKEN_CACHE.getExpireTime()); return accessToken; } private String accessTokenCacheKey(String accessToken) { return RedisKeyEnum.PAYMENT_APP_ACCESS_TOKEN_CACHE.getKey(accessToken); } }