|
|
@@ -0,0 +1,611 @@
|
|
|
+package com.crm.settlement.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.crm.rely.backend.core.constant.Constants;
|
|
|
+import com.crm.rely.backend.core.dto.base.BaseResultDto;
|
|
|
+import com.crm.rely.backend.core.exception.PayValidatedException;
|
|
|
+import com.crm.rely.backend.core.exception.ServiceException;
|
|
|
+import com.crm.rely.backend.core.pojo.table.SysConfigTable;
|
|
|
+import com.crm.rely.backend.model.entity.settlement.vaultody.callback.*;
|
|
|
+import com.crm.rely.backend.model.pojo.table.*;
|
|
|
+import com.crm.rely.backend.service.MqSendService;
|
|
|
+import com.crm.rely.backend.service.RedisService;
|
|
|
+import com.crm.rely.backend.util.HAMCSHA256Util;
|
|
|
+import com.crm.rely.backend.util.HttpUtil;
|
|
|
+import com.crm.rely.backend.util.MapUtil;
|
|
|
+import com.crm.rely.backend.model.constant.ConfigConstants;
|
|
|
+import com.crm.rely.backend.model.constant.SettlementConstant;
|
|
|
+import com.crm.rely.backend.model.dto.vaultody.TxResult;
|
|
|
+
|
|
|
+import com.crm.rely.backend.model.entity.property.vaultody.VaultodyPayPropertyEntity;
|
|
|
+import com.crm.rely.backend.model.entity.property.vaultody.VaultodyPayWithdrawAddressPropertyEntity;
|
|
|
+import com.crm.rely.backend.model.entity.property.vaultody.VaultodyPayWithdrawPropertyEntity;
|
|
|
+import com.crm.rely.backend.model.entity.settlement.vaultody.VaultodyDepositRequestEntity;
|
|
|
+import com.crm.rely.backend.model.entity.settlement.vaultody.VaultodyWithdrawRequestEntity;
|
|
|
+import com.crm.rely.backend.model.util.EncryptionHashQueryUtil;
|
|
|
+import com.crm.settlement.dao.mapper.FinanceDepositAddressMapper;
|
|
|
+import com.crm.settlement.dao.repository.FinanceDepositAddressRepository;
|
|
|
+import com.crm.settlement.dao.repository.SettlementDepositRecordRepository;
|
|
|
+import com.crm.settlement.dao.repository.SettlementWithdrawRecordRepository;
|
|
|
+import com.crm.settlement.dao.repository.SysConfigRepository;
|
|
|
+import com.crm.settlement.service.*;
|
|
|
+import com.crm.settlement.service.impl.base.BaseSettlementServiceImpl;
|
|
|
+import com.google.common.base.Strings;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.jsoup.Connection;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Propagation;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.crypto.Mac;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.atomic.AtomicReference;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class VaultodyServiceImpl extends BaseSettlementServiceImpl implements VaultodyService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FinanceDepositAddressMapper depositAddressMapper;
|
|
|
+ @Autowired
|
|
|
+ private FinanceDepositAddressRepository depositAddressRepository;
|
|
|
+ @Autowired
|
|
|
+ private RedisService redisService;
|
|
|
+ @Autowired
|
|
|
+ private SysConfigService sysConfigService;
|
|
|
+ @Autowired
|
|
|
+ private SysConfigRepository sysConfigRepository;
|
|
|
+ @Autowired
|
|
|
+ private SettlementDepositRecordRepository settlementDepositRecordRepository;
|
|
|
+ @Autowired
|
|
|
+ private SettlementWithdrawRecordRepository settlementWithdrawRecordRepository;
|
|
|
+ @Autowired
|
|
|
+ private MqSendService mqSendService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public BaseResultDto deposit(VaultodyDepositRequestEntity request) throws Exception {
|
|
|
+ verifyRequestTimeAndAmount(request);
|
|
|
+ String signString = getSignString(request);
|
|
|
+ verifySign(request.getSign(), signString, request.getMerchantId());
|
|
|
+
|
|
|
+ SysRemittanceChannelTable channelTable = getPayChannel(SettlementConstant.VAULTODY_PAY_KEY);
|
|
|
+ VaultodyPayPropertyEntity property = getPayProperty(channelTable, VaultodyPayPropertyEntity.class);
|
|
|
+ String address = reallocationAddress(request, property);
|
|
|
+ // 保存记录
|
|
|
+ saveDepositRecord(request, channelTable.getProperty(), address);
|
|
|
+ return BaseResultDto.success(address);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 回调处理数据
|
|
|
+ *
|
|
|
+ * @param
|
|
|
+ * @throws ServiceException
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public BaseResultDto callback(String content, String signature) throws Exception {
|
|
|
+ log.info("Vaultody回调通知开始:{},signature:{}", content, signature);
|
|
|
+
|
|
|
+ if (Strings.isNullOrEmpty(content) || Strings.isNullOrEmpty(signature)) {
|
|
|
+ log.error("Vaultody回调通知处理失败,数据为空,content:{},signature:{}", content, signature);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ VaultodyPayPropertyEntity propertyEntity = getPayProperty(SettlementConstant.VAULTODY_PAY_KEY, VaultodyPayPropertyEntity.class);
|
|
|
+ if (!isCallbackValid(content, propertyEntity.getWebhooksPassphrase(), signature)) {
|
|
|
+ //打印日志 详细说明
|
|
|
+ log.error("callback valid error");
|
|
|
+ throw new ServiceException("callback valid error");
|
|
|
+ }
|
|
|
+ VaultodyCallbackEntity entity = JSON.parseObject(content, VaultodyCallbackEntity.class);
|
|
|
+ VaultodyDataEntity dataEntity = entity.getData();
|
|
|
+ String event = dataEntity.getEvent();
|
|
|
+ VaultodyItemEntity itemEntity = dataEntity.getItem();
|
|
|
+ VaultodyTokenEntity tokenEntity = itemEntity.getToken();
|
|
|
+ String transactionId = itemEntity.getTransactionId();
|
|
|
+ VaultodyMinedInBlockEntity blockEntity = itemEntity.getMinedInBlock();
|
|
|
+ if (blockEntity == null || StringUtils.isEmpty(blockEntity.getHash())) {
|
|
|
+ log.error("hash is empty,{}", JSON.toJSONString(blockEntity));
|
|
|
+ throw new ServiceException("hash is empty");
|
|
|
+ }
|
|
|
+ String address = itemEntity.getAddress();
|
|
|
+ if (StringUtils.isEmpty(address)) {
|
|
|
+ log.error("Vaultody通知回调:address为空,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+// String direction = itemEntity.getDirection();
|
|
|
+ // 只处理入账
|
|
|
+// if (!"INCOMING_CONFIRMED_TOKEN_TX".equals(event) && !"incoming".equals(direction)) {
|
|
|
+// log.error("Vaultody通知回调:不是入账类型,{}", content);
|
|
|
+// return BaseResultDto.error();
|
|
|
+// }
|
|
|
+
|
|
|
+ if (StringUtils.isEmpty(event)) {
|
|
|
+ log.error("Vaultody通知回调:event为空,暂不处理,{}", JSON.toJSONString(entity));
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ if (!"INCOMING_CONFIRMED_TOKEN_TX".equals(event)) {
|
|
|
+ log.error("Vaultody通知回调:不是入账类型,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ FinanceDepositAddressTable addressTable = depositAddressRepository.findByAddressOrderByAddTimeDesc(address);
|
|
|
+ if (addressTable == null || StringUtils.isEmpty(addressTable.getSerial())) {
|
|
|
+ log.error("Vaultody通知回调:addressTable为空,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ SettlementDepositRecordTable recordTable = settlementDepositRecordRepository.findFirstByRequestSerialAndChannelCode(address, SettlementConstant.VAULTODY_PAY_KEY);
|
|
|
+
|
|
|
+ if (recordTable == null || recordTable.getId() == null) {
|
|
|
+ log.error("Vaultody通知回调:未找到对应的请求记录,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ String currency = recordTable.getCurrency();
|
|
|
+
|
|
|
+ TxResult txResult;
|
|
|
+ try {
|
|
|
+
|
|
|
+ txResult = EncryptionHashQueryUtil.query(itemEntity.getTransactionId(), itemEntity.getBlockchain(),
|
|
|
+ currency);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("Vaultody通知回调:查询交易hash结果失败,{}", JSON.toJSONString(entity));
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (txResult == null || StringUtils.isEmpty(txResult.getTo()) || StringUtils.isEmpty(itemEntity.getAddress())) {
|
|
|
+ log.error("Vaultody通知回调:通过hash:{} 未查询到结果,{}", blockEntity.getHash(), JSON.toJSONString(entity));
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ if (!txResult.getTo().equals(itemEntity.getAddress())) {
|
|
|
+ log.error("Vaultody通知回调:通过hash:{} 比对地址不相同,{}", txResult.getTo(), itemEntity.getAddress());
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ if (!txResult.getStatus().equals(itemEntity.getStatus())) {
|
|
|
+ log.error("Vaultody通知回调:通过hash:{} 比对状态不相同,{}", txResult.getStatus(), itemEntity.getStatus());
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ if (!txResult.getTimestamp().equals(blockEntity.getTimestamp())) {
|
|
|
+ log.error("Vaultody通知回调:通过hash:{} 比对交易时间不相同,{}", txResult.getTimestamp(), blockEntity.getTimestamp());
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ if (txResult.getAmount().compareTo(new BigDecimal(tokenEntity.getTokensAmount())) != 0) {
|
|
|
+ log.error("Vaultody通知回调:通过hash:{} 比对金额不相同,{}", txResult.getAmount(), itemEntity.getAmount());
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ addressTable.setContent(txResult.getContent());
|
|
|
+ addressTable.setIsUsed(false);
|
|
|
+ depositAddressRepository.save(addressTable);
|
|
|
+
|
|
|
+ int status = ConfigConstants.CALLBACE_PROCESSING_STATUS;
|
|
|
+ if ("success".equals(txResult.getStatus())) {
|
|
|
+ status = ConfigConstants.CALLBACE_SUCCESS_STATUS;
|
|
|
+ } else if ("failed".equals(txResult.getStatus())) {
|
|
|
+ status = ConfigConstants.CALLBACE_FAIL_STATUS;
|
|
|
+ }
|
|
|
+
|
|
|
+ recordTable.setCallbackAmount(new BigDecimal(tokenEntity.getTokensAmount()));
|
|
|
+ recordTable.setCallbackCurrency(itemEntity.getBlockchain());
|
|
|
+ recordTable.setStatus(status);
|
|
|
+ recordTable.setCallbackTime(new Date());
|
|
|
+ recordTable.setCallbackStatus(true);
|
|
|
+ recordTable.setCallbackSerial(transactionId);
|
|
|
+ recordTable.setCallbackData(content);
|
|
|
+ settlementDepositRecordRepository.save(recordTable);
|
|
|
+
|
|
|
+ if (recordTable.getCallbackAmount() == null || recordTable.getCallbackAmount().compareTo(recordTable.getAmount()) < 0) {
|
|
|
+ log.error("Vaultody 通知回调:回调金额:{} 小于订单金额,{}", recordTable.getCallbackAmount(), recordTable.getAmount());
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ mqSendService.send(ConfigConstants.DEPOSIT_CALLBACK, recordTable, 3 * 1000);
|
|
|
+
|
|
|
+ return BaseResultDto.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public BaseResultDto withdraw(VaultodyWithdrawRequestEntity request) throws Exception {
|
|
|
+ verifyRequestTimeAndAmount(request);
|
|
|
+ String signString = getWithdrawSignString(request);
|
|
|
+ verifySign(request.getSign(), signString, request.getMerchantId());
|
|
|
+
|
|
|
+ SysRemitChannelTable withdrawChannel = getWithdrawChannel(request.getChannelCode());
|
|
|
+ VaultodyPayWithdrawPropertyEntity property = getWithdrawPayProperty(withdrawChannel, VaultodyPayWithdrawPropertyEntity.class);
|
|
|
+ String requestId = createWithdraw(request, property);
|
|
|
+ saveWithdrawRecord(request, withdrawChannel.getProperty(), requestId);
|
|
|
+ return BaseResultDto.success(requestId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public BaseResultDto withdrawCallback(String content, String signature) throws Exception {
|
|
|
+ log.info("vaultody withdraw callback:{},signature:{}", content, signature);
|
|
|
+
|
|
|
+ if (Strings.isNullOrEmpty(content) || Strings.isNullOrEmpty(signature)) {
|
|
|
+ log.error("vaultody withdraw callback error,data is empty,content:{},signature:{}", content, signature);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ VaultodyPayWithdrawPropertyEntity withdrawProperty = getWithdrawProperty(SettlementConstant.VAULTODY_PAY_REMIT,
|
|
|
+ VaultodyPayWithdrawPropertyEntity.class);
|
|
|
+
|
|
|
+ if (!isCallbackValid(content, withdrawProperty.getWebhooksPassphrase(), signature)) {
|
|
|
+ //打印日志 详细说明
|
|
|
+ log.error("vaultody withdraw callback valid error");
|
|
|
+ throw new ServiceException("vaultody withdraw callback valid error");
|
|
|
+ }
|
|
|
+ VaultodyCallbackEntity entity = com.alibaba.fastjson.JSON.parseObject(content, VaultodyCallbackEntity.class);
|
|
|
+ VaultodyDataEntity dataEntity = entity.getData();
|
|
|
+ String event = dataEntity.getEvent();
|
|
|
+ VaultodyItemEntity itemEntity = dataEntity.getItem();
|
|
|
+ String transactionId = itemEntity.getTransactionId();
|
|
|
+ String requestId = itemEntity.getRequestId();
|
|
|
+ if (StringUtils.isEmpty(requestId)) {
|
|
|
+ log.error("vaultody withdraw callback:requestId is empty,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isEmpty(event)) {
|
|
|
+ log.error("vaultody withdraw callback:event is empty,{}", com.alibaba.fastjson.JSON.toJSONString(entity));
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!"OUTGOING_MINED".equals(event)) {
|
|
|
+ log.error("vaultody withdraw callback:notice type error,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ SettlementWithdrawRecordTable withdrawRecordTable = settlementWithdrawRecordRepository.findFirstByRequestSerialAndChannelCode(requestId, SettlementConstant.VAULTODY_PAY_KEY);
|
|
|
+ if (withdrawRecordTable == null) {
|
|
|
+ log.error("Vaultody withdraw 通知回调:未找到对应的记录,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ String currency = withdrawRecordTable.getCurrency();
|
|
|
+ String address = withdrawRecordTable.getAddress();
|
|
|
+ BigDecimal transformAmount = withdrawRecordTable.getAmount();
|
|
|
+
|
|
|
+ TxResult txResult;
|
|
|
+ try {
|
|
|
+ txResult = EncryptionHashQueryUtil.query(transactionId, itemEntity.getBlockchain(), currency);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("vaultody withdraw callback:查询交易hash结果失败,{}", content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (txResult == null || StringUtils.isEmpty(txResult.getTo())) {
|
|
|
+ log.error("vaultody withdraw callback:通过hash:{} 未查询到结果,{}", transactionId, content);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ if (!txResult.getTo().equals(address)) {
|
|
|
+ log.error("vaultody withdraw callback:通过hash:{} 比对地址不相同,{}", txResult.getTo(), address);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+ if (txResult.getAmount().compareTo(transformAmount) != 0) {
|
|
|
+ log.error("vaultody withdraw callback:通过hash:{} 比对金额不相同,{}", txResult.getAmount(), transformAmount);
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if ("success".equals(txResult.getStatus())) {
|
|
|
+ withdrawRecordTable.setStatus(1);
|
|
|
+ } else if ("failed".equals(txResult.getStatus())) {
|
|
|
+ withdrawRecordTable.setStatus(2);
|
|
|
+ }
|
|
|
+
|
|
|
+ withdrawRecordTable.setCallbackAmount(txResult.getAmount());
|
|
|
+ withdrawRecordTable.setCallbackCurrency(itemEntity.getBlockchain());
|
|
|
+ withdrawRecordTable.setCallbackTime(new Date());
|
|
|
+ withdrawRecordTable.setCallbackStatus(true);
|
|
|
+ withdrawRecordTable.setCallbackSerial(transactionId);
|
|
|
+ withdrawRecordTable.setCallbackData(content);
|
|
|
+ settlementWithdrawRecordRepository.save(withdrawRecordTable);
|
|
|
+
|
|
|
+ if (withdrawRecordTable.getCallbackAmount() == null || withdrawRecordTable.getCallbackAmount().compareTo(withdrawRecordTable.getAmount()) < 0) {
|
|
|
+ log.error("Vaultody withdraw 通知回调:回调金额:{} 小于订单金额,{}", withdrawRecordTable.getCallbackAmount(), withdrawRecordTable.getAmount());
|
|
|
+ return BaseResultDto.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ mqSendService.send(ConfigConstants.WITHDRAW_CALLBACK, withdrawRecordTable, 3 * 1000);
|
|
|
+ return BaseResultDto.success();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分配地址
|
|
|
+ */
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public String reallocationAddress(VaultodyDepositRequestEntity request, VaultodyPayPropertyEntity propertyEntity) throws Exception {
|
|
|
+ SysConfigTable configTable = sysConfigService.getByCode(ConfigConstants.VAULTODY_ADDRESS_TOTAL_NUMBER);
|
|
|
+ if (configTable == null || configTable.getValue() == null || Long.parseLong(configTable.getValue()) <= 0) {
|
|
|
+ throw ServiceException.exception(Constants.SYSTEM_ERROR);
|
|
|
+ }
|
|
|
+ long addressTotalNumber = Long.parseLong(configTable.getValue());
|
|
|
+
|
|
|
+ long addressNumber = updateConfigTableInNewTransaction();
|
|
|
+
|
|
|
+ String address;
|
|
|
+ if (addressNumber > addressTotalNumber) {
|
|
|
+ //计算查询数据下标
|
|
|
+ int index = Math.toIntExact((addressNumber - 1) % addressTotalNumber);
|
|
|
+ address = depositAddressMapper.getTableByIndex(index);
|
|
|
+ } else {
|
|
|
+ address = getAddress(propertyEntity, request);
|
|
|
+ }
|
|
|
+ // 保存数据
|
|
|
+ FinanceDepositAddressTable table = new FinanceDepositAddressTable();
|
|
|
+ table.setAddress(address);
|
|
|
+ table.setSerial(request.getSerial());
|
|
|
+ table.setLastUsedTime(new Date());
|
|
|
+ table.setAddTime(new Date());
|
|
|
+ table.setIsUsed(true);
|
|
|
+ depositAddressRepository.save(table);
|
|
|
+ return address;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取钱包地址
|
|
|
+ */
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public String getAddress(VaultodyPayPropertyEntity propertyEntity, VaultodyDepositRequestEntity request) throws Exception {
|
|
|
+ String requestPath = getPath(propertyEntity.getAddressPathTemplate(), propertyEntity.getVaultId(),
|
|
|
+ request.getCurrency(),
|
|
|
+ propertyEntity.getNetwork());
|
|
|
+ String apiKey = propertyEntity.getApiKey();
|
|
|
+ String apiSecret = propertyEntity.getApiSecret();
|
|
|
+ String passphrase = propertyEntity.getPassphrase();
|
|
|
+ String method = propertyEntity.getMethod();
|
|
|
+ String baseUrl = propertyEntity.getBaseUrl();
|
|
|
+
|
|
|
+ Map<String, Object> labelMap = new HashMap<>();
|
|
|
+ labelMap.put("label", request.getSerial());
|
|
|
+ Map<String, Object> itemMap = new HashMap<>();
|
|
|
+ itemMap.put("item", labelMap);
|
|
|
+ Map<String, Object> paramMap = new HashMap<>();
|
|
|
+ paramMap.put("data", itemMap);
|
|
|
+
|
|
|
+ // POST JSON body
|
|
|
+ String body = JSON.toJSONString(paramMap);
|
|
|
+ String query = "{}"; // POST 时 query 通常为空,否则按接口要求填写
|
|
|
+
|
|
|
+ // ------------------ 时间戳(秒) ------------------
|
|
|
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
|
|
+
|
|
|
+ // ------------------ 构建消息用于签名 ------------------
|
|
|
+ String message = timestamp + method + requestPath + body + query;
|
|
|
+
|
|
|
+ String signature = getSignature(message, apiSecret);
|
|
|
+ Map<String, String> headers = new HashMap<>();
|
|
|
+ headers.put("x-api-key", apiKey);
|
|
|
+ headers.put("x-api-sign", signature);
|
|
|
+ headers.put("x-api-timestamp", timestamp);
|
|
|
+ headers.put("x-api-passphrase", passphrase);
|
|
|
+ headers.put("Content-Type", "application/json");
|
|
|
+
|
|
|
+ Connection.Response response = HttpUtil.post(baseUrl + requestPath, headers, body);
|
|
|
+ log.info(String.format("url:%s,json:%s,body:%s", baseUrl + requestPath, body, response.body()));
|
|
|
+ if (response.statusCode() != 200) {
|
|
|
+ log.error("订单: {} 钱包地址生成失败: {}", request.getSerial(), response.body());
|
|
|
+ throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
|
|
|
+ " service.");
|
|
|
+ }
|
|
|
+ // 解析 JSON 字符串
|
|
|
+ JSONObject jsonObject = JSON.parseObject(response.body());
|
|
|
+
|
|
|
+ // 获取 address 和 label 字段
|
|
|
+ String address = jsonObject.getJSONObject("data").getJSONObject("item").getString("address");
|
|
|
+
|
|
|
+ return address;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
|
|
|
+ public long updateConfigTableInNewTransaction() throws ServiceException {
|
|
|
+ Long number = redisService.incr(ConfigConstants.ADDRESS_ID_CONTROL_AUTO_NUMBER);
|
|
|
+ SysConfigTable sysConfigTable = sysConfigRepository.getByCode(ConfigConstants.ADDRESS_ID_CONTROL_AUTO_NUMBER);
|
|
|
+ sysConfigTable.setValue(String.valueOf(number));
|
|
|
+ sysConfigRepository.save(sysConfigTable);
|
|
|
+ return number;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String createWithdraw(VaultodyWithdrawRequestEntity entity, VaultodyPayWithdrawPropertyEntity propertyEntity) throws Exception {
|
|
|
+ String blockchain = null;
|
|
|
+ if (entity.getCurrency().contains("-")) {
|
|
|
+ blockchain = entity.getCurrency().split("-")[0];
|
|
|
+ }
|
|
|
+ if (StringUtils.isEmpty(blockchain)) {
|
|
|
+ blockchain = entity.getCurrency();
|
|
|
+ }
|
|
|
+ String requestPath = getPath(propertyEntity.getCreateTokenTransactionPathTemplate(),
|
|
|
+ propertyEntity.getVaultId(),
|
|
|
+ blockchain, propertyEntity.getNetwork());
|
|
|
+ AtomicReference<String> contractAddress = new AtomicReference<>();
|
|
|
+ AtomicReference<String> fromAddress = new AtomicReference<>();
|
|
|
+ List<VaultodyPayWithdrawAddressPropertyEntity> withdrawAddress = propertyEntity.getWithdrawAddress();
|
|
|
+ if (CollectionUtils.isNotEmpty(withdrawAddress)) {
|
|
|
+ withdrawAddress.forEach(item -> {
|
|
|
+ if (item.getBankCode().equals(entity.getCurrency())) {
|
|
|
+ fromAddress.set(item.getFromAddress());
|
|
|
+ contractAddress.set(item.getContractAddress());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> itemMap = new HashMap<>();
|
|
|
+ itemMap.put("amount", entity.getAmount().stripTrailingZeros().toPlainString());
|
|
|
+ itemMap.put("contractAddress", contractAddress.get());
|
|
|
+ itemMap.put("fromAddress", fromAddress.get());
|
|
|
+ itemMap.put("recipientAddress", entity.getAddress());
|
|
|
+ Map<String, Object> dataMap = new HashMap<>();
|
|
|
+ dataMap.put("item", itemMap);
|
|
|
+ Map<String, Object> paramMap = new HashMap<>();
|
|
|
+ paramMap.put("data", dataMap);
|
|
|
+
|
|
|
+
|
|
|
+ // POST JSON body
|
|
|
+ String body = JSON.toJSONString(paramMap);
|
|
|
+ String query = "{}"; // POST 时 query 通常为空,否则按接口要求填写
|
|
|
+
|
|
|
+ // ------------------ 时间戳(秒) ------------------
|
|
|
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
|
|
+
|
|
|
+ // ------------------ 构建消息用于签名 ------------------
|
|
|
+ String message = timestamp + propertyEntity.getMethod() + requestPath + body + query;
|
|
|
+
|
|
|
+ String signature = getSignature(message, propertyEntity.getApiSecret());
|
|
|
+ System.out.println("Signature: " + signature);
|
|
|
+ Map<String, String> headers = new HashMap<>();
|
|
|
+ headers.put("x-api-key", propertyEntity.getApiKey());
|
|
|
+ headers.put("x-api-sign", signature);
|
|
|
+ headers.put("x-api-timestamp", timestamp);
|
|
|
+ headers.put("x-api-passphrase", propertyEntity.getPassphrase());
|
|
|
+ headers.put("Content-Type", "application/json");
|
|
|
+
|
|
|
+ Connection.Response response = HttpUtil.post(propertyEntity.getBaseUrl() + requestPath, headers, body);
|
|
|
+ log.info(String.format("url:%s,json:%s,body:%s",
|
|
|
+ propertyEntity.getBaseUrl() + requestPath, body, response.body()));
|
|
|
+
|
|
|
+ // 解析 JSON 字符串
|
|
|
+ JSONObject jsonObject = JSON.parseObject(response.body());
|
|
|
+ if (response.statusCode() != 200
|
|
|
+ && response.statusCode() != 201
|
|
|
+ && (jsonObject.getJSONObject("data") == null || !"created".equals(jsonObject.getJSONObject("data").getJSONObject("item").getString("transactionRequestStatus")))
|
|
|
+ ) {
|
|
|
+ log.error("vaultody withdraw address: {} error: {}", entity.getAddress(), response.body());
|
|
|
+ throw new PayValidatedException("Payment platform is abnormal, please try again later or contact " +
|
|
|
+ "customer service.");
|
|
|
+ }
|
|
|
+
|
|
|
+ String requestId = jsonObject.getJSONObject("data").getJSONObject("item").getString("transactionRequestId");
|
|
|
+ if (Strings.isNullOrEmpty(requestId)) {
|
|
|
+ throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
|
|
|
+ " service.");
|
|
|
+ }
|
|
|
+
|
|
|
+ return requestId;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存记录
|
|
|
+ */
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void saveDepositRecord(VaultodyDepositRequestEntity request, String property, String address) {
|
|
|
+ SysConfigTable configTable = sysConfigService.getByCode(ConfigConstants.CALLBACK_MAX_RETRY_COUNT);
|
|
|
+ Integer maxRetryCount = Integer.parseInt(configTable.getValue());
|
|
|
+ SettlementDepositRecordTable table = new SettlementDepositRecordTable();
|
|
|
+ table.setMerchantId(request.getMerchantId());
|
|
|
+ table.setSerial(request.getSerial());
|
|
|
+ table.setRequestSerial(address);
|
|
|
+ table.setAmount(request.getAmount());
|
|
|
+ table.setCurrency(request.getCurrency());
|
|
|
+ table.setStatus(0);
|
|
|
+ table.setChannelCode(request.getChannelCode());
|
|
|
+ table.setChannelProperty(property);
|
|
|
+ table.setRequestBody(JSON.toJSONString(request));
|
|
|
+ table.setCallbackUrl(request.getCallbackUrl());
|
|
|
+ table.setCallbackStatus(false);
|
|
|
+ table.setCallbackMerchantStatus(0);
|
|
|
+ table.setCallbackMerchantCount(0);
|
|
|
+ table.setCallbackMerchantMaxRetryCount(maxRetryCount);
|
|
|
+ table.setAddTime(new Date());
|
|
|
+ settlementDepositRecordRepository.save(table);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void saveWithdrawRecord(VaultodyWithdrawRequestEntity request, String property, String requestId) {
|
|
|
+ String blockchain = null;
|
|
|
+ if (request.getCurrency().contains("-")) {
|
|
|
+ blockchain = request.getCurrency().split("-")[0];
|
|
|
+ }
|
|
|
+ if (StringUtils.isEmpty(blockchain)) {
|
|
|
+ blockchain = request.getCurrency();
|
|
|
+ }
|
|
|
+ SysConfigTable configTable = sysConfigService.getByCode(ConfigConstants.CALLBACK_MAX_RETRY_COUNT);
|
|
|
+ Integer maxRetryCount = Integer.parseInt(configTable.getValue());
|
|
|
+ SettlementWithdrawRecordTable table = new SettlementWithdrawRecordTable();
|
|
|
+ table.setMerchantId(request.getMerchantId());
|
|
|
+ table.setSerial(request.getSerial());
|
|
|
+ table.setRequestSerial(requestId);
|
|
|
+ table.setAmount(request.getAmount());
|
|
|
+ table.setCurrency(blockchain);
|
|
|
+ table.setAddress(request.getAddress());
|
|
|
+ table.setBankUname(request.getBankUname());
|
|
|
+ table.setBankCardNum(request.getBankCardNum());
|
|
|
+ table.setBankName(request.getBankName());
|
|
|
+ table.setBankBranchName(request.getBankBranchName());
|
|
|
+ table.setBankAddr(request.getBankAddr());
|
|
|
+ table.setSwiftCode(request.getSwiftCode());
|
|
|
+ table.setCvv(request.getCvv());
|
|
|
+ table.setExpiryMonth(request.getExpiryMonth());
|
|
|
+ table.setChannelCode(request.getChannelCode());
|
|
|
+ table.setChannelProperty(property);
|
|
|
+ table.setRequestBody(JSON.toJSONString(request));
|
|
|
+ table.setStatus(0);
|
|
|
+ table.setCallbackUrl(request.getCallbackUrl());
|
|
|
+ table.setCallbackStatus(false);
|
|
|
+ table.setCallbackMerchantStatus(0);
|
|
|
+ table.setCallbackMerchantCount(0);
|
|
|
+ table.setCallbackMerchantMaxRetryCount(maxRetryCount);
|
|
|
+ table.setAddTime(new Date());
|
|
|
+ settlementWithdrawRecordRepository.save(table);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getSignString(VaultodyDepositRequestEntity entity) {
|
|
|
+ Map<String, String> map = getBaseSignString(entity);
|
|
|
+ return MapUtil.getStringSortByKey(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getWithdrawSignString(VaultodyWithdrawRequestEntity entity) {
|
|
|
+ Map<String, String> map = getBaseSignString(entity);
|
|
|
+ map.put("address", entity.getAddress());
|
|
|
+ map.put("bankUname", entity.getBankUname());
|
|
|
+ map.put("bankCardNum", entity.getBankCardNum());
|
|
|
+ map.put("bankName", entity.getBankName());
|
|
|
+ map.put("bankBranchName", entity.getBankBranchName());
|
|
|
+ map.put("bankAddr", entity.getBankAddr());
|
|
|
+ map.put("swiftCode", entity.getSwiftCode());
|
|
|
+ map.put("cvv", entity.getCvv());
|
|
|
+ map.put("expiryMonth", entity.getExpiryMonth());
|
|
|
+ return MapUtil.getStringSortByKey(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String getSignature(String message, String apiSecret) {
|
|
|
+ try {
|
|
|
+ byte[] decodedSecret = Base64.getDecoder().decode(apiSecret);
|
|
|
+ Mac mac = Mac.getInstance("HmacSHA256");
|
|
|
+ SecretKeySpec secretKeySpec = new SecretKeySpec(decodedSecret, "HmacSHA256");
|
|
|
+ mac.init(secretKeySpec);
|
|
|
+ byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
|
|
|
+
|
|
|
+ return Base64.getEncoder().encodeToString(hash);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getPath(String pathTemplate, String vaultId, String blockchain, String network) {
|
|
|
+ String actualPath = String.format(pathTemplate, vaultId, blockchain, network);
|
|
|
+ return actualPath;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isCallbackValid(String message, String apiSecret, String signature) {
|
|
|
+ String _signature = HAMCSHA256Util.sha256_HMAC(message, apiSecret);
|
|
|
+
|
|
|
+ //打印签名 两个签名数据和比对结果
|
|
|
+ log.info("isCallbackValid,_signature:{},signature:{}", _signature, signature);
|
|
|
+
|
|
|
+ return _signature.equals(signature);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|