PaymentApiService.java 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package com.webchat.pay.service;
  2. import com.webchat.common.bean.APIResponseBean;
  3. import com.webchat.common.enums.CommonStatusEnum;
  4. import com.webchat.common.enums.RedisKeyEnum;
  5. import com.webchat.common.service.RedisService;
  6. import com.webchat.common.service.SnowflakeIdGeneratorService;
  7. import com.webchat.common.util.AkSkGenerator;
  8. import com.webchat.common.util.IDGenerateUtil;
  9. import com.webchat.common.util.JsonUtil;
  10. import com.webchat.common.util.SignUtil;
  11. import com.webchat.domain.dto.payment.PaymentOrderCreateDTO;
  12. import com.webchat.domain.dto.payment.TransIdDTO;
  13. import com.webchat.domain.vo.dto.payment.AppAkSkDTO;
  14. import com.webchat.domain.vo.response.payment.AppBaseResponseVO;
  15. import com.webchat.pay.config.constants.PaymentConstant;
  16. import com.webchat.pay.config.exception.PaymentAccessAuthException;
  17. import com.webchat.pay.repository.entity.PaymentOrderEntity;
  18. import org.apache.commons.lang3.ObjectUtils;
  19. import org.apache.commons.lang3.StringUtils;
  20. import org.springframework.beans.factory.annotation.Autowired;
  21. import org.springframework.stereotype.Service;
  22. import org.springframework.util.Assert;
  23. @Service
  24. public class PaymentApiService {
  25. @Autowired
  26. private PaymentAppService appService;
  27. @Autowired
  28. private RedisService redisService;
  29. @Autowired
  30. private SnowflakeIdGeneratorService snowflakeIdGeneratorService;
  31. /**
  32. * 交易凭证获取签名的有效期:5分钟
  33. */
  34. private static final Long SIGNATURE_EXPIRE_TIME = 5 * 60 * 1000L;
  35. /**
  36. * 生成交易凭证
  37. *
  38. * @param appId
  39. * @param accessKey
  40. * @param timestamp
  41. * @param signature
  42. * @param logId
  43. * @return
  44. */
  45. public String token(Long appId,
  46. String accessKey,
  47. String secretKey,
  48. Long timestamp,
  49. String signature,
  50. String logId) {
  51. /**
  52. * 这里三重校验
  53. * ------------------------
  54. * 1. 校验访问凭证
  55. * 2. 校验校验有效
  56. * 3. 校验签名
  57. */
  58. // 1. 校验访问凭证
  59. AppBaseResponseVO app = appService.appInfo(appId);
  60. if (app == null || !CommonStatusEnum.PUBLISHED.getStatusCode().equals(app.getStatus())) {
  61. throw new PaymentAccessAuthException("应用不存在/未发布");
  62. }
  63. AppAkSkDTO akSkDTO = appService.getAppAkSk(appId);
  64. if (akSkDTO == null) {
  65. throw new PaymentAccessAuthException("应用不存在");
  66. }
  67. boolean checkAk = ObjectUtils.equals(akSkDTO.getAccessKey(), accessKey);
  68. boolean checkSk = AkSkGenerator.checkKey(secretKey, akSkDTO.getSecretHashKey());
  69. if (!(checkAk && checkSk)) {
  70. throw new PaymentAccessAuthException("应用访问凭证校验失败");
  71. }
  72. // 2. 校验校验有效
  73. Long diffTime = System.currentTimeMillis() - timestamp;
  74. if (diffTime > SIGNATURE_EXPIRE_TIME) {
  75. throw new PaymentAccessAuthException("签名已过期");
  76. }
  77. // 3. 校验签名
  78. String validSignature = SignUtil.generateSignature(secretKey,
  79. String.valueOf(appId),
  80. accessKey,
  81. String.valueOf(timestamp));
  82. if (!ObjectUtils.equals(signature, validSignature)) {
  83. throw new PaymentAccessAuthException("签名校验失败");
  84. }
  85. /**
  86. * 生成并返回接入方access-token(交易凭证,用于后续的交易校验)
  87. */
  88. return this.generateAccessToken(appId);
  89. }
  90. /**
  91. * 校验支付交易接口请求token的有效性
  92. *
  93. * @param accessToken
  94. * @return
  95. */
  96. public boolean validateAccessToken(String accessToken) {
  97. /**
  98. * token 由支付平台生成且未失效 ===> accToken是有效的
  99. */
  100. String cacheKey = this.accessTokenCacheKey(accessToken);
  101. return redisService.exists(cacheKey);
  102. }
  103. /**
  104. * 生成交易事务id
  105. *
  106. * @return
  107. */
  108. public String transId(String accessToken) {
  109. Long appId = this.getAppIdFromAccessToken(accessToken);
  110. Assert.notNull(appId, "交易凭证失效");
  111. AppBaseResponseVO appBaseResponse = appService.appInfo(appId);
  112. Assert.notNull(appBaseResponse, "应用不存在,联系客服");
  113. Assert.isTrue(CommonStatusEnum.PUBLISHED.getStatusCode().equals(appBaseResponse.getStatus()), "应用未发布");
  114. /**
  115. * 基于雪花算法生成分布式下唯一id,作为交易分布式事务id,用于后续异常交易快速回滚
  116. */
  117. String transId = snowflakeIdGeneratorService.generateId();
  118. /**
  119. * 初始化TransId缓存,用于后续事务类操作校验
  120. */
  121. this.setPaymentTransIdCache(transId, appId, PaymentConstant.TrasnsIdStatusEnum.INIT);
  122. return transId;
  123. }
  124. /**
  125. * 走缓存获取分布式事务id详情
  126. *
  127. * @param transId
  128. * @return
  129. */
  130. public TransIdDTO getTransIdDTO(String transId) {
  131. String cacheKey = this.transIdCacheKey(transId);
  132. String cache = redisService.get(cacheKey);
  133. if (StringUtils.isBlank(cache)) {
  134. return null;
  135. }
  136. return JsonUtil.fromJson(cache, TransIdDTO.class);
  137. }
  138. /**
  139. * 刷新事务id状态
  140. *
  141. * @param transId
  142. * @param status
  143. */
  144. public void setPaymentTransIdCache(String transId, PaymentConstant.TrasnsIdStatusEnum status) {
  145. TransIdDTO transIdDTO = this.getTransIdDTO(transId);
  146. Assert.notNull(transIdDTO, "transId is null!");
  147. this.setPaymentTransIdCache(transId, transIdDTO.getAppId(), status);
  148. }
  149. /**
  150. * 分布式交易事务id添加orderId信息
  151. *
  152. * @param transId
  153. * @param orderId
  154. */
  155. public void setPaymentTransOrderInfo(String transId, String orderId) {
  156. TransIdDTO transIdDTO = this.getTransIdDTO(transId);
  157. Assert.notNull(transIdDTO, "transId is null!");
  158. transIdDTO.setOrderId(orderId);
  159. String cacheKey = this.transIdCacheKey(transId);
  160. redisService.set(cacheKey, JsonUtil.toJsonString(transIdDTO), RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getExpireTime());
  161. }
  162. /**
  163. * 刷新分布式交易事务id缓存
  164. *
  165. * @param transId 事务id
  166. * @param appId 应用id
  167. * @param status 状态
  168. */
  169. private void setPaymentTransIdCache(String transId, Long appId, PaymentConstant.TrasnsIdStatusEnum status) {
  170. String cacheKey = this.transIdCacheKey(transId);
  171. TransIdDTO transIdDTO = TransIdDTO.builder()
  172. .transId(transId)
  173. .appId(appId)
  174. .status(status.getStatus())
  175. .build();
  176. redisService.set(cacheKey, JsonUtil.toJsonString(transIdDTO), RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getExpireTime());
  177. }
  178. private String transIdCacheKey(String transId) {
  179. return RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getKey(transId);
  180. }
  181. /**
  182. * 获取交易凭证背后的应用信息
  183. *
  184. * @param accessToken
  185. * @return
  186. */
  187. public Long getAppIdFromAccessToken(String accessToken) {
  188. String accessTokenCacheKey = this.accessTokenCacheKey(accessToken);
  189. String cache = redisService.get(accessTokenCacheKey);
  190. if (StringUtils.isNotBlank(cache)) {
  191. return Long.valueOf(cache);
  192. }
  193. return null;
  194. }
  195. private String generateAccessToken(Long appId) {
  196. /**
  197. * 生成交易凭证
  198. */
  199. String accessToken = IDGenerateUtil.uuid();
  200. String accessTokenCacheKey = this.accessTokenCacheKey(accessToken);
  201. redisService.set(accessTokenCacheKey, String.valueOf(appId),
  202. RedisKeyEnum.PAYMENT_APP_ACCESS_TOKEN_CACHE.getExpireTime());
  203. return accessToken;
  204. }
  205. private String accessTokenCacheKey(String accessToken) {
  206. return RedisKeyEnum.PAYMENT_APP_ACCESS_TOKEN_CACHE.getKey(accessToken);
  207. }
  208. }