Auto17RechargeServiceImpl.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. package com.cyksj.service.recharge.impl;
  2. import cn.hutool.core.date.DateTime;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import cn.hutool.http.HttpRequest;
  6. import cn.hutool.http.HttpResponse;
  7. import cn.hutool.json.JSONArray;
  8. import cn.hutool.json.JSONObject;
  9. import cn.hutool.json.JSONUtil;
  10. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  11. import com.cyksj.common.constant.Constant;
  12. import com.cyksj.common.exception.BusinessRuntimeException;
  13. import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
  14. import com.cyksj.common.util.Jsons;
  15. import com.cyksj.mapper.GoodsDonSkuMapper;
  16. import com.cyksj.mapper.GptRechargeCardKeyMapper;
  17. import com.cyksj.mapper.GroupsMapper;
  18. import com.cyksj.mapper.GroupsRelationMapper;
  19. import com.cyksj.mapper.OrderDonMapper;
  20. import com.cyksj.model.entity.CmsUser;
  21. import com.cyksj.model.entity.GoodsDonSku;
  22. import com.cyksj.model.entity.GptRechargeCardKey;
  23. import com.cyksj.model.entity.GroupsRelation;
  24. import com.cyksj.model.entity.GroupsTrips;
  25. import com.cyksj.model.entity.OrderDon;
  26. import com.cyksj.model.response.recharge.Auto17CardKeyCheckResponse;
  27. import com.cyksj.model.response.recharge.Auto17OrderCreateResponse;
  28. import com.cyksj.model.response.recharge.Auto17OrderStatusResponse;
  29. import com.cyksj.redis.RedisService;
  30. import com.cyksj.server.recharge.dto.TaskResult;
  31. import com.cyksj.service.chatgpt.GptUserRechargeRecordService;
  32. import com.cyksj.service.recharge.Auto17RechargeService;
  33. import com.cyksj.service.recharge.GptRechargeCardKeyChannelService;
  34. import lombok.RequiredArgsConstructor;
  35. import lombok.extern.slf4j.Slf4j;
  36. import org.springframework.stereotype.Service;
  37. import java.io.UnsupportedEncodingException;
  38. import java.net.URLEncoder;
  39. import java.nio.charset.StandardCharsets;
  40. import java.util.Date;
  41. import java.util.Locale;
  42. @Service
  43. @RequiredArgsConstructor
  44. @Slf4j
  45. public class Auto17RechargeServiceImpl implements Auto17RechargeService {
  46. private static final String CHECK_CARD_KEY_PATH = "/card-keys/check-usage";
  47. private static final String CREATE_ORDER_PATH = "/orders";
  48. private static final String ORDER_STATUS_PATH = "/orders/status/";
  49. private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
  50. private final GptRechargeCardKeyChannelService gptRechargeCardKeyChannelService;
  51. private final GroupsRelationMapper relationMapper;
  52. private final GroupsMapper groupsMapper;
  53. private final GoodsDonSkuMapper skuMapper;
  54. private final RedisService redisService;
  55. private final OrderDonMapper orderDonMapper;
  56. private final GptRechargeCardKeyMapper gptRechargeCardKeyMapper;
  57. private final GptUserRechargeRecordService gptUserRechargeRecordService;
  58. @Override
  59. public Auto17CardKeyCheckResponse validateCardKey(String cardKey) throws Exception {
  60. JSONObject requestBody = new JSONObject();
  61. requestBody.putOpt("key", cardKey);
  62. String BASE_URL = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey);
  63. String url = buildApiBaseUrl(BASE_URL) + CHECK_CARD_KEY_PATH;
  64. HttpResponse response = HttpRequest.post(url)
  65. .header("Content-Type", "application/json")
  66. .timeout(Constant.CONNECT_MILLISECONDS)
  67. .body(requestBody.toString())
  68. .execute();
  69. Auto17CardKeyCheckResponse result = parseResponse(response.body(), Auto17CardKeyCheckResponse.class, "卡密校验");
  70. if (!response.isOk() || result == null || !result.isSuccess() || result.getData() == null) {
  71. throw requestException(result != null ? result.getMessage() : null, "卡密校验失败");
  72. }
  73. log.info("auto17卡密校验完成, exists:{}, used:{}, status:{}",
  74. result.getData().getExists(), result.getData().getUsed(), result.getData().getStatus());
  75. return result;
  76. }
  77. @Override
  78. public String confirmRecharge(GroupsRelation relation, String cardKey, CmsUser cmsUser) {
  79. Long relationId = relation.getId();
  80. try {
  81. Auto17CardKeyCheckResponse cardKeyResponse = validateCardKey(cardKey);
  82. if (!cardKeyResponse.getData().isAvailable()) {
  83. if (cmsUser == null) {
  84. throw BusinessRuntimeException.getInstance("系统繁忙,请重试");
  85. }
  86. throw BusinessRuntimeException.getInstance("卡密:{0}不可用", cardKey);
  87. }
  88. } catch (BusinessRuntimeException e) {
  89. throw e;
  90. } catch (Exception e) {
  91. log.error("auto17校验卡密失败: {}", e.getMessage(), e);
  92. if (cmsUser == null) {
  93. throw BusinessRuntimeException.getInstance("系统繁忙,请重试");
  94. }
  95. throw BusinessRuntimeException.getInstance("卡密校验失败,{0}", e.getMessage());
  96. }
  97. try {
  98. Auto17OrderCreateResponse createResponse = submitOrder(cardKey, relation.getGptToken());
  99. String taskId = createResponse.getData().getOrderNo();
  100. String operator = null;
  101. if (cmsUser != null) {
  102. operator = cmsUser.getNickname();
  103. }
  104. String password = relation.getPassword();
  105. relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
  106. .set(GroupsRelation::getAccount, relation.getAccount())
  107. .set(GroupsRelation::getOperator, operator)
  108. .set(StrUtil.isNotBlank(password), GroupsRelation::getPassword, password)
  109. .set(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharging)
  110. .set(GroupsRelation::getCardKey, cardKey)
  111. .set(GroupsRelation::getGptToken, relation.getGptToken())
  112. .set(GroupsRelation::getGptTaskId, taskId)
  113. .eq(GroupsRelation::getId, relationId));
  114. relation.setRechargeStatus(GroupsRelation.RechargeStatus.recharging);
  115. relation.setCardKey(cardKey);
  116. relation.setGptTaskId(taskId);
  117. relation.setOperator(operator);
  118. return taskId;
  119. } catch (Exception e) {
  120. setRechargeRelationStatusAutoError(relation);
  121. log.error("auto17提交充值任务失败: {}", e.getMessage(), e);
  122. if (cmsUser == null) {
  123. throw BusinessRuntimeException.getInstance("充值失败,请联系客服开通");
  124. }
  125. throw BusinessRuntimeException.getInstance("充值失败,{0}", e.getMessage());
  126. }
  127. }
  128. @Override
  129. public TaskResult getTaskResult(GroupsRelation relation) {
  130. try {
  131. Auto17OrderStatusResponse response = queryOrderStatus(relation.getCardKey(), relation.getGptTaskId());
  132. Auto17OrderStatusResponse.OrderData data = response.getData();
  133. boolean specialSuccess = isSpecialSuccess(data);
  134. TaskResult taskResult = convertTaskResult(data);
  135. String result = StrUtil.blankToDefault(taskResult.getError(), taskResult.getResult());
  136. if (specialSuccess) {
  137. result = getStatusMessage(data);
  138. }
  139. resetGroupsRelationRechargeStatus(relation, taskResult.getStatus(), result, specialSuccess);
  140. return taskResult;
  141. } catch (Exception e) {
  142. log.error("auto17查询充值任务失败: {}", e.getMessage(), e);
  143. throw new RuntimeException("获取任务结果失败: " + e.getMessage(), e);
  144. }
  145. }
  146. private Auto17OrderCreateResponse submitOrder(String cardKey, String rechargePayload) throws Exception {
  147. JSONObject requestBody = new JSONObject();
  148. requestBody.putOpt("cardKey", cardKey);
  149. requestBody.putOpt("token", parseTokenPayload(rechargePayload));
  150. String BASE_URL = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey);
  151. String url = buildApiBaseUrl(BASE_URL) + CREATE_ORDER_PATH;
  152. HttpResponse response = HttpRequest.post(url)
  153. .header("Content-Type", "application/json")
  154. .timeout(Constant.CONNECT_MILLISECONDS)
  155. .body(requestBody.toString())
  156. .execute();
  157. Auto17OrderCreateResponse result = parseResponse(response.body(), Auto17OrderCreateResponse.class, "提交订单");
  158. if (response.getStatus() < 200 || response.getStatus() >= 300 || result == null || !result.isSuccess()
  159. || result.getData() == null || StrUtil.isBlank(result.getData().getOrderNo())) {
  160. throw requestException(result != null ? result.getMessage() : null, "提交订单失败");
  161. }
  162. log.info("auto17订单提交成功, orderNo:{}, status:{}",
  163. result.getData().getOrderNo(), result.getData().getStatus());
  164. return result;
  165. }
  166. private Auto17OrderStatusResponse queryOrderStatus(String cardKey, String taskId) throws Exception {
  167. String BASE_URL = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey);
  168. String url = buildApiBaseUrl(BASE_URL) + ORDER_STATUS_PATH + encode(cardKey);
  169. HttpResponse response = HttpRequest.get(url)
  170. .timeout(Constant.CONNECT_MILLISECONDS)
  171. .execute();
  172. Auto17OrderStatusResponse result = parseResponse(response.body(), Auto17OrderStatusResponse.class, "查询订单");
  173. if (!response.isOk() || result == null || !result.isSuccess() || result.getData() == null) {
  174. throw requestException(result != null ? result.getMessage() : null, "查询订单失败");
  175. }
  176. if (StrUtil.isNotBlank(taskId) && StrUtil.isNotBlank(result.getData().getOrderNo())
  177. && !StrUtil.equals(taskId, result.getData().getOrderNo())) {
  178. throw BusinessRuntimeException.getInstance("订单号不匹配");
  179. }
  180. log.info("auto17订单状态查询完成, orderNo:{}, status:{}, retryAttempt:{}",
  181. result.getData().getOrderNo(), result.getData().getStatus(), result.getData().getRetryAttempt());
  182. return result;
  183. }
  184. private TaskResult convertTaskResult(Auto17OrderStatusResponse.OrderData data) {
  185. TaskResult result = new TaskResult();
  186. String status = data.getStatus();
  187. String message = getStatusMessage(data);
  188. if (isSpecialSuccess(data) || "success".equalsIgnoreCase(status)) {
  189. result.setStatus("completed");
  190. result.setResult(message);
  191. } else if ("failed".equalsIgnoreCase(status)) {
  192. result.setStatus("failed");
  193. result.setError(message);
  194. } else {
  195. result.setStatus("recharging");
  196. result.setResult(StrUtil.blankToDefault(message, status));
  197. }
  198. return result;
  199. }
  200. private boolean isSpecialSuccess(Auto17OrderStatusResponse.OrderData data) {
  201. if (data == null) {
  202. return false;
  203. }
  204. Auto17OrderStatusResponse.PaymentResult paymentResult = data.getPaymentResult();
  205. if (paymentResult == null) {
  206. return false;
  207. }
  208. String message = StrUtil.blankToDefault(paymentResult.getFailure_detail(), "");
  209. String failureReason = StrUtil.blankToDefault(data.getFailureReason(), "");
  210. String details = message + " " + failureReason;
  211. String lowerCaseDetails = details.toLowerCase(Locale.ROOT);
  212. return lowerCaseDetails.contains("already succeeded after being previously confirmed")
  213. || lowerCaseDetails.contains("payment_intent_unexpected_state");
  214. }
  215. private String getStatusMessage(Auto17OrderStatusResponse.OrderData data) {
  216. if (StrUtil.isNotBlank(data.getFailureReason())) {
  217. return data.getFailureReason();
  218. }
  219. if (data.getPaymentResult() != null && StrUtil.isNotBlank(data.getPaymentResult().getMessage())) {
  220. return data.getPaymentResult().getMessage();
  221. }
  222. return data.getStatus();
  223. }
  224. private JSONObject parseTokenPayload(String rechargePayload) throws Exception {
  225. String payload = unwrapJsonString(rechargePayload);
  226. if (StrUtil.isBlank(payload)) {
  227. throw BusinessRuntimeException.getInstance("提交任务需要token");
  228. }
  229. if (StrUtil.startWith(payload, "[")) {
  230. JSONArray array = JSONUtil.parseArray(payload);
  231. if (array.isEmpty()) {
  232. throw BusinessRuntimeException.getInstance("token不能为空");
  233. }
  234. payload = unwrapJsonString(String.valueOf(array.get(0)));
  235. }
  236. if (!StrUtil.startWith(payload, "{")) {
  237. JSONObject token = new JSONObject();
  238. token.putOpt("accessToken", payload);
  239. return token;
  240. }
  241. JSONObject token = JSONUtil.parseObj(payload);
  242. Object nestedToken = token.get("token");
  243. if (nestedToken instanceof JSONObject) {
  244. return (JSONObject) nestedToken;
  245. }
  246. if (nestedToken instanceof CharSequence) {
  247. return parseTokenObject(nestedToken.toString());
  248. }
  249. Object nestedPayload = token.get("payload");
  250. if (nestedPayload instanceof JSONObject) {
  251. return (JSONObject) nestedPayload;
  252. }
  253. if (nestedPayload instanceof CharSequence) {
  254. return parseTokenObject(nestedPayload.toString());
  255. }
  256. String accessToken = token.getStr("accessToken");
  257. if (!isCompleteSession(token) && StrUtil.isNotBlank(accessToken)) {
  258. String accessTokenPayload = unwrapJsonString(accessToken);
  259. if (StrUtil.startWith(accessTokenPayload, "{")) {
  260. return JSONUtil.parseObj(accessTokenPayload);
  261. }
  262. }
  263. return token;
  264. }
  265. private JSONObject parseTokenObject(String value) throws Exception {
  266. String payload = unwrapJsonString(value);
  267. if (StrUtil.startWith(payload, "{")) {
  268. return JSONUtil.parseObj(payload);
  269. }
  270. JSONObject token = new JSONObject();
  271. token.putOpt("accessToken", payload);
  272. return token;
  273. }
  274. private boolean isCompleteSession(JSONObject token) {
  275. return token.get("user") instanceof JSONObject
  276. || token.get("account") instanceof JSONObject
  277. || token.containsKey("expires")
  278. || token.containsKey("sessionToken")
  279. || token.containsKey("authProvider");
  280. }
  281. private String unwrapJsonString(String value) throws Exception {
  282. String result = StrUtil.trim(value);
  283. for (int i = 0; i < 3 && StrUtil.startWith(result, "\""); i++) {
  284. result = StrUtil.trim(Jsons.parseObject(result, String.class));
  285. }
  286. return result;
  287. }
  288. private String buildApiBaseUrl(String baseUrl) {
  289. if (StrUtil.isBlank(baseUrl)) {
  290. throw BusinessRuntimeException.getInstance("充值渠道域名未配置");
  291. }
  292. baseUrl = StrUtil.removeSuffix(StrUtil.trim(baseUrl), "/");
  293. if (StrUtil.endWithIgnoreCase(baseUrl, "/api")) {
  294. baseUrl = baseUrl + "/v1";
  295. } else if (!StrUtil.endWithIgnoreCase(baseUrl, "/api/v1")) {
  296. baseUrl = baseUrl + "/api/v1";
  297. }
  298. return baseUrl;
  299. }
  300. private String encode(String value) {
  301. try {
  302. return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
  303. } catch (UnsupportedEncodingException e) {
  304. throw new RuntimeException(e);
  305. }
  306. }
  307. private <T> T parseResponse(String body, Class<T> responseType, String apiName) {
  308. try {
  309. return Jsons.parseObject(body, responseType);
  310. } catch (Exception e) {
  311. throw BusinessRuntimeException.getInstance("{0}响应解析失败", apiName);
  312. }
  313. }
  314. private BusinessRuntimeException requestException(String message, String defaultMessage) {
  315. return BusinessRuntimeException.getInstance(StrUtil.blankToDefault(message, defaultMessage));
  316. }
  317. private void setRechargeRelationStatusAutoError(GroupsRelation relation) {
  318. TASK_EXECUTOR.execute(() -> relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
  319. .set(GroupsRelation::getAccount, relation.getAccount())
  320. .set(GroupsRelation::getGptToken, relation.getGptToken())
  321. .set(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharge_error)
  322. .eq(GroupsRelation::getId, relation.getId())));
  323. }
  324. private void resetGroupsRelationRechargeStatus(GroupsRelation relation, String status, String message, boolean specialSuccess) {
  325. if (!"completed".equals(status) && !"failed".equals(status)) {
  326. return;
  327. }
  328. relation = relationMapper.selectById(relation.getId());
  329. if (relation == null || relation.getRechargeStatus() != GroupsRelation.RechargeStatus.recharging) {
  330. return;
  331. }
  332. boolean completed = "completed".equals(status);
  333. int rechargeNum = getRechargeNum(relation);
  334. Integer rechargeRemainNum = relation.getRechargeRemainNum();
  335. GroupsRelation.RechargeStatus rechargeStatus = completed
  336. ? GroupsRelation.RechargeStatus.complete
  337. : GroupsRelation.RechargeStatus.recharge_error;
  338. GptRechargeCardKey cardKey = gptRechargeCardKeyMapper.selectOne(Wrappers.lambdaQuery(GptRechargeCardKey.class)
  339. .eq(GptRechargeCardKey::getCardKey, relation.getCardKey())
  340. .last("limit 1"));
  341. if (!completed && !redisService.setNx(
  342. RedisService.key.GPT_RECHARGE_RECOVER_KEY.getName() + relation.getGptTaskId(),
  343. relation.getGptTaskId(), RedisService.key.GPT_RECHARGE_RECOVER_KEY.getTimeout())) {
  344. return;
  345. }
  346. if (!completed && cardKey != null) {
  347. gptRechargeCardKeyMapper.update(null, Wrappers.lambdaUpdate(GptRechargeCardKey.class)
  348. .set(GptRechargeCardKey::getStatus, Boolean.FALSE)
  349. .set(GptRechargeCardKey::getOrderId, null)
  350. .set(GptRechargeCardKey::getOrderNo, null)
  351. .eq(GptRechargeCardKey::getId, cardKey.getId())
  352. .eq(GptRechargeCardKey::getStatus, Boolean.TRUE));
  353. }
  354. Date startTime = relation.getStartTime();
  355. DateTime expiryTime = null;
  356. if (completed && startTime == null) {
  357. GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
  358. if (groupsTrips != null) {
  359. GoodsDonSku sku = skuMapper.selectById(groupsTrips.getSkuId());
  360. if (sku != null) {
  361. startTime = DateTime.now();
  362. if (sku.getDays() != null && sku.getDays() > 0) {
  363. expiryTime = DateUtil.offsetDay(startTime, sku.getDays());
  364. } else if (sku.getMonths() != null) {
  365. expiryTime = DateUtil.offsetMonth(startTime, sku.getMonths());
  366. }
  367. }
  368. }
  369. }
  370. int totalRechargeNum = relation.getRechargeNum() == null ? rechargeRemainNum + rechargeNum : relation.getRechargeNum();
  371. int recoveredRemainNum = Math.min(totalRechargeNum, rechargeRemainNum + rechargeNum);
  372. int update = relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
  373. .set(!completed, GroupsRelation::getRechargeRemainNum, recoveredRemainNum)
  374. .set(GroupsRelation::getRechargeStatus, rechargeStatus)
  375. .set(expiryTime != null, GroupsRelation::getStartTime, startTime)
  376. .set(expiryTime != null, GroupsRelation::getExpiryTime, expiryTime)
  377. .set((!completed || specialSuccess) && StrUtil.isNotBlank(message), GroupsRelation::getRechargeErrorInfo, message)
  378. .eq(GroupsRelation::getId, relation.getId())
  379. .eq(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharging));
  380. if (update == 0 || !completed) {
  381. return;
  382. }
  383. String remark = StrUtil.isNotBlank(relation.getOperator()) ? "后台自动代充" : "自动代充";
  384. gptUserRechargeRecordService.recordGptUserRechargeNum(relation.getCardKey(), relation.getUserId(),
  385. relation.getId(), relation.getAccount(), -rechargeNum, relation.getOperator(), remark);
  386. if (cardKey != null) {
  387. OrderDon order = orderDonMapper.selectById(cardKey.getOrderId());
  388. if (order != null && order.getStatus() == OrderDon.Status.hasPayment) {
  389. order.setStatus(OrderDon.Status.complete);
  390. orderDonMapper.updateById(order);
  391. }
  392. }
  393. }
  394. private int getRechargeNum(GroupsRelation relation) {
  395. GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
  396. if (groupsTrips == null) {
  397. return 1;
  398. }
  399. GoodsDonSku sku = skuMapper.selectById(groupsTrips.getSkuId());
  400. return sku != null && Integer.valueOf(2).equals(sku.getRechargeType()) ? 12 : 1;
  401. }
  402. }