diff --git a/src/main/java/com/example/demo/service/cash/BankService.java b/src/main/java/com/example/demo/service/cash/BankService.java new file mode 100644 index 0000000..e42d599 --- /dev/null +++ b/src/main/java/com/example/demo/service/cash/BankService.java @@ -0,0 +1,30 @@ +package com.example.demo.service.cash; + + +import com.example.demo.domain.DTO.BankDTO; +import com.example.demo.domain.vo.cash.BankVO; +import com.example.demo.domain.vo.coin.Result; +import com.stripe.exception.StripeException; + +/** + * @program: gold-java + * @ClassName BankService + * @description: + * @author: Double + * @create: 2025−11-21 10:43 + * @Version 1.0 + **/ + +public interface BankService { + //payment银行接口(批量) + Result paymentAuto(BankDTO bankDTO); + + //payment银行接口(单个) + Result getPayment(BankDTO bankDTO); + + //stripe银行接口(批量) + Result stripeAuto(BankDTO bankDTO); + + //stripe银行接口(单个) + Result getStripe(BankDTO bankDTO) throws StripeException; +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/serviceImpl/cash/BankServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/cash/BankServiceImpl.java new file mode 100644 index 0000000..47c9667 --- /dev/null +++ b/src/main/java/com/example/demo/serviceImpl/cash/BankServiceImpl.java @@ -0,0 +1,520 @@ +package com.example.demo.serviceImpl.cash; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.example.demo.Util.DateConvertUtil; +import com.example.demo.domain.DTO.BankDTO; +import com.example.demo.domain.DTO.PaymentDTO; +import com.example.demo.domain.DTO.StripeDTO; +import com.example.demo.domain.vo.cash.BankVO; +import com.example.demo.domain.vo.cash.CashCollection; +import com.example.demo.domain.vo.coin.Result; +import com.example.demo.mapper.cash.CashCollectionMapper; +import com.example.demo.service.cash.BankService; +import com.stripe.Stripe; +import com.stripe.exception.StripeException; +import com.stripe.model.BalanceTransaction; +import com.stripe.model.Charge; +import com.stripe.model.ChargeCollection; +import com.stripe.param.ChargeListParams; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * @program: gold-java + * @ClassName BankServiceImpl + * @description: + * @author: Double + * @create: 2025−11-21 10:46 + * @Version 1.0 + **/ + +@Service +@Slf4j +public class BankServiceImpl implements BankService { + + @Autowired + private CashCollectionMapper cashCollectionMapper; + + // 第三方API地址 + private static final String API_URL = "https://gateway.pa-sys.com/v1.1/reconciliation/519e26b2-8145-418c-b3e7-c1e88e52b946/settlement"; + // 签名密钥 + private static final String SECRET = "8987d1b8-1d82-4b15-af06-828d0b12076f"; + + // 注入RestTemplate用于HTTP请求(需在Spring配置类中定义) + private final RestTemplate restTemplate; + + public BankServiceImpl(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + //payment银行接口(单个) + @Override + public Result getPayment(BankDTO bankDTO) { + try { + // 1. 准备参数 + String settlementDate = bankDTO.getTime(); // 从BankDTO对象获取time作为settlement_date + String network = "FPS"; // 固定值 + // 2. 生成签名 + Map params = new TreeMap<>(); // 按key升序排序 + params.put("settlement_date", settlementDate); + params.put("network", network); + + String signSource = buildQueryString(params) + SECRET; + String sign = sha512(signSource); + + // 3. 构建form-data请求参数 + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("settlement_date", settlementDate); + formData.add("network", network); + formData.add("sign", sign); + + // 4. 发送HTTP POST请求(优化:显式设置multipart/form-data的字符集) + HttpHeaders headers = new HttpHeaders(); + // 补充charset=UTF-8,避免部分服务器对编码敏感 + headers.setContentType(new MediaType("multipart", "form-data", StandardCharsets.UTF_8)); + HttpEntity> requestEntity = new HttpEntity<>(formData, headers); + + // 调用第三方API(使用配置好SSL协议的RestTemplate) + ResponseEntity response = restTemplate.exchange( + API_URL, + HttpMethod.POST, + requestEntity, + String.class + ); + + if (response.getStatusCode().is2xxSuccessful()) { + String responseBody = response.getBody(); + log.info("第三方API响应: {}", responseBody); + + // 解析JSON获取payload.transactions数组 + JSONObject jsonObject = JSON.parseObject(responseBody); + JSONArray transactions = jsonObject.getJSONObject("payload").getJSONArray("transactions"); + + // 创建BankDTO并设置paymentDTOList + BankVO bankVO = new BankVO(); + List paymentDTOList = transactions.toJavaList(PaymentDTO.class); + bankVO.setPaymentDTOList(paymentDTOList); + // 获取订单号 + String orderNo = bankDTO.getOrderNo(); + + // 如果订单号不为空,则进行匹配查找 + if (orderNo != null && !orderNo.isEmpty()) { + boolean found = false; + for (PaymentDTO paymentDTO : paymentDTOList) { + if (orderNo.equals(paymentDTO.getMerchant_reference())) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + paymentDTO.setTime(sdf.parse(bankDTO.getTime())); + paymentDTO.setOrder_amount(paymentDTO.getOrder_amount().multiply(BigDecimal.valueOf(100))); + paymentDTO.setCharge(paymentDTO.getCharge().multiply(BigDecimal.valueOf(100))); + paymentDTO.setNet_amount(paymentDTO.getNet_amount().multiply(BigDecimal.valueOf(100))); + paymentDTO.setCurrency("2"); + bankVO.setPaymentDTO(paymentDTO); + found = true; + break; + } + } + // 如果没有找到匹配的订单号,返回提示信息 + if (!found) { + log.info("当前日期 {} 该订单号 {} 未查到", settlementDate, orderNo); + // 可以根据业务需求进行相应处理,比如抛出异常或设置特定标识 + return Result.error("payment当前日期 " + settlementDate + " 该订单号 " + orderNo + " 未查到"); + } + } + + CashCollection cashCollection = cashCollectionMapper.selectByGoldCoinOrderCode(orderNo); + if (cashCollection == null) { + return Result.error("金币系统当前日期 " + settlementDate + " 该订单号 " + orderNo + " 未查到"); + } else { + cashCollectionMapper.updateByGoldCoinOrderCodeByPayment(bankVO.getPaymentDTO()); + } + return Result.success(bankVO); + } else { + throw new RuntimeException("API请求失败,状态码: " + response.getStatusCodeValue()); + } + + } catch (Exception e) { + log.error("payment银行接口处理失败", e); + throw new RuntimeException("处理失败: " + e.getMessage()); + } + } + + //payment银行接口(批量) + @Override + public Result paymentAuto(BankDTO bankDTO) { + try { + // 1. 准备参数 + String settlementDate = bankDTO.getTime(); // 从BankDTO对象获取time作为settlement_date + String network = "FPS"; // 固定值 + // 2. 生成签名 + Map params = new TreeMap<>(); // 按key升序排序 + params.put("settlement_date", settlementDate); + params.put("network", network); + + String signSource = buildQueryString(params) + SECRET; + String sign = sha512(signSource); + + // 3. 构建form-data请求参数 + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("settlement_date", settlementDate); + formData.add("network", network); + formData.add("sign", sign); + + // 4. 发送HTTP POST请求(优化:显式设置multipart/form-data的字符集) + HttpHeaders headers = new HttpHeaders(); + // 补充charset=UTF-8,避免部分服务器对编码敏感 + headers.setContentType(new MediaType("multipart", "form-data", StandardCharsets.UTF_8)); + HttpEntity> requestEntity = new HttpEntity<>(formData, headers); + + // 调用第三方API(使用配置好SSL协议的RestTemplate) + ResponseEntity response = restTemplate.exchange( + API_URL, + HttpMethod.POST, + requestEntity, + String.class + ); + + if (response.getStatusCode().is2xxSuccessful()) { + String responseBody = response.getBody(); + log.info("第三方API响应: {}", responseBody); + + // 解析JSON获取payload.transactions数组 + JSONObject jsonObject = JSON.parseObject(responseBody); + JSONArray transactions = jsonObject.getJSONObject("payload").getJSONArray("transactions"); + + // 创建BankVO并设置paymentDTOList + BankVO bankVO = new BankVO(); + List paymentDTOList = transactions.toJavaList(PaymentDTO.class); + bankVO.setPaymentDTOList(paymentDTOList); + // 收集处理信息 + List messages = new ArrayList<>(); + // 对paymentDTOList中的每个元素进行处理 + List processedPayments = new ArrayList<>(); + for (PaymentDTO paymentDTO : paymentDTOList) { + try { + + // 格式化时间 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + paymentDTO.setTime(sdf.parse(settlementDate)); + // 金额转换(假设原始数据是元为单位,需要转为分为单位) + paymentDTO.setOrder_amount(paymentDTO.getOrder_amount().multiply(BigDecimal.valueOf(100))); + paymentDTO.setCharge(paymentDTO.getCharge().multiply(BigDecimal.valueOf(100))); + paymentDTO.setNet_amount(paymentDTO.getNet_amount().multiply(BigDecimal.valueOf(100))); + // 设置货币代码 + paymentDTO.setCurrency("2"); + + // 获取订单号 + String orderNo = paymentDTO.getMerchant_reference(); + + // 查询金币系统中的订单 + CashCollection cashCollection = cashCollectionMapper.selectByGoldCoinOrderCode(orderNo); + if (cashCollection != null) { + // 更新金币系统中的订单信息 + cashCollectionMapper.updateByGoldCoinOrderCodeByPayment(paymentDTO); + processedPayments.add(paymentDTO); + log.info("成功处理订单: {}", orderNo); + messages.add("成功处理订单: " + orderNo); + } else { + log.warn("金币系统中未找到订单: {}", orderNo); + messages.add("金币系统中未找到订单: " + orderNo); + } + } catch (Exception e) { + log.error("处理单个支付记录时发生错误: {}", paymentDTO.getMerchant_reference(), e); + } + } + + // 设置处理成功的支付列表 + bankVO.setPaymentDTOList(processedPayments); + bankVO.setMessage(messages); + + return Result.success(bankVO); + } else { + throw new RuntimeException("API请求失败,状态码: " + response.getStatusCodeValue()); + } + + } catch (Exception e) { + log.error("payment银行接口处理失败", e); + throw new RuntimeException("处理失败: " + e.getMessage()); + } + } + + //stripe银行接口(批量) + @Override + public Result stripeAuto(BankDTO bankDTO) { + try { + // 设置Stripe API密钥 + Stripe.apiKey = "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"; + + // 从Stripe获取最近的收费记录(最多200条) + List allCharges = new ArrayList<>(); + String startingAfter = null; + int totalLimit = 200; // 目标获取条数 + int pageSize = 100; // 单次最大获取条数 + + do { + // 计算当前页需查询的条数(最后一页可能不足100条) + int currentPageSize = Math.min(pageSize, totalLimit - allCharges.size()); + if (currentPageSize <= 0) { + break; // 已获取足够条数,停止 + } + + // 构建分页参数 + ChargeListParams params = ChargeListParams.builder() + .setLimit((long) currentPageSize) + .setStartingAfter(startingAfter) + .build(); + + try { + // 执行分页查询 + ChargeCollection charges = Charge.list(params); + List currentPageData = charges.getData(); + allCharges.addAll(currentPageData); + + // 更新分页游标:若有下一页且未达200条,继续 + boolean hasMore = charges.getHasMore() && allCharges.size() < totalLimit; + startingAfter = hasMore ? currentPageData.get(currentPageData.size() - 1).getId() : null; + + } catch (StripeException e) { + log.error("Stripe 分页查询失败:" + e.getMessage()); + break; // 异常时停止查询,返回已获取的数据 + } + + } while (startingAfter != null); + + // 创建StripeDTO列表用于存储所有处理后的数据 + List stripeDTOList = new ArrayList<>(); + + // 处理每一条Stripe数据 + for (Charge charge : allCharges) { + try { + // 获取charge对应的余额交易ID + String balanceTransactionId = charge.getBalanceTransaction(); + + // 通过余额交易ID获取详细信息 + BalanceTransaction balanceTransaction = BalanceTransaction.retrieve(balanceTransactionId); + + // 创建StripeDTO对象并填充所需数据点 + StripeDTO stripeDTO = new StripeDTO(); + + // 从metadata中获取订单号 + if (charge.getMetadata() != null) { + stripeDTO.setOrderNo(charge.getMetadata().get("order_no")); + } + + // 设置余额交易ID + stripeDTO.setBalanceTransaction(charge.getBalanceTransaction()); + + // 设置付款币种和金额(来自charge) + stripeDTO.setCurrency(charge.getCurrency().toUpperCase()); + stripeDTO.setAmount(String.valueOf(charge.getAmount())); + + // 设置收款币种(来自charge) + stripeDTO.setChargeCurrency(charge.getCurrency().toUpperCase()); + + // 设置到账金额和手续费(来自balanceTransaction) + stripeDTO.setNet(String.valueOf(balanceTransaction.getNet())); + stripeDTO.setFee(String.valueOf(balanceTransaction.getFee())); + + // 设置到账币种(来自balanceTransaction) + stripeDTO.setCurrency(balanceTransaction.getCurrency().toUpperCase()); + + // 设置available_on日期 + if (balanceTransaction.getAvailableOn() != null) { + long availableOnInSeconds = balanceTransaction.getAvailableOn(); + // 将Unix时间戳转换为Date对象 + Date availableOnDate = new Date(availableOnInSeconds * 1000L); + stripeDTO.setAvailableOn(availableOnDate); + } + + // 添加到列表中 + stripeDTOList.add(stripeDTO); + + // 如果订单号存在,则更新数据库中的记录 + if (stripeDTO.getOrderNo() != null && !stripeDTO.getOrderNo().isEmpty()) { + CashCollection cashCollection = cashCollectionMapper.selectByGoldCoinOrderCode(stripeDTO.getOrderNo()); + if (cashCollection != null) { + cashCollectionMapper.updateByGoldCoinOrderCodeByStripe(stripeDTO); + } + } + } catch (Exception e) { + log.error("处理Stripe数据失败,chargeId: " + charge.getId(), e); + // 继续处理其他数据,不中断整个流程 + } + } + + // 创建响应VO对象 + BankVO bankVO = new BankVO(); + bankVO.setStripeDTOList(stripeDTOList); + + return Result.success(bankVO); + } catch (Exception e) { + log.error("stripe银行接口处理失败", e); + throw new RuntimeException("处理失败: " + e.getMessage()); + } + } + + + //stripe银行接口(单个) + @Override + public Result getStripe(BankDTO bankDTO) throws StripeException { + try { + // 设置Stripe API密钥 + Stripe.apiKey = "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"; + // 方式一:通过订单号查找最近数据 + String orderNo = bankDTO.getOrderNo(); + + if (orderNo == null || orderNo.isEmpty()) { + return Result.error("订单号为空"); + } + + // 从Stripe获取最近的收费记录(最多200条) + List allCharges = new ArrayList<>(); + String startingAfter = null; + int totalLimit = 200; // 目标获取条数 + int pageSize = 100; // 单次最大获取条数 + + do { + // 计算当前页需查询的条数(最后一页可能不足100条) + int currentPageSize = Math.min(pageSize, totalLimit - allCharges.size()); + if (currentPageSize <= 0) { + break; // 已获取足够条数,停止 + } + + // 构建分页参数 + ChargeListParams params = ChargeListParams.builder() + .setLimit((long) currentPageSize) + .setStartingAfter(startingAfter) + .build(); + + try { + // 执行分页查询 + ChargeCollection charges = Charge.list(params); + List currentPageData = charges.getData(); + allCharges.addAll(currentPageData); + + // 更新分页游标:若有下一页且未达200条,继续 + boolean hasMore = charges.getHasMore() && allCharges.size() < totalLimit; + startingAfter = hasMore ? currentPageData.get(currentPageData.size() - 1).getId() : null; + + } catch (StripeException e) { + log.error("Stripe 分页查询失败:" + e.getMessage()); + break; // 异常时停止查询,返回已获取的数据 + } + + } while (startingAfter != null); + + // 在获取的所有记录中查找匹配订单号的记录 + Charge matchedCharge = null; + for (Charge charge : allCharges) { + // 从metadata中获取订单号进行匹配 + if (charge.getMetadata() != null) { + String chargeOrderNo = charge.getMetadata().get("order_no"); + if (chargeOrderNo != null && orderNo.equals(chargeOrderNo)) { + matchedCharge = charge; + break; + } + } + } + + // 如果未找到匹配的订单,返回错误信息 + if (matchedCharge == null) { + return Result.error("未找到订单号 " + orderNo + " 的支付记录"); + } + + // 获取匹配到的charge对应的余额交易ID + String balanceTransactionId = matchedCharge.getBalanceTransaction(); + + // 通过余额交易ID获取详细信息 + BalanceTransaction balanceTransaction = BalanceTransaction.retrieve(balanceTransactionId); + + // 创建StripeDTO对象并填充所需数据点 + StripeDTO stripeDTO = new StripeDTO(); + // 设置订单号 + stripeDTO.setOrderNo(matchedCharge.getMetadata().get("order_no")); + // 设置余额交易ID + stripeDTO.setBalanceTransaction(matchedCharge.getBalanceTransaction()); + + // 设置付款币种和金额(来自charge) + stripeDTO.setCurrency(matchedCharge.getCurrency().toUpperCase()); + stripeDTO.setAmount(String.valueOf(matchedCharge.getAmount())); + + // 设置收款币种(来自charge) + stripeDTO.setChargeCurrency(matchedCharge.getCurrency().toUpperCase()); + + // 设置到账金额和手续费(来自balanceTransaction) + stripeDTO.setNet(String.valueOf(balanceTransaction.getNet())); + stripeDTO.setFee(String.valueOf(balanceTransaction.getFee())); + + // 设置到账币种(来自balanceTransaction) + stripeDTO.setCurrency(balanceTransaction.getCurrency().toUpperCase()); + + // 设置available_on日期 + if (balanceTransaction.getAvailableOn() != null) { + long availableOnInSeconds = balanceTransaction.getAvailableOn(); + // 将Unix时间戳转换为Date对象 + Date availableOnDate = new Date(availableOnInSeconds * 1000L); + stripeDTO.setAvailableOn(availableOnDate); + } + + // 创建响应VO对象 + BankVO bankVO = new BankVO(); + bankVO.setStripeDTO(stripeDTO); + CashCollection cashCollection = cashCollectionMapper.selectByGoldCoinOrderCode(orderNo); + if (cashCollection == null) { + return Result.error("金币系统当前日期 " + " 该订单号 " + orderNo + " 未查到"); + } else { + cashCollectionMapper.updateByGoldCoinOrderCodeByStripe(bankVO.getStripeDTO()); + } + return Result.success(bankVO); + } catch (Exception e) { + log.error("stripe银行接口处理失败", e); + throw new RuntimeException("处理失败: " + e.getMessage()); + } + } + + + /** + * http_build_query的查询字符串(key=value&key=value) + */ + private String buildQueryString(Map params) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(entry.getKey()).append("=").append(entry.getValue()); + } + return sb.toString(); + } + + /** + * SHA512加密 + */ + private String sha512(String content) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-512"); + byte[] bytes = md.digest(content.getBytes(StandardCharsets.UTF_8)); + // 转换为十六进制字符串 + StringBuilder hexStr = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexStr.append('0'); + } + hexStr.append(hex); + } + return hexStr.toString(); + } +} \ No newline at end of file