package com.cyksj.service.recharge.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.cyksj.common.constant.Constant; import com.cyksj.common.exception.BusinessRuntimeException; import com.cyksj.common.task.GlobalThreadPoolTaskExecutor; import com.cyksj.common.util.Jsons; import com.cyksj.mapper.GoodsDonSkuMapper; import com.cyksj.mapper.GptRechargeCardKeyMapper; import com.cyksj.mapper.GroupsMapper; import com.cyksj.mapper.GroupsRelationMapper; import com.cyksj.mapper.OrderDonMapper; import com.cyksj.model.entity.CmsUser; import com.cyksj.model.entity.GoodsDonSku; import com.cyksj.model.entity.GptRechargeCardKey; import com.cyksj.model.entity.GroupsRelation; import com.cyksj.model.entity.GroupsTrips; import com.cyksj.model.entity.OrderDon; import com.cyksj.model.response.recharge.Auto17CardKeyCheckResponse; import com.cyksj.model.response.recharge.Auto17OrderCreateResponse; import com.cyksj.model.response.recharge.Auto17OrderStatusResponse; import com.cyksj.redis.RedisService; import com.cyksj.server.recharge.dto.TaskResult; import com.cyksj.service.chatgpt.GptUserRechargeRecordService; import com.cyksj.service.recharge.Auto17RechargeService; import com.cyksj.service.recharge.GptRechargeCardKeyChannelService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Locale; @Service @RequiredArgsConstructor @Slf4j public class Auto17RechargeServiceImpl implements Auto17RechargeService { private static final String CHECK_CARD_KEY_PATH = "/card-keys/check-usage"; private static final String CREATE_ORDER_PATH = "/orders"; private static final String ORDER_STATUS_PATH = "/orders/status/"; private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance(); private final GptRechargeCardKeyChannelService gptRechargeCardKeyChannelService; private final GroupsRelationMapper relationMapper; private final GroupsMapper groupsMapper; private final GoodsDonSkuMapper skuMapper; private final RedisService redisService; private final OrderDonMapper orderDonMapper; private final GptRechargeCardKeyMapper gptRechargeCardKeyMapper; private final GptUserRechargeRecordService gptUserRechargeRecordService; @Override public Auto17CardKeyCheckResponse validateCardKey(String cardKey) throws Exception { JSONObject requestBody = new JSONObject(); requestBody.putOpt("key", cardKey); String BASE_URL = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey); String url = buildApiBaseUrl(BASE_URL) + CHECK_CARD_KEY_PATH; HttpResponse response = HttpRequest.post(url) .header("Content-Type", "application/json") .timeout(Constant.CONNECT_MILLISECONDS) .body(requestBody.toString()) .execute(); Auto17CardKeyCheckResponse result = parseResponse(response.body(), Auto17CardKeyCheckResponse.class, "卡密校验"); if (!response.isOk() || result == null || !result.isSuccess() || result.getData() == null) { throw requestException(result != null ? result.getMessage() : null, "卡密校验失败"); } log.info("auto17卡密校验完成, exists:{}, used:{}, status:{}", result.getData().getExists(), result.getData().getUsed(), result.getData().getStatus()); return result; } @Override public String confirmRecharge(GroupsRelation relation, String cardKey, CmsUser cmsUser) { Long relationId = relation.getId(); try { Auto17CardKeyCheckResponse cardKeyResponse = validateCardKey(cardKey); if (!cardKeyResponse.getData().isAvailable()) { if (cmsUser == null) { throw BusinessRuntimeException.getInstance("系统繁忙,请重试"); } throw BusinessRuntimeException.getInstance("卡密:{0}不可用", cardKey); } } catch (BusinessRuntimeException e) { throw e; } catch (Exception e) { log.error("auto17校验卡密失败: {}", e.getMessage(), e); if (cmsUser == null) { throw BusinessRuntimeException.getInstance("系统繁忙,请重试"); } throw BusinessRuntimeException.getInstance("卡密校验失败,{0}", e.getMessage()); } try { Auto17OrderCreateResponse createResponse = submitOrder(cardKey, relation.getGptToken()); String taskId = createResponse.getData().getOrderNo(); String operator = null; if (cmsUser != null) { operator = cmsUser.getNickname(); } String password = relation.getPassword(); relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class) .set(GroupsRelation::getAccount, relation.getAccount()) .set(GroupsRelation::getOperator, operator) .set(StrUtil.isNotBlank(password), GroupsRelation::getPassword, password) .set(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharging) .set(GroupsRelation::getCardKey, cardKey) .set(GroupsRelation::getGptToken, relation.getGptToken()) .set(GroupsRelation::getGptTaskId, taskId) .eq(GroupsRelation::getId, relationId)); relation.setRechargeStatus(GroupsRelation.RechargeStatus.recharging); relation.setCardKey(cardKey); relation.setGptTaskId(taskId); relation.setOperator(operator); return taskId; } catch (Exception e) { setRechargeRelationStatusAutoError(relation); log.error("auto17提交充值任务失败: {}", e.getMessage(), e); if (cmsUser == null) { throw BusinessRuntimeException.getInstance("充值失败,请联系客服开通"); } throw BusinessRuntimeException.getInstance("充值失败,{0}", e.getMessage()); } } @Override public TaskResult getTaskResult(GroupsRelation relation) { try { Auto17OrderStatusResponse response = queryOrderStatus(relation.getCardKey(), relation.getGptTaskId()); Auto17OrderStatusResponse.OrderData data = response.getData(); boolean specialSuccess = isSpecialSuccess(data); TaskResult taskResult = convertTaskResult(data); String result = StrUtil.blankToDefault(taskResult.getError(), taskResult.getResult()); if (specialSuccess) { result = getStatusMessage(data); } resetGroupsRelationRechargeStatus(relation, taskResult.getStatus(), result, specialSuccess); return taskResult; } catch (Exception e) { log.error("auto17查询充值任务失败: {}", e.getMessage(), e); throw new RuntimeException("获取任务结果失败: " + e.getMessage(), e); } } private Auto17OrderCreateResponse submitOrder(String cardKey, String rechargePayload) throws Exception { JSONObject requestBody = new JSONObject(); requestBody.putOpt("cardKey", cardKey); requestBody.putOpt("token", parseTokenPayload(rechargePayload)); String BASE_URL = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey); String url = buildApiBaseUrl(BASE_URL) + CREATE_ORDER_PATH; HttpResponse response = HttpRequest.post(url) .header("Content-Type", "application/json") .timeout(Constant.CONNECT_MILLISECONDS) .body(requestBody.toString()) .execute(); Auto17OrderCreateResponse result = parseResponse(response.body(), Auto17OrderCreateResponse.class, "提交订单"); if (response.getStatus() < 200 || response.getStatus() >= 300 || result == null || !result.isSuccess() || result.getData() == null || StrUtil.isBlank(result.getData().getOrderNo())) { throw requestException(result != null ? result.getMessage() : null, "提交订单失败"); } log.info("auto17订单提交成功, orderNo:{}, status:{}", result.getData().getOrderNo(), result.getData().getStatus()); return result; } private Auto17OrderStatusResponse queryOrderStatus(String cardKey, String taskId) throws Exception { String BASE_URL = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey); String url = buildApiBaseUrl(BASE_URL) + ORDER_STATUS_PATH + encode(cardKey); HttpResponse response = HttpRequest.get(url) .timeout(Constant.CONNECT_MILLISECONDS) .execute(); Auto17OrderStatusResponse result = parseResponse(response.body(), Auto17OrderStatusResponse.class, "查询订单"); if (!response.isOk() || result == null || !result.isSuccess() || result.getData() == null) { throw requestException(result != null ? result.getMessage() : null, "查询订单失败"); } if (StrUtil.isNotBlank(taskId) && StrUtil.isNotBlank(result.getData().getOrderNo()) && !StrUtil.equals(taskId, result.getData().getOrderNo())) { throw BusinessRuntimeException.getInstance("订单号不匹配"); } log.info("auto17订单状态查询完成, orderNo:{}, status:{}, retryAttempt:{}", result.getData().getOrderNo(), result.getData().getStatus(), result.getData().getRetryAttempt()); return result; } private TaskResult convertTaskResult(Auto17OrderStatusResponse.OrderData data) { TaskResult result = new TaskResult(); String status = data.getStatus(); String message = getStatusMessage(data); if (isSpecialSuccess(data) || "success".equalsIgnoreCase(status)) { result.setStatus("completed"); result.setResult(message); } else if ("failed".equalsIgnoreCase(status)) { result.setStatus("failed"); result.setError(message); } else { result.setStatus("recharging"); result.setResult(StrUtil.blankToDefault(message, status)); } return result; } private boolean isSpecialSuccess(Auto17OrderStatusResponse.OrderData data) { if (data == null) { return false; } Auto17OrderStatusResponse.PaymentResult paymentResult = data.getPaymentResult(); if (paymentResult == null) { return false; } String message = StrUtil.blankToDefault(paymentResult.getFailure_detail(), ""); String failureReason = StrUtil.blankToDefault(data.getFailureReason(), ""); String details = message + " " + failureReason; String lowerCaseDetails = details.toLowerCase(Locale.ROOT); return lowerCaseDetails.contains("already succeeded after being previously confirmed") || lowerCaseDetails.contains("payment_intent_unexpected_state"); } private String getStatusMessage(Auto17OrderStatusResponse.OrderData data) { if (StrUtil.isNotBlank(data.getFailureReason())) { return data.getFailureReason(); } if (data.getPaymentResult() != null && StrUtil.isNotBlank(data.getPaymentResult().getMessage())) { return data.getPaymentResult().getMessage(); } return data.getStatus(); } private JSONObject parseTokenPayload(String rechargePayload) throws Exception { String payload = unwrapJsonString(rechargePayload); if (StrUtil.isBlank(payload)) { throw BusinessRuntimeException.getInstance("提交任务需要token"); } if (StrUtil.startWith(payload, "[")) { JSONArray array = JSONUtil.parseArray(payload); if (array.isEmpty()) { throw BusinessRuntimeException.getInstance("token不能为空"); } payload = unwrapJsonString(String.valueOf(array.get(0))); } if (!StrUtil.startWith(payload, "{")) { JSONObject token = new JSONObject(); token.putOpt("accessToken", payload); return token; } JSONObject token = JSONUtil.parseObj(payload); Object nestedToken = token.get("token"); if (nestedToken instanceof JSONObject) { return (JSONObject) nestedToken; } if (nestedToken instanceof CharSequence) { return parseTokenObject(nestedToken.toString()); } Object nestedPayload = token.get("payload"); if (nestedPayload instanceof JSONObject) { return (JSONObject) nestedPayload; } if (nestedPayload instanceof CharSequence) { return parseTokenObject(nestedPayload.toString()); } String accessToken = token.getStr("accessToken"); if (!isCompleteSession(token) && StrUtil.isNotBlank(accessToken)) { String accessTokenPayload = unwrapJsonString(accessToken); if (StrUtil.startWith(accessTokenPayload, "{")) { return JSONUtil.parseObj(accessTokenPayload); } } return token; } private JSONObject parseTokenObject(String value) throws Exception { String payload = unwrapJsonString(value); if (StrUtil.startWith(payload, "{")) { return JSONUtil.parseObj(payload); } JSONObject token = new JSONObject(); token.putOpt("accessToken", payload); return token; } private boolean isCompleteSession(JSONObject token) { return token.get("user") instanceof JSONObject || token.get("account") instanceof JSONObject || token.containsKey("expires") || token.containsKey("sessionToken") || token.containsKey("authProvider"); } private String unwrapJsonString(String value) throws Exception { String result = StrUtil.trim(value); for (int i = 0; i < 3 && StrUtil.startWith(result, "\""); i++) { result = StrUtil.trim(Jsons.parseObject(result, String.class)); } return result; } private String buildApiBaseUrl(String baseUrl) { if (StrUtil.isBlank(baseUrl)) { throw BusinessRuntimeException.getInstance("充值渠道域名未配置"); } baseUrl = StrUtil.removeSuffix(StrUtil.trim(baseUrl), "/"); if (StrUtil.endWithIgnoreCase(baseUrl, "/api")) { baseUrl = baseUrl + "/v1"; } else if (!StrUtil.endWithIgnoreCase(baseUrl, "/api/v1")) { baseUrl = baseUrl + "/api/v1"; } return baseUrl; } private String encode(String value) { try { return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private T parseResponse(String body, Class responseType, String apiName) { try { return Jsons.parseObject(body, responseType); } catch (Exception e) { throw BusinessRuntimeException.getInstance("{0}响应解析失败", apiName); } } private BusinessRuntimeException requestException(String message, String defaultMessage) { return BusinessRuntimeException.getInstance(StrUtil.blankToDefault(message, defaultMessage)); } private void setRechargeRelationStatusAutoError(GroupsRelation relation) { TASK_EXECUTOR.execute(() -> relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class) .set(GroupsRelation::getAccount, relation.getAccount()) .set(GroupsRelation::getGptToken, relation.getGptToken()) .set(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharge_error) .eq(GroupsRelation::getId, relation.getId()))); } private void resetGroupsRelationRechargeStatus(GroupsRelation relation, String status, String message, boolean specialSuccess) { if (!"completed".equals(status) && !"failed".equals(status)) { return; } relation = relationMapper.selectById(relation.getId()); if (relation == null || relation.getRechargeStatus() != GroupsRelation.RechargeStatus.recharging) { return; } boolean completed = "completed".equals(status); int rechargeNum = getRechargeNum(relation); Integer rechargeRemainNum = relation.getRechargeRemainNum(); GroupsRelation.RechargeStatus rechargeStatus = completed ? GroupsRelation.RechargeStatus.complete : GroupsRelation.RechargeStatus.recharge_error; GptRechargeCardKey cardKey = gptRechargeCardKeyMapper.selectOne(Wrappers.lambdaQuery(GptRechargeCardKey.class) .eq(GptRechargeCardKey::getCardKey, relation.getCardKey()) .last("limit 1")); if (!completed && !redisService.setNx( RedisService.key.GPT_RECHARGE_RECOVER_KEY.getName() + relation.getGptTaskId(), relation.getGptTaskId(), RedisService.key.GPT_RECHARGE_RECOVER_KEY.getTimeout())) { return; } if (!completed && cardKey != null) { gptRechargeCardKeyMapper.update(null, Wrappers.lambdaUpdate(GptRechargeCardKey.class) .set(GptRechargeCardKey::getStatus, Boolean.FALSE) .set(GptRechargeCardKey::getOrderId, null) .set(GptRechargeCardKey::getOrderNo, null) .eq(GptRechargeCardKey::getId, cardKey.getId()) .eq(GptRechargeCardKey::getStatus, Boolean.TRUE)); } Date startTime = relation.getStartTime(); DateTime expiryTime = null; if (completed && startTime == null) { GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId()); if (groupsTrips != null) { GoodsDonSku sku = skuMapper.selectById(groupsTrips.getSkuId()); if (sku != null) { startTime = DateTime.now(); if (sku.getDays() != null && sku.getDays() > 0) { expiryTime = DateUtil.offsetDay(startTime, sku.getDays()); } else if (sku.getMonths() != null) { expiryTime = DateUtil.offsetMonth(startTime, sku.getMonths()); } } } } int totalRechargeNum = relation.getRechargeNum() == null ? rechargeRemainNum + rechargeNum : relation.getRechargeNum(); int recoveredRemainNum = Math.min(totalRechargeNum, rechargeRemainNum + rechargeNum); int update = relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class) .set(!completed, GroupsRelation::getRechargeRemainNum, recoveredRemainNum) .set(GroupsRelation::getRechargeStatus, rechargeStatus) .set(expiryTime != null, GroupsRelation::getStartTime, startTime) .set(expiryTime != null, GroupsRelation::getExpiryTime, expiryTime) .set((!completed || specialSuccess) && StrUtil.isNotBlank(message), GroupsRelation::getRechargeErrorInfo, message) .eq(GroupsRelation::getId, relation.getId()) .eq(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharging)); if (update == 0 || !completed) { return; } String remark = StrUtil.isNotBlank(relation.getOperator()) ? "后台自动代充" : "自动代充"; gptUserRechargeRecordService.recordGptUserRechargeNum(relation.getCardKey(), relation.getUserId(), relation.getId(), relation.getAccount(), -rechargeNum, relation.getOperator(), remark); if (cardKey != null) { OrderDon order = orderDonMapper.selectById(cardKey.getOrderId()); if (order != null && order.getStatus() == OrderDon.Status.hasPayment) { order.setStatus(OrderDon.Status.complete); orderDonMapper.updateById(order); } } } private int getRechargeNum(GroupsRelation relation) { GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId()); if (groupsTrips == null) { return 1; } GoodsDonSku sku = skuMapper.selectById(groupsTrips.getSkuId()); return sku != null && Integer.valueOf(2).equals(sku.getRechargeType()) ? 12 : 1; } }