添加pay模块的使用示例

This commit is contained in:
b2baccline
2021-01-06 15:25:58 +08:00
parent 1da39896c2
commit 14c7668f73
13 changed files with 518 additions and 1 deletions

View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 "";
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,25 @@
package com.hccake.sample.pay.virtual.enums;
/**
* 充值订单状态
* @author lingting 2021/1/5 14:33
*/
public enum Status {
/**
* 成功
*/
SUCCESS,
/**
* 失败
*/
FAILED,
/**
* 充值中
*/
WAIT,
;
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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: '*'

View File

@@ -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>