✨ 添加pay模块的使用示例
This commit is contained in:
39
ballcat-samples/ballcat-sample-pay/pom.xml
Normal file
39
ballcat-samples/ballcat-sample-pay/pom.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ballcat-samples</artifactId>
|
||||
<groupId>com.hccake</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ballcat-sample-pay</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.hccake</groupId>
|
||||
<artifactId>ballcat-spring-boot-starter-pay</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--undertow容器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.hccake.sample.pay;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author lingting 2021/1/5 14:20
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class PayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hccake.sample.pay.virtual.config;
|
||||
|
||||
import com.hccake.starter.pay.PayProperties;
|
||||
import java.util.function.Supplier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import live.lingting.virtual.currency.properties.OmniProperties;
|
||||
|
||||
/**
|
||||
* @author lingting 2021/1/5 16:24
|
||||
*/
|
||||
@Configuration
|
||||
public class OmniConfig {
|
||||
|
||||
@Bean
|
||||
public OmniProperties bitcoinProperties(PayProperties properties) {
|
||||
// omni 使用的接口限制请求频率(5-10s一次), 需要根据项目实现, 这里直接返回true
|
||||
Supplier<Boolean> lock = () -> true;
|
||||
Supplier<Boolean> unlock = () -> true;
|
||||
|
||||
return new OmniProperties()
|
||||
// 节点
|
||||
.setEndpoints(properties.getBitcoin().getOmni().getEndpoints()).setLock(lock).setUnlock(unlock);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.hccake.sample.pay.virtual.controller;
|
||||
|
||||
import com.hccake.sample.pay.virtual.entity.Order;
|
||||
import com.hccake.sample.pay.virtual.enums.Status;
|
||||
import com.hccake.sample.pay.virtual.thread.EtherscanThread;
|
||||
import com.hccake.sample.pay.virtual.thread.OmniThread;
|
||||
import com.hccake.sample.pay.virtual.thread.TronscanThread;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import live.lingting.virtual.currency.contract.EtherscanContract;
|
||||
import live.lingting.virtual.currency.contract.OmniContract;
|
||||
import live.lingting.virtual.currency.contract.TronscanContract;
|
||||
|
||||
/**
|
||||
* @author lingting 2021/1/5 16:17
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class VirtualController {
|
||||
|
||||
private final EtherscanThread etherscanThread;
|
||||
|
||||
private final TronscanThread tronscanThread;
|
||||
|
||||
private final OmniThread omniThread;
|
||||
|
||||
@GetMapping("eth")
|
||||
public Object eth() {
|
||||
etherscanThread.putObject(new Order()
|
||||
// infura 平台由于需要 project id, 可以自己填上自己的projectId 进行验证
|
||||
.setHash("0x2ab87cc91f48fa940aab53e602a57786ad3d4a263875b6ffb779713bf5a60645")
|
||||
.setCreateTime(LocalDateTime.now()).setContract(EtherscanContract.USDT));
|
||||
return "";
|
||||
}
|
||||
|
||||
@GetMapping("trc")
|
||||
public Object trc() {
|
||||
tronscanThread.putObject(new Order().setHash("b22dce34a2c60661989ee710cf71d80d3ff50c1613e7fde6f9e34146ef7bdd2e")
|
||||
.setContract(TronscanContract.USDT).setSn(2312314L).setCreateTime(LocalDateTime.now())
|
||||
.setStatus(Status.WAIT).setAddress("TFm55T9n2Qs3gfehoXt4YFBJzRJKrHWH3V"));
|
||||
return "";
|
||||
}
|
||||
|
||||
@GetMapping("btc")
|
||||
public Object btc() {
|
||||
omniThread.putObject(new Order().setHash("f583049c257da84d17874aef32425c8d192c12f9718db152fc72370b9d6bd01f")
|
||||
.setContract(OmniContract.USDT).setSn(2312314L).setCreateTime(LocalDateTime.now())
|
||||
.setStatus(Status.WAIT).setAddress("34Bs4AJigJUcbXXJk5kVznRNDiAGj57qCm"));
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.hccake.sample.pay.virtual.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 交易校验返回结果
|
||||
*
|
||||
* @author lingting 2021/1/5 14:38
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Result {
|
||||
|
||||
/**
|
||||
* 如果校验失败, 这里的值为错误信息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.hccake.sample.pay.virtual.entity;
|
||||
|
||||
import com.hccake.sample.pay.virtual.enums.Status;
|
||||
import com.hccake.starter.pay.viratual.VerifyObj;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import live.lingting.virtual.currency.contract.Contract;
|
||||
|
||||
/**
|
||||
* 充值订单, 实现 {@link VerifyObj}
|
||||
*
|
||||
* @author lingting 2021/1/5 14:26
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class Order implements VerifyObj {
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private Long sn;
|
||||
|
||||
/**
|
||||
* 交易hash
|
||||
*/
|
||||
private String hash;
|
||||
|
||||
/**
|
||||
* 收款地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 收款货币类型, 以下三个枚举分别用来表示三个平台的货币类型
|
||||
* @see live.lingting.virtual.currency.contract.EtherscanContract
|
||||
* @see live.lingting.virtual.currency.contract.TronscanContract
|
||||
* @see live.lingting.virtual.currency.contract.OmniContract
|
||||
*/
|
||||
private Contract contract;
|
||||
|
||||
/**
|
||||
* 充值状态
|
||||
*/
|
||||
private Status status;
|
||||
|
||||
/**
|
||||
* 充值金额
|
||||
*/
|
||||
private BigDecimal value;
|
||||
|
||||
/**
|
||||
* 订单描述, 一般用于解释失败订单的失败原因
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.hccake.sample.pay.virtual.enums;
|
||||
|
||||
/**
|
||||
* 充值订单状态
|
||||
* @author lingting 2021/1/5 14:33
|
||||
*/
|
||||
public enum Status {
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS,
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
FAILED,
|
||||
/**
|
||||
* 充值中
|
||||
*/
|
||||
WAIT,
|
||||
|
||||
;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.hccake.sample.pay.virtual.thread;
|
||||
|
||||
import com.hccake.ballcat.common.core.util.JacksonUtils;
|
||||
import com.hccake.sample.pay.virtual.domain.Result;
|
||||
import com.hccake.sample.pay.virtual.entity.Order;
|
||||
import com.hccake.starter.pay.viratual.AbstractVerifyThread;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import live.lingting.virtual.currency.Transaction;
|
||||
import live.lingting.virtual.currency.enums.TransactionStatus;
|
||||
|
||||
/**
|
||||
* 配置基本校验
|
||||
*
|
||||
* @author lingting 2021/1/5 14:25
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractThread extends AbstractVerifyThread<Order, Result> {
|
||||
|
||||
public final List<Order> CACHE = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public long getBatchSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void errorLog(Throwable e, List<Order> list) {
|
||||
// 读取缓存 和 接收数据时出现异常执行此方法
|
||||
log.error("读取缓存 和 接收数据时出现异常执行此方法, 数据: " + JacksonUtils.toJson(list), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handler(Order obj, Optional<Transaction> optional) {
|
||||
/*
|
||||
* 不管哪个平台的充值订单, 验证逻辑都是一样的
|
||||
*
|
||||
* 如果对某平台有特殊要求, 可以在子类中实现本方法,
|
||||
*/
|
||||
|
||||
// 没有获取到交易数据
|
||||
if (!optional.isPresent()) {
|
||||
// 订单已创建超过6小时
|
||||
if (Duration.between(obj.getCreateTime(), LocalDateTime.now()).toHours() > 6) {
|
||||
failed(obj, optional, new Result("超时"));
|
||||
}
|
||||
// 没有超过限时, 缓存
|
||||
else {
|
||||
cache(obj);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Transaction transaction = optional.get();
|
||||
|
||||
if (transaction.getStatus() == TransactionStatus.WAIT) {
|
||||
// 交易需要等待继续查询
|
||||
cache(obj);
|
||||
}
|
||||
// 交易失败
|
||||
else if (transaction.getStatus() == TransactionStatus.FAIL) {
|
||||
failed(obj, optional, new Result("交易失败"));
|
||||
}
|
||||
// 交易生成时间超过订单创建时间 12小时范围, 拒绝
|
||||
else if (Duration.between(transaction.getTime(), obj.getCreateTime()).toHours() > 12) {
|
||||
failed(obj, optional, new Result("交易生成时间超过订单创建时间 12小时范围, 拒绝"));
|
||||
}
|
||||
// 收款地址验证
|
||||
else if (!transaction.getTo().equals(obj.getAddress())) {
|
||||
failed(obj, optional, new Result("收款地址异常"));
|
||||
}
|
||||
// 收款货币类型验证
|
||||
else if (obj.getContract() != transaction.getContract()) {
|
||||
failed(obj, optional, new Result("收款货币类型异常"));
|
||||
}
|
||||
|
||||
/*
|
||||
* .... 其他业务判断逻辑
|
||||
*/
|
||||
|
||||
// 交易成功
|
||||
else {
|
||||
success(obj, optional, new Result("成功"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Order obj, Throwable e) {
|
||||
// 校验订单 和 获取交易数据时出现异常执行此方法
|
||||
log.error("校验订单 和 获取交易数据时出现异常执行此方法, 出错订单: " + JacksonUtils.toJson(obj), e);
|
||||
|
||||
// 可根据业务需求在此进行其他处理.
|
||||
|
||||
/*
|
||||
* 例如 放入缓存等待下次处理.
|
||||
*/
|
||||
cache(obj);
|
||||
/*
|
||||
* 例如 直接按照充值失败进行结算
|
||||
*/
|
||||
failed(obj, Optional.empty(), new Result("出现异常, 具体请查询日志! 简易信息: " + e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Order obj, Optional<Transaction> optional, Result verifyResult) {
|
||||
log.info("交易成功, 订单数据: {}, 交易信息: {}, 结果: {}", JacksonUtils.toJson(obj),
|
||||
!optional.isPresent() ? "null" : JacksonUtils.toJson(optional.get()),
|
||||
JacksonUtils.toJson(verifyResult));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Order obj, Optional<Transaction> optional, Result verifyResult) {
|
||||
log.info("交易失败, 订单数据: {}, 交易信息: {}, 结果: {}", JacksonUtils.toJson(obj),
|
||||
!optional.isPresent() ? "null" : JacksonUtils.toJson(optional.get()),
|
||||
JacksonUtils.toJson(verifyResult));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cache(Order obj) {
|
||||
CACHE.add(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Order> readCache() {
|
||||
/*
|
||||
* 这里使用 list 缓存是因为只是样例, 生产环境不建议
|
||||
*/
|
||||
List<Order> list = new ArrayList<>(CACHE);
|
||||
CACHE.clear();
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.hccake.sample.pay.virtual.thread;
|
||||
|
||||
import com.hccake.ballcat.common.core.util.JacksonUtils;
|
||||
import com.hccake.sample.pay.virtual.entity.Order;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import live.lingting.virtual.currency.Transaction;
|
||||
import live.lingting.virtual.currency.service.impl.InfuraServiceImpl;
|
||||
|
||||
/**
|
||||
* @author lingting 2021/1/5 15:22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class EtherscanThread extends AbstractThread {
|
||||
|
||||
private final InfuraServiceImpl service;
|
||||
|
||||
@Override
|
||||
public void startLog() {
|
||||
log.debug("Etherscan 订单校验");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Transaction> getTransaction(Order obj) {
|
||||
try {
|
||||
return service.getTransactionByHash(obj.getHash());
|
||||
}
|
||||
catch (Throwable e) {
|
||||
log.error("查询订单出错, 订单: " + JacksonUtils.toJson(obj), e);
|
||||
// 查询出错, 返回 empty
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.hccake.sample.pay.virtual.thread;
|
||||
|
||||
import com.hccake.ballcat.common.core.util.JacksonUtils;
|
||||
import com.hccake.sample.pay.virtual.entity.Order;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import live.lingting.virtual.currency.Transaction;
|
||||
import live.lingting.virtual.currency.service.impl.OmniServiceImpl;
|
||||
|
||||
/**
|
||||
* @author lingting 2021/1/5 15:22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OmniThread extends AbstractThread {
|
||||
|
||||
private final OmniServiceImpl service;
|
||||
|
||||
@Override
|
||||
public void startLog() {
|
||||
log.debug("Omni 订单校验");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Transaction> getTransaction(Order obj) {
|
||||
try {
|
||||
return service.getTransactionByHash(obj.getHash());
|
||||
}
|
||||
catch (Throwable e) {
|
||||
log.error("查询订单出错, 订单: " + JacksonUtils.toJson(obj), e);
|
||||
// 查询出错, 返回 empty
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.hccake.sample.pay.virtual.thread;
|
||||
|
||||
import com.hccake.ballcat.common.core.util.JacksonUtils;
|
||||
import com.hccake.sample.pay.virtual.entity.Order;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import live.lingting.virtual.currency.Transaction;
|
||||
import live.lingting.virtual.currency.service.impl.TronscanServiceImpl;
|
||||
|
||||
/**
|
||||
* @author lingting 2021/1/5 15:22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class TronscanThread extends AbstractThread {
|
||||
|
||||
private final TronscanServiceImpl service;
|
||||
|
||||
@Override
|
||||
public void startLog() {
|
||||
log.debug("Tronscan 订单校验");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Transaction> getTransaction(Order obj) {
|
||||
try {
|
||||
return service.getTransactionByHash(obj.getHash());
|
||||
}
|
||||
catch (Throwable e) {
|
||||
log.error("查询订单出错, 订单: " + JacksonUtils.toJson(obj), e);
|
||||
// 查询出错, 返回 empty
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
ballcat:
|
||||
pay:
|
||||
bitcoin:
|
||||
omni:
|
||||
endpoints: mainnet
|
||||
ethereum:
|
||||
infura:
|
||||
endpoints: mainnet
|
||||
# 正式运营请使用自己的 project id, 这个随时可能失效
|
||||
project-id: b6066b4cfce54e7384ea38d52f9260ac
|
||||
tronscan:
|
||||
endpoints: mainnet
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: '*'
|
||||
@@ -16,6 +16,7 @@
|
||||
<module>ballcat-sample-monitor</module>
|
||||
<module>ballcat-sample-admin-application</module>
|
||||
<module>ballcat-sample-swagger-provider</module>
|
||||
<module>ballcat-sample-pay</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
||||
Reference in New Issue
Block a user