You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

851 lines
39 KiB

  1. package com.example.demo.serviceImpl.cash;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONArray;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.example.demo.domain.DTO.*;
  6. import com.example.demo.domain.vo.cash.BankVO;
  7. import com.example.demo.domain.vo.cash.CashCollection;
  8. import com.example.demo.domain.vo.coin.Result;
  9. import com.example.demo.mapper.cash.CashCollectionMapper;
  10. import com.example.demo.service.cash.BankService;
  11. import com.stripe.Stripe;
  12. import com.stripe.exception.StripeException;
  13. import com.stripe.model.BalanceTransaction;
  14. import com.stripe.model.Charge;
  15. import com.stripe.model.ChargeCollection;
  16. import com.stripe.param.ChargeListParams;
  17. import lombok.extern.slf4j.Slf4j;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.http.*;
  20. import org.springframework.scheduling.annotation.Scheduled;
  21. import org.springframework.stereotype.Service;
  22. import org.springframework.util.LinkedMultiValueMap;
  23. import org.springframework.util.MultiValueMap;
  24. import org.springframework.web.client.RestTemplate;
  25. import javax.crypto.Mac;
  26. import javax.crypto.spec.SecretKeySpec;
  27. import java.math.BigDecimal;
  28. import java.net.http.HttpResponse;
  29. import java.nio.charset.StandardCharsets;
  30. import java.security.MessageDigest;
  31. import java.security.NoSuchAlgorithmException;
  32. import java.text.SimpleDateFormat;
  33. import java.time.LocalDate;
  34. import java.time.ZoneId;
  35. import java.time.format.DateTimeFormatter;
  36. import java.util.*;
  37. /**
  38. * @program: gold-java
  39. * @ClassName BankServiceImpl
  40. * @description:
  41. * @author: Double
  42. * @create: 202511-21 10:46
  43. * @Version 1.0
  44. **/
  45. @Service
  46. @Slf4j
  47. public class BankServiceImpl implements BankService {
  48. @Autowired
  49. private CashCollectionMapper cashCollectionMapper;
  50. // 第三方API地址
  51. private static final String API_URL = "https://gateway.pa-sys.com/v1.1/reconciliation/519e26b2-8145-418c-b3e7-c1e88e52b946/settlement";
  52. // 签名密钥
  53. private static final String SECRET = "8987d1b8-1d82-4b15-af06-828d0b12076f";
  54. // 注入RestTemplate用于HTTP请求(需在Spring配置类中定义)
  55. private final RestTemplate restTemplate;
  56. public BankServiceImpl(RestTemplate restTemplate) {
  57. this.restTemplate = restTemplate;
  58. }
  59. //payment银行接口(单个)
  60. @Override
  61. public Result getPayment(BankDTO bankDTO) {
  62. try {
  63. // 1. 准备参数
  64. String settlementDate = bankDTO.getTime(); // 从BankDTO对象获取time作为settlement_date
  65. String network = "FPS"; // 固定值
  66. // 2. 生成签名
  67. Map<String, String> params = new TreeMap<>(); // 按key升序排序
  68. params.put("settlement_date", settlementDate);
  69. params.put("network", network);
  70. String signSource = buildQueryString(params) + SECRET;
  71. String sign = sha512(signSource);
  72. // 3. 构建form-data请求参数
  73. MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
  74. formData.add("settlement_date", settlementDate);
  75. formData.add("network", network);
  76. formData.add("sign", sign);
  77. // 4. 发送HTTP POST请求(优化:显式设置multipart/form-data的字符集)
  78. HttpHeaders headers = new HttpHeaders();
  79. // 补充charset=UTF-8,避免部分服务器对编码敏感
  80. headers.setContentType(new MediaType("multipart", "form-data", StandardCharsets.UTF_8));
  81. HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
  82. // 调用第三方API(使用配置好SSL协议的RestTemplate)
  83. ResponseEntity<String> response = restTemplate.exchange(
  84. API_URL,
  85. HttpMethod.POST,
  86. requestEntity,
  87. String.class
  88. );
  89. if (response.getStatusCode().is2xxSuccessful()) {
  90. String responseBody = response.getBody();
  91. log.info("第三方API响应: {}", responseBody);
  92. // 解析JSON获取payload.transactions数组
  93. JSONObject jsonObject = JSON.parseObject(responseBody);
  94. JSONArray transactions = jsonObject.getJSONObject("payload").getJSONArray("transactions");
  95. // 创建BankDTO并设置paymentDTOList
  96. BankVO bankVO = new BankVO();
  97. List<PaymentDTO> paymentDTOList;
  98. try {
  99. paymentDTOList = transactions.toJavaList(PaymentDTO.class);
  100. } catch (Exception e) {
  101. log.error("解析JSON响应时发生错误: {}", e.getMessage(), e);
  102. return Result.error("payment当天无数据请切换日期");
  103. }
  104. bankVO.setPaymentDTOList(paymentDTOList);
  105. // 获取订单号
  106. String orderNo = bankDTO.getOrderNo();
  107. // 如果订单号不为空,则进行匹配查找
  108. if (orderNo != null && !orderNo.isEmpty()) {
  109. boolean found = false;
  110. for (PaymentDTO paymentDTO : paymentDTOList) {
  111. if (orderNo.equals(paymentDTO.getMerchant_reference())) {
  112. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
  113. paymentDTO.setTime(sdf.parse(bankDTO.getTime()));
  114. paymentDTO.setOrder_amount(paymentDTO.getOrder_amount().multiply(BigDecimal.valueOf(100)));
  115. paymentDTO.setCharge(paymentDTO.getCharge().multiply(BigDecimal.valueOf(100)));
  116. paymentDTO.setNet_amount(paymentDTO.getNet_amount().multiply(BigDecimal.valueOf(100)));
  117. paymentDTO.setCurrency("港币HKD");
  118. bankVO.setPaymentDTO(paymentDTO);
  119. found = true;
  120. break;
  121. }
  122. }
  123. // 如果没有找到匹配的订单号,返回提示信息
  124. if (!found) {
  125. log.info("当前日期 {} 该订单号 {} 未查到", settlementDate, orderNo);
  126. // 可以根据业务需求进行相应处理,比如抛出异常或设置特定标识
  127. return Result.error("payment当前日期 " + settlementDate + " 该订单号 " + orderNo + " 未查到");
  128. }
  129. }
  130. CashCollection cashCollection = cashCollectionMapper.selectByGoldCoinOrderCode(orderNo);
  131. if (cashCollection == null) {
  132. return Result.error("金币系统当前日期 " + settlementDate + " 该订单号 " + orderNo + " 未查到");
  133. } else {
  134. cashCollectionMapper.updateByGoldCoinOrderCodeByPayment(bankVO.getPaymentDTO());
  135. }
  136. return Result.success(bankVO);
  137. } else {
  138. throw new RuntimeException("API请求失败,状态码: " + response.getStatusCodeValue());
  139. }
  140. } catch (Exception e) {
  141. log.error("payment银行接口处理失败", e);
  142. throw new RuntimeException("处理失败: " + e.getMessage());
  143. }
  144. }
  145. //payment银行接口(批量)
  146. @Override
  147. public Result paymentAuto(BankDTO bankDTO) {
  148. try {
  149. // 1. 准备参数
  150. String settlementDate = bankDTO.getTime(); // 从BankDTO对象获取time作为settlement_date
  151. String network = "FPS"; // 固定值
  152. // 2. 生成签名
  153. Map<String, String> params = new TreeMap<>(); // 按key升序排序
  154. params.put("settlement_date", settlementDate);
  155. params.put("network", network);
  156. String signSource = buildQueryString(params) + SECRET;
  157. String sign = sha512(signSource);
  158. // 3. 构建form-data请求参数
  159. MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
  160. formData.add("settlement_date", settlementDate);
  161. formData.add("network", network);
  162. formData.add("sign", sign);
  163. // 4. 发送HTTP POST请求(优化:显式设置multipart/form-data的字符集)
  164. HttpHeaders headers = new HttpHeaders();
  165. // 补充charset=UTF-8,避免部分服务器对编码敏感
  166. headers.setContentType(new MediaType("multipart", "form-data", StandardCharsets.UTF_8));
  167. HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
  168. // 调用第三方API(使用配置好SSL协议的RestTemplate)
  169. ResponseEntity<String> response = restTemplate.exchange(
  170. API_URL,
  171. HttpMethod.POST,
  172. requestEntity,
  173. String.class
  174. );
  175. if (response.getStatusCode().is2xxSuccessful()) {
  176. String responseBody = response.getBody();
  177. log.info("第三方API响应: {}", responseBody);
  178. // 解析JSON获取payload.transactions数组
  179. JSONObject jsonObject = JSON.parseObject(responseBody);
  180. JSONArray transactions = jsonObject.getJSONObject("payload").getJSONArray("transactions");
  181. // 创建BankVO并设置paymentDTOList
  182. BankVO bankVO = new BankVO();
  183. List<PaymentDTO> paymentDTOList;
  184. try {
  185. paymentDTOList = transactions.toJavaList(PaymentDTO.class);
  186. } catch (Exception e) {
  187. log.error("解析JSON响应时发生错误: {}", e.getMessage(), e);
  188. return Result.error("payment当天无数据请切换日期");
  189. }
  190. bankVO.setPaymentDTOList(paymentDTOList);
  191. // 收集处理信息
  192. List<String> messages = new ArrayList<>();
  193. // 对paymentDTOList中的每个元素进行处理
  194. List<PaymentDTO> processedPayments = new ArrayList<>();
  195. for (PaymentDTO paymentDTO : paymentDTOList) {
  196. try {
  197. // 格式化时间
  198. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
  199. paymentDTO.setTime(sdf.parse(settlementDate));
  200. // 金额转换(假设原始数据是元为单位,需要转为分为单位)
  201. paymentDTO.setOrder_amount(paymentDTO.getOrder_amount().multiply(BigDecimal.valueOf(100)));
  202. paymentDTO.setCharge(paymentDTO.getCharge().multiply(BigDecimal.valueOf(100)));
  203. paymentDTO.setNet_amount(paymentDTO.getNet_amount().multiply(BigDecimal.valueOf(100)));
  204. // 设置货币代码
  205. paymentDTO.setCurrency("港币HKD");
  206. // 获取订单号
  207. String orderNo = paymentDTO.getMerchant_reference();
  208. List<String> orderNoList = cashCollectionMapper.selectStripeList();
  209. // 检查当前订单号是否在列表中
  210. if (orderNoList.contains(orderNo)) {
  211. cashCollectionMapper.updateByGoldCoinOrderCodeByPayment(paymentDTO);
  212. processedPayments.add(paymentDTO);
  213. log.info("成功处理订单: {}", orderNo);
  214. messages.add("成功处理订单: " + orderNo);
  215. }
  216. else {
  217. log.warn("金币系统中未找到订单: {}", orderNo);
  218. messages.add("金币系统中未找到订单: " + orderNo);
  219. }
  220. } catch (Exception e) {
  221. messages.add("处理单个支付记录时发生错误: " + paymentDTO.getMerchant_reference() + ",错误信息: " + e.getMessage());
  222. log.error("处理单个支付记录时发生错误: {}", paymentDTO.getMerchant_reference(), e);
  223. }
  224. }
  225. // 设置处理成功的支付列表
  226. bankVO.setPaymentDTOList(processedPayments);
  227. bankVO.setMessage(messages);
  228. return Result.success(bankVO);
  229. } else {
  230. throw new RuntimeException("API请求失败,状态码: " + response.getStatusCodeValue());
  231. }
  232. } catch (Exception e) {
  233. log.error("payment银行接口处理失败", e);
  234. throw new RuntimeException("处理失败: " + e.getMessage());
  235. }
  236. }
  237. //stripe银行接口(批量)
  238. @Override
  239. public Result stripeAuto(BankDTO bankDTO) {
  240. try {
  241. // 设置Stripe API密钥
  242. Stripe.apiKey = "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";
  243. if (bankDTO.getSum() <= 0) {
  244. return Result.error("最大条数不能小于等于0");
  245. }
  246. // 收集处理信息
  247. List<String> messages = new ArrayList<>();
  248. // 从Stripe获取最近的收费记录(最多200条)
  249. List<Charge> allCharges = new ArrayList<>();
  250. String startingAfter = null;
  251. int totalLimit = bankDTO.getSum(); // 目标获取条数
  252. int pageSize = 100; // 单次最大获取条数
  253. do {
  254. // 计算当前页需查询的条数(最后一页可能不足100条)
  255. int currentPageSize = Math.min(pageSize, totalLimit - allCharges.size());
  256. if (currentPageSize <= 0) {
  257. break; // 已获取足够条数,停止
  258. }
  259. Long startTime = LocalDate.parse(bankDTO.getStartTime(), DateTimeFormatter.ofPattern("yyyyMMdd")).atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond();
  260. Long endTime = LocalDate.parse(bankDTO.getEndTime(), DateTimeFormatter.ofPattern("yyyyMMdd")).atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond();
  261. ChargeListParams.Created createdCondition = ChargeListParams.Created.builder()
  262. .setGte(startTime) // 大于等于开始时间
  263. .setLt(endTime) // 小于结束时间
  264. .build();
  265. ChargeListParams params = ChargeListParams.builder()
  266. .setLimit((long) currentPageSize)
  267. .setStartingAfter(startingAfter)
  268. .setCreated(createdCondition) // 加入时间筛选
  269. .build();
  270. try {
  271. // 执行分页查询
  272. ChargeCollection charges = Charge.list(params);
  273. List<Charge> currentPageData = charges.getData();
  274. allCharges.addAll(currentPageData);
  275. // 更新分页游标:若有下一页且未达200条,继续
  276. boolean hasMore = charges.getHasMore() && allCharges.size() < totalLimit;
  277. startingAfter = hasMore ? currentPageData.get(currentPageData.size() - 1).getId() : null;
  278. } catch (StripeException e) {
  279. log.error("Stripe 分页查询失败:" + e.getMessage());
  280. break; // 异常时停止查询,返回已获取的数据
  281. }
  282. } while (startingAfter != null);
  283. // 创建StripeDTO列表用于存储所有处理后的数据
  284. List<StripeDTO> stripeDTOList = new ArrayList<>();
  285. // 处理每一条Stripe数据
  286. for (Charge charge : allCharges) {
  287. try {
  288. // 获取charge对应的余额交易ID
  289. String balanceTransactionId = charge.getBalanceTransaction();
  290. // 通过余额交易ID获取详细信息
  291. BalanceTransaction balanceTransaction = BalanceTransaction.retrieve(balanceTransactionId);
  292. // 创建StripeDTO对象并填充所需数据点
  293. StripeDTO stripeDTO = new StripeDTO();
  294. // 从metadata中获取订单号
  295. if (charge.getMetadata() != null) {
  296. stripeDTO.setOrderNo(charge.getMetadata().get("order_no"));
  297. }
  298. // 设置余额交易ID
  299. stripeDTO.setBalanceTransaction(charge.getBalanceTransaction());
  300. // 设置付款币种和金额(来自charge)
  301. stripeDTO.setCurrency(charge.getCurrency().toUpperCase());
  302. stripeDTO.setAmount(String.valueOf(balanceTransaction.getAmount()));
  303. // 设置收款币种(来自charge)
  304. stripeDTO.setChargeCurrency(charge.getCurrency().toUpperCase());
  305. // 设置到账金额和手续费(来自balanceTransaction)
  306. stripeDTO.setNet(String.valueOf(balanceTransaction.getNet()));
  307. stripeDTO.setFee(String.valueOf(balanceTransaction.getFee()));
  308. // 设置到账币种(来自balanceTransaction)
  309. stripeDTO.setCurrency(balanceTransaction.getCurrency().toUpperCase());
  310. // 设置available_on日期
  311. if (balanceTransaction.getAvailableOn() != null) {
  312. long availableOnInSeconds = balanceTransaction.getAvailableOn();
  313. // 将Unix时间戳转换为Date对象
  314. Date availableOnDate = new Date(availableOnInSeconds * 1000L);
  315. stripeDTO.setAvailableOn(availableOnDate);
  316. }
  317. // 添加到列表中
  318. stripeDTOList.add(stripeDTO);
  319. // 如果订单号存在,且在selectStripeList返回的列表中,则更新数据库中的记录
  320. if (stripeDTO.getOrderNo() != null && !stripeDTO.getOrderNo().isEmpty()) {
  321. // 获取需要处理的订单号列表
  322. List<String> orderNoList = cashCollectionMapper.selectStripeList();
  323. // 检查当前订单号是否在列表中
  324. if (orderNoList.contains(stripeDTO.getOrderNo())) {
  325. cashCollectionMapper.updateByGoldCoinOrderCodeByStripe(stripeDTO);
  326. }
  327. }
  328. messages.add("成功处理订单: " + stripeDTO.getOrderNo());
  329. } catch (Exception e) {
  330. log.error("处理Stripe数据失败,chargeId: " + charge.getId(), e);
  331. // 继续处理其他数据,不中断整个流程
  332. }
  333. }
  334. // 创建响应VO对象
  335. BankVO bankVO = new BankVO();
  336. bankVO.setStripeDTOList(stripeDTOList);
  337. bankVO.setMessage(messages);
  338. return Result.success(bankVO);
  339. } catch (Exception e) {
  340. log.error("stripe银行接口处理失败", e);
  341. throw new RuntimeException("处理失败: " + e.getMessage());
  342. }
  343. }
  344. //stripe银行接口(单个)
  345. @Override
  346. public Result getStripe(BankDTO bankDTO) throws StripeException {
  347. try {
  348. // 设置Stripe API密钥
  349. Stripe.apiKey = "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";
  350. // 方式一:通过订单号查找最近数据
  351. String orderNo = bankDTO.getOrderNo();
  352. if (bankDTO.getSum() <= 0) {
  353. return Result.error("最大条数不能小于等于0");
  354. }
  355. // 从Stripe获取最近的收费记录(最多200条)
  356. List<Charge> allCharges = new ArrayList<>();
  357. String startingAfter = null;
  358. int totalLimit = bankDTO.getSum(); // 目标获取条数
  359. int pageSize = 100; // 单次最大获取条数
  360. do {
  361. // 计算当前页需查询的条数(最后一页可能不足100条)
  362. int currentPageSize = Math.min(pageSize, totalLimit - allCharges.size());
  363. if (currentPageSize <= 0) {
  364. break; // 已获取足够条数,停止
  365. }
  366. Long startTime = LocalDate.parse(bankDTO.getStartTime(), DateTimeFormatter.ofPattern("yyyyMMdd")).atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond();
  367. Long endTime = LocalDate.parse(bankDTO.getEndTime(), DateTimeFormatter.ofPattern("yyyyMMdd")).atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond();
  368. ChargeListParams.Created createdCondition = ChargeListParams.Created.builder()
  369. .setGte(startTime) // 大于等于开始时间
  370. .setLt(endTime) // 小于结束时间
  371. .build();
  372. ChargeListParams params = ChargeListParams.builder()
  373. .setLimit((long) currentPageSize)
  374. .setStartingAfter(startingAfter)
  375. .setCreated(createdCondition) // 加入时间筛选
  376. .build();
  377. try {
  378. // 执行分页查询
  379. ChargeCollection charges = Charge.list(params);
  380. List<Charge> currentPageData = charges.getData();
  381. allCharges.addAll(currentPageData);
  382. // 更新分页游标:若有下一页且未达200条,继续
  383. boolean hasMore = charges.getHasMore() && allCharges.size() < totalLimit;
  384. startingAfter = hasMore ? currentPageData.get(currentPageData.size() - 1).getId() : null;
  385. } catch (StripeException e) {
  386. log.error("Stripe 分页查询失败:" + e.getMessage());
  387. break; // 异常时停止查询,返回已获取的数据
  388. }
  389. } while (startingAfter != null);
  390. // 在获取的所有记录中查找匹配订单号的记录
  391. Charge matchedCharge = null;
  392. System.out.println(allCharges);
  393. for (Charge charge : allCharges) {
  394. // 从metadata中获取订单号进行匹配
  395. if (charge.getMetadata() != null) {
  396. String chargeOrderNo = charge.getMetadata().get("order_no");
  397. if (chargeOrderNo != null && orderNo.equals(chargeOrderNo)) {
  398. matchedCharge = charge;
  399. break;
  400. }
  401. }
  402. }
  403. // 如果未找到匹配的订单,返回错误信息
  404. if (matchedCharge == null) {
  405. return Result.error("未找到订单号 " + orderNo + " 的支付记录");
  406. }
  407. // 获取匹配到的charge对应的余额交易ID
  408. String balanceTransactionId = matchedCharge.getBalanceTransaction();
  409. // 通过余额交易ID获取详细信息
  410. BalanceTransaction balanceTransaction = BalanceTransaction.retrieve(balanceTransactionId);
  411. // 创建StripeDTO对象并填充所需数据点
  412. StripeDTO stripeDTO = new StripeDTO();
  413. // 设置订单号
  414. stripeDTO.setOrderNo(matchedCharge.getMetadata().get("order_no"));
  415. // 设置余额交易ID
  416. stripeDTO.setBalanceTransaction(matchedCharge.getBalanceTransaction());
  417. // 设置付款币种和金额(来自charge和来自balanceTransaction)
  418. stripeDTO.setCurrency(matchedCharge.getCurrency().toUpperCase());
  419. stripeDTO.setAmount(String.valueOf(balanceTransaction.getAmount()));
  420. // 设置收款币种(来自charge)
  421. stripeDTO.setChargeCurrency(matchedCharge.getCurrency().toUpperCase());
  422. // 设置到账金额和手续费(来自balanceTransaction)
  423. stripeDTO.setNet(String.valueOf(balanceTransaction.getNet()));
  424. stripeDTO.setFee(String.valueOf(balanceTransaction.getFee()));
  425. // 设置到账币种(来自balanceTransaction)
  426. stripeDTO.setCurrency(balanceTransaction.getCurrency().toUpperCase());
  427. // 设置available_on日期
  428. if (balanceTransaction.getAvailableOn() != null) {
  429. long availableOnInSeconds = balanceTransaction.getAvailableOn();
  430. // 将Unix时间戳转换为Date对象
  431. Date availableOnDate = new Date(availableOnInSeconds * 1000L);
  432. stripeDTO.setAvailableOn(availableOnDate);
  433. }
  434. // 创建响应VO对象
  435. BankVO bankVO = new BankVO();
  436. bankVO.setStripeDTO(stripeDTO);
  437. CashCollection cashCollection = cashCollectionMapper.selectByGoldCoinOrderCode(orderNo);
  438. if (cashCollection == null) {
  439. return Result.error("金币系统当前日期 " + " 该订单号 " + orderNo + " 未查到");
  440. } else {
  441. cashCollectionMapper.updateByGoldCoinOrderCodeByStripe(bankVO.getStripeDTO());
  442. }
  443. return Result.success(bankVO);
  444. } catch (Exception e) {
  445. log.error("stripe银行接口处理失败", e);
  446. throw new RuntimeException("处理失败: " + e.getMessage());
  447. }
  448. }
  449. //firstdata银行接口(批量)
  450. @Override
  451. public Result firstdataAuto(BankDTO bankDTO) {
  452. // 获取需要处理的订单号列表
  453. List<String> orderNoList = cashCollectionMapper.selectFirstdataList();
  454. // 存储处理结果的列表
  455. List<Result> results = new ArrayList<>();
  456. // 对每个订单执行getFirstdata方法
  457. for (String orderNo : orderNoList) {
  458. // 创建一个新的BankDTO实例,设置订单号
  459. BankDTO dto = new BankDTO();
  460. dto.setOrderNo(orderNo);
  461. // 调用getFirstdata方法处理单个订单
  462. Result result = getFirstdata(dto);
  463. results.add(result);
  464. }
  465. // 返回处理结果列表
  466. return Result.success(results);
  467. }
  468. //firstdata银行接口(单个)
  469. @Override
  470. public Result getFirstdata(BankDTO bankDTO) {
  471. try {
  472. CashCollection cashCollection = cashCollectionMapper.selectByBankCode(bankDTO.getOrderNo());
  473. if (cashCollection == null) {
  474. return Result.error("金币系统当前日期 " + " 该银行订单号 " + bankDTO.getOrderNo() + " 未查到");
  475. }
  476. // 获取签名参数
  477. FirstdataRequestDTO firstdataRequestDTO = generatePaymentAsiaSignature();
  478. // 构建请求URL,使用bankDTO中的orderNo
  479. String orderNo = bankDTO.getOrderNo();
  480. String url = "https://prod.api.firstdata.com/gateway/v2/payments/" + orderNo + "?storeId=4530056594";
  481. // 使用RestTemplate发送GET请求
  482. HttpHeaders headers = new HttpHeaders();
  483. headers.set("accept", "application/json");
  484. headers.set("Client-Request-Id", String.valueOf(firstdataRequestDTO.getClientRequestId()));
  485. headers.set("Api-Key", firstdataRequestDTO.getKey());
  486. headers.set("Timestamp", String.valueOf(firstdataRequestDTO.getTime()));
  487. headers.set("Message-Signature", firstdataRequestDTO.getHmacBase64());
  488. headers.set("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
  489. headers.set("Content-Type", "application/json");
  490. headers.set("Host", "prod.api.firstdata.com");
  491. headers.set("Connection", "keep-alive");
  492. HttpEntity<String> entity = new HttpEntity<>(headers);
  493. ResponseEntity<String> response = restTemplate.exchange(
  494. url,
  495. HttpMethod.GET,
  496. entity,
  497. String.class
  498. );
  499. // 解析响应数据
  500. // API返回的是对象格式,需要先解析为JSONObject
  501. JSONObject jsonObject = JSON.parseObject(response.getBody());
  502. // 创建BankVO对象并设置数据
  503. BankVO bankVO = new BankVO();
  504. List<String> message = new ArrayList<>();
  505. // 检查jsonObject是否为空或者是否包含错误信息
  506. if (jsonObject != null && !jsonObject.isEmpty()) {
  507. // 提取需要的字段
  508. String country = jsonObject.getString("country");
  509. String orderId = jsonObject.getString("orderId");
  510. // 提取currency和total
  511. JSONObject transactionAmount = jsonObject.getJSONObject("transactionAmount");
  512. String currency = transactionAmount != null ? transactionAmount.getString("currency") : null;
  513. Integer total = transactionAmount != null ? transactionAmount.getInteger("total") : null;
  514. // 创建FirstdataDTO对象并存储数据
  515. FirstdataDTO firstdataDTO = new FirstdataDTO();
  516. firstdataDTO.setCountry(country);
  517. firstdataDTO.setOrderId(orderId);
  518. firstdataDTO.setCurrency(currency);
  519. firstdataDTO.setTotal(total);
  520. // 根据要求设置amount为永久金币×100
  521. Integer amount = cashCollection.getPermanentGold();
  522. firstdataDTO.setAmount(amount);
  523. // 根据国家计算fee
  524. double feeValue;
  525. if ("Singapore".equals(country)) {
  526. // 新加坡:amount的值×百分之2.8加上20保留整数四舍五入
  527. feeValue = Math.round(amount * 0.028 + 20);
  528. } else {
  529. // 其他国家:amount的值×百分之3加20
  530. feeValue = Math.round(amount * 0.03 + 20);
  531. }
  532. firstdataDTO.setFee((int) feeValue);
  533. // net的值为amount减去fee
  534. firstdataDTO.setNet(amount - (int) feeValue);
  535. // 将firstdataDTO存入bankVO
  536. bankVO.setFirstdataDTO(firstdataDTO);
  537. // 创建一个简单的Map来存储提取的数据
  538. Map<String, Object> extractedData = new HashMap<>();
  539. extractedData.put("country", country);
  540. extractedData.put("orderId", orderId);
  541. extractedData.put("currency", currency);
  542. extractedData.put("total", total);
  543. extractedData.put("amount", amount);
  544. extractedData.put("fee", (int) feeValue);
  545. extractedData.put("net", amount - (int) feeValue);
  546. extractedData.put("success", true); // 添加成功标识
  547. // 将提取的数据转换为JSON字符串并添加到message列表
  548. message.add(JSON.toJSONString(extractedData, true));
  549. // 将message存入bankVO
  550. bankVO.setMessage(message);
  551. cashCollectionMapper.updateByGoldCoinOrderCodeByFirstdata(bankVO.getFirstdataDTO());
  552. return Result.success(bankVO);
  553. } else {
  554. // 没有数据时也添加失败标识
  555. Map<String, Object> emptyData = new HashMap<>();
  556. emptyData.put("success", false);
  557. emptyData.put("message", "No data found");
  558. message.add(JSON.toJSONString(emptyData, true));
  559. // 将message存入bankVO
  560. bankVO.setMessage(message);
  561. return Result.error("No data found");
  562. }
  563. } catch (Exception e) {
  564. log.error("Firstdata银行接口调用失败", e);
  565. // 在异常情况下也构建包含错误信息的message
  566. BankVO bankVO = new BankVO();
  567. List<String> message = new ArrayList<>();
  568. Map<String, Object> errorData = new HashMap<>();
  569. errorData.put("success", false);
  570. errorData.put("error", e.getMessage());
  571. message.add(JSON.toJSONString(errorData, true));
  572. bankVO.setMessage(message);
  573. return Result.error("Firstdata银行接口调用失败: " + e.getMessage());
  574. }
  575. }
  576. //IPAY银行接口(批量)
  577. @Override
  578. public Result ipayAuto(BankDTO bankDTO) {
  579. // 获取需要处理的订单号列表
  580. List<CashCollection> cashCollections = cashCollectionMapper.selectIpayList();
  581. BankVO bankVO = new BankVO();
  582. List<String> message = new ArrayList<>();
  583. // 对每个订单执行getFirstdata方法
  584. for (CashCollection cashCollection : cashCollections) {
  585. // 创建一个新的BankDTO实例,设置订单号
  586. Ipay88DTO ipay88DTO = new Ipay88DTO();
  587. ipay88DTO.setOrderNo(cashCollection.getOrderCode());
  588. // 将double先转为int再转为string
  589. double amountDouble = cashCollection.getPermanentGold() * 3.18;
  590. int amountInt = (int) amountDouble;
  591. ipay88DTO.setAmount(String.valueOf(amountInt));
  592. double feeDouble = cashCollection.getPermanentGold() * 3.18 * 0.0085;
  593. int feeInt = (int) feeDouble;
  594. ipay88DTO.setFee(String.valueOf(feeInt));
  595. double netDouble = cashCollection.getPermanentGold() * 3.18 - cashCollection.getPermanentGold() * 3.18 * 0.0085;
  596. int netInt = (int) netDouble;
  597. ipay88DTO.setNet(String.valueOf(netInt));
  598. cashCollectionMapper.updateByGoldCoinOrderCodeByIpay88(ipay88DTO);
  599. // 构建成功消息
  600. Map<String, Object> successData = new HashMap<>();
  601. successData.put("success", true);
  602. successData.put("orderNo", ipay88DTO.getOrderNo());
  603. successData.put("amount", ipay88DTO.getAmount());
  604. successData.put("fee", ipay88DTO.getFee());
  605. successData.put("net", ipay88DTO.getNet());
  606. message.add(JSON.toJSONString(successData, true));
  607. }
  608. // 将message存入bankVO
  609. bankVO.setMessage(message);
  610. // 返回处理结果列表
  611. return Result.success(bankVO);
  612. }
  613. // 银行自动处理接口(每天早上6点执行)
  614. @Scheduled(cron = "0 53 10 * * ?")
  615. @Override
  616. public Result bankAuto() {
  617. try {
  618. // 生成昨天的日期,格式为yyyyMMdd
  619. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
  620. LocalDate sevenDayAgo = LocalDate.now().minusDays(7);
  621. String sevenDayAgoStr = sevenDayAgo.format(formatter);
  622. LocalDate sixDayAgo = LocalDate.now().minusDays(6);
  623. String sixDayAgoStr = sixDayAgo.format(formatter);
  624. LocalDate fiveDayAgo = LocalDate.now().minusDays(5);
  625. String fiveDayAgoStr = fiveDayAgo.format(formatter);
  626. LocalDate fourDayAgo = LocalDate.now().minusDays(4);
  627. String fourDayAgoStr = fourDayAgo.format(formatter);
  628. LocalDate threeDayAgo = LocalDate.now().minusDays(3);
  629. String threeDayAgoStr = threeDayAgo.format(formatter);
  630. LocalDate twoDayAgo = LocalDate.now().minusDays(2);
  631. String twoDayAgoStr = twoDayAgo.format(formatter);
  632. LocalDate oneDayAgo = LocalDate.now().minusDays(1);
  633. String oneDayAgoStr = oneDayAgo.format(formatter);
  634. LocalDate today = LocalDate.now();
  635. String todayStr = today.format(formatter);
  636. // 创建BankDTO实例并设置时间
  637. BankDTO dto = new BankDTO();
  638. dto.setStartTime(fiveDayAgoStr);
  639. dto.setEndTime(todayStr);
  640. dto.setTime(sixDayAgoStr);
  641. dto.setSum(1000);
  642. // 依次调用各个自动处理方法
  643. Result paymentResult = paymentAuto(dto);
  644. Result stripeResult = stripeAuto(dto);
  645. Result firstdataResult = firstdataAuto(dto);
  646. Result ipayResult = ipayAuto(dto);
  647. // 创建响应VO对象并收集处理结果
  648. BankVO bankVO = new BankVO();
  649. List<String> messages = new ArrayList<>();
  650. // 收集各方法的处理结果信息
  651. messages.add("Payment Auto Result: " + (paymentResult != null ? paymentResult.toString() : "null"));
  652. messages.add("Stripe Auto Result: " + (stripeResult != null ? stripeResult.toString() : "null"));
  653. messages.add("Firstdata Auto Result: " + (firstdataResult != null ? firstdataResult.toString() : "null"));
  654. messages.add("Ipay Auto Result: " + (ipayResult != null ? ipayResult.toString() : "null"));
  655. bankVO.setMessage(messages);
  656. return Result.success(bankVO);
  657. } catch (Exception e) {
  658. log.error("bankAuto执行失败", e);
  659. return Result.error("bankAuto执行失败: " + e.getMessage());
  660. }
  661. }
  662. /**
  663. * 生成PaymentAsia API所需的签名
  664. *
  665. * @return 签名字符串
  666. */
  667. public FirstdataRequestDTO generatePaymentAsiaSignature() {
  668. try {
  669. String key = "3E04ZUCKFmQKrW0uoBa89QKIJWYoU9OX";
  670. String secret = "ZLtBPgfMIT4HXg25SoVuCyUQZ6GtSv9UFmDmYaoVSKS";
  671. // 生成ClientRequestId(1-10000000的随机数)
  672. long clientRequestId = new Random().nextInt(10000000) + 1;
  673. // 获取当前时间戳(毫秒)
  674. long time = System.currentTimeMillis();
  675. // 构造原始签名数据
  676. String rawSignature = key + clientRequestId + time;
  677. // 计算HMAC-SHA256
  678. Mac sha256Hmac = Mac.getInstance("HmacSHA256");
  679. SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
  680. sha256Hmac.init(secretKey);
  681. byte[] hmacBytes = sha256Hmac.doFinal(rawSignature.getBytes(StandardCharsets.UTF_8));
  682. // 转换为Base64编码
  683. String hmacBase64 = Base64.getEncoder().encodeToString(hmacBytes);
  684. // 赋值给DTO
  685. FirstdataRequestDTO firstdataRequestDTO = new FirstdataRequestDTO();
  686. firstdataRequestDTO.setKey(key);
  687. firstdataRequestDTO.setSecret(secret);
  688. firstdataRequestDTO.setClientRequestId(clientRequestId);
  689. firstdataRequestDTO.setTime(time);
  690. firstdataRequestDTO.setHmacBase64(hmacBase64);
  691. // Base64编码并返回签名
  692. return firstdataRequestDTO;
  693. } catch (Exception e) {
  694. throw new RuntimeException("生成PaymentAsia签名失败", e);
  695. }
  696. }
  697. /**
  698. * http_build_query的查询字符串key=value&key=value
  699. */
  700. private String buildQueryString(Map<String, String> params) {
  701. StringBuilder sb = new StringBuilder();
  702. for (Map.Entry<String, String> entry : params.entrySet()) {
  703. if (sb.length() > 0) {
  704. sb.append("&");
  705. }
  706. sb.append(entry.getKey()).append("=").append(entry.getValue());
  707. }
  708. return sb.toString();
  709. }
  710. /**
  711. * SHA512加密
  712. */
  713. private String sha512(String content) throws NoSuchAlgorithmException {
  714. MessageDigest md = MessageDigest.getInstance("SHA-512");
  715. byte[] bytes = md.digest(content.getBytes(StandardCharsets.UTF_8));
  716. // 转换为十六进制字符串
  717. StringBuilder hexStr = new StringBuilder();
  718. for (byte b : bytes) {
  719. String hex = Integer.toHexString(0xff & b);
  720. if (hex.length() == 1) {
  721. hexStr.append('0');
  722. }
  723. hexStr.append(hex);
  724. }
  725. return hexStr.toString();
  726. }
  727. }