refactor(trade):导入项目支付模块初始工程

This commit is contained in:
JIAN 2024-09-08 19:20:34 +08:00
parent 85b6192ba1
commit 18ebfcc04f
66 changed files with 4277 additions and 0 deletions

8
jzo2o-trade/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM openjdk:11-jdk
LABEL maintainer="研究院研发组 <research-maint@itcast.cn>"
RUN echo "Asia/Shanghai" > /etc/timezone
ARG PACKAGE_PATH=./target/jzo2o-trade.jar
ADD ${PACKAGE_PATH:-./} app.jar
EXPOSE 11505
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS app.jar"]

111
jzo2o-trade/pom.xml Normal file
View File

@ -0,0 +1,111 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<artifactId>jzo2o-trade</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>jzo2o-parent</artifactId>
<groupId>com.jzo2o</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-mvc</artifactId>
</dependency>
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-knife4j-web</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-api</artifactId>
</dependency>
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-mysql</artifactId>
</dependency>
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-xxl-job</artifactId>
</dependency>
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-rabbitmq</artifactId>
</dependency>
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
<!--二维码生成工具包-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.jzo2o.trade.TradeApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,23 @@
package com.jzo2o.trade;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author itcast
*/
@Slf4j
@MapperScan("com.jzo2o.trade.mapper")
@SpringBootApplication
public class TradeApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(TradeApplication.class)
.build(args)
.run(args);
log.info("家政服务-支付服务启动");
}
}

View File

@ -0,0 +1,17 @@
package com.jzo2o.trade.annotation;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import java.lang.annotation.*;
/**
* @author itcast
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented //标记注解
public @interface PayChannel {
PayChannelEnum type();
}

View File

@ -0,0 +1,58 @@
package com.jzo2o.trade.config;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.awt.*;
/**
* 二维码生成参数配置
*
* @author zzj
* @version 1.0
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "jzo2o.qrcode")
public class QRCodeConfig {
private static Image WECHAT_LOGO;
private static Image ALIPAY_LOGO;
static {
WECHAT_LOGO = ImgUtil.read(ResourceUtil.getResource("logos/wechat.png"));
ALIPAY_LOGO = ImgUtil.read(ResourceUtil.getResource("logos/alipay.png"));
}
//边距二维码和背景之间的边距
private Integer margin = 2;
// 二维码颜色默认黑色
private String foreColor = "#000000";
//背景色默认白色
private String backColor = "#ffffff";
//纠错级别可选参数LMQH默认M
//低级别的像素块更大可以远距离识别但是遮挡就会造成无法识别高级别则相反像素块小允许遮挡一定范围但是像素块更密集
private String errorCorrectionLevel = "M";
//
private Integer width = 300;
//
private Integer height = 300;
public Image getLogo(PayChannelEnum payChannelEnum) {
switch (payChannelEnum) {
case ALI_PAY: {
return ALIPAY_LOGO;
}
case WECHAT_PAY: {
return WECHAT_LOGO;
}
default: {
return null;
}
}
}
}

View File

@ -0,0 +1,39 @@
//package com.jzo2o.trade.config;
//
//import cn.hutool.core.convert.Convert;
//import cn.hutool.core.util.StrUtil;
//import lombok.Data;
//import org.redisson.Redisson;
//import org.redisson.api.RedissonClient;
//import org.redisson.config.Config;
//import org.redisson.config.SingleServerConfig;
//import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
//import org.springframework.boot.context.properties.EnableConfigurationProperties;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//
//import javax.annotation.Resource;
//
//@Configuration
//@EnableConfigurationProperties(RedisProperties.class)
//@Data
//public class RedissonConfiguration {
//
// @Resource
// private RedisProperties redisProperties;
//
// @Bean
// public RedissonClient redissonSingle() {
// Config config = new Config();
// SingleServerConfig serverConfig = config.useSingleServer()
// .setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort());
// if (null != (redisProperties.getTimeout())) {
// serverConfig.setTimeout(1000 * Convert.toInt(redisProperties.getTimeout().getSeconds()));
// }
// if (StrUtil.isNotEmpty(redisProperties.getPassword())) {
// serverConfig.setPassword(redisProperties.getPassword());
// }
// return Redisson.create(config);
// }
//
//}

View File

@ -0,0 +1,19 @@
package com.jzo2o.trade.constant;
/**
* 静态变量
*
* @author zzj
* @version 1.0
*/
public interface Constants {
/**
* 常量是
*/
String YES = "YES";
/**
* 常量否
*/
String NO = "NO";
}

View File

@ -0,0 +1,42 @@
package com.jzo2o.trade.constant;
/**
* @ClassName TradingCacheConstant.java
* @Description 交易缓存维护
*/
public class TradingCacheConstant {
//默认redis等待时间
public static final int REDIS_WAIT_TIME = 5;
//默认redis自动释放时间
public static final int REDIS_LEASETIME = 4;
//安全组前缀
public static final String PREFIX = "trading:";
//分布式锁前缀
public static final String LOCK_PREFIX = PREFIX + "lock:";
//创建交易加锁
public static final String CREATE_PAY = LOCK_PREFIX + "create_pay";
//查询交易状态加锁
public static final String QUERY_PAY = LOCK_PREFIX + "query_pay";
//创建退款加锁
public static final String REFUND_PAY = LOCK_PREFIX + "refund_pay";
//退款查询加锁
public static final String REFUND_QUERY_PAY = LOCK_PREFIX + "refund_query_pay";
//创建退款加锁
public static final String PAY_CHANNEL_LIST = PREFIX + "pay_channel_list&ttl=-1";
//创建退款加锁
public static final String CLOSE_PAY = LOCK_PREFIX + "close_pay";
//page分页
public static final String PAGE = PREFIX + "page";
}

View File

@ -0,0 +1,71 @@
package com.jzo2o.trade.constant;
import com.jzo2o.api.trade.enums.PayChannelEnum;
/**
* @ClassName TardingConstant.java
* @Description 交易常量类
*/
public class TradingConstant {
//阿里云退款返回状态
//REFUND_SUCCESS:成功
public static final String REFUND_SUCCESS = "REFUND_SUCCESS";
//阿里云返回付款状态
//TRADE_CLOSED:未付款交易超时关闭或支付完成后全额退款
public static final String ALI_TRADE_CLOSED = "TRADE_CLOSED";
//TRADE_SUCCESS:交易支付成功
public static final String ALI_TRADE_SUCCESS = "TRADE_SUCCESS";
//TRADE_FINISHED:交易结束不可退款
public static final String ALI_TRADE_FINISHED = "TRADE_FINISHED";
//微信退款返回状态
//SUCCESS退款成功
public static final String WECHAT_REFUND_SUCCESS = "SUCCESS";
//CLOSED退款关闭
public static final String WECHAT_REFUND_CLOSED = "CLOSED";
//PROCESSING退款处理中
public static final String WECHAT_REFUND_PROCESSING = "PROCESSING";
//ABNORMAL退款异常
public static final String WECHAT_REFUND_ABNORMAL = "TRADE_CLOSED";
//微信返回付款状态
//SUCCESS支付成功
public static final String WECHAT_TRADE_SUCCESS = "SUCCESS";
//REFUND转入退款
public static final String WECHAT_TRADE_REFUND = "REFUND";
//NOTPAY未支付
public static final String WECHAT_TRADE_NOTPAY = "NOTPAY";
//CLOSED已关闭
public static final String WECHAT_TRADE_CLOSED = "CLOSED";
//REVOKED已撤销仅付款码支付会返回
public static final String WECHAT_TRADE_REVOKED = "REVOKED";
//USERPAYING用户支付中仅付款码支付会返回
public static final String WECHAT_TRADE_USERPAYING = "USERPAYING";
//PAYERROR支付失败仅付款码支付会返回
public static final String WECHAT_TRADE_PAYERROR = "PAYERROR";
//平台:交易渠道
//阿里支付
public static final String TRADING_CHANNEL_ALI_PAY = PayChannelEnum.ALI_PAY.name();
//微信支付
public static final String TRADING_CHANNEL_WECHAT_PAY = PayChannelEnum.WECHAT_PAY.name();
//现金
public static final String TRADING_CHANNEL_CASH_PAY = "CASH_PAY";
//免单挂账信用渠道
public static final String TRADING_CHANNEL_CREDIT_PAY = "CREDIT_PAY";
//平台:交易动作
//付款
public static final String TRADING_TYPE_FK = "FK";
//退款
public static final String TRADING_TYPE_TK = "TK";
//免单
public static final String TRADING_TYPE_MD = "MD";
//挂账
public static final String TRADING_TYPE_GZ = "GZ";
}

View File

@ -0,0 +1,49 @@
package com.jzo2o.trade.controller.inner;
import cn.hutool.core.bean.BeanUtil;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.service.NativePayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* Native支付方式Face接口商户生成二维码用户扫描支付
*
* @author itcast
*/
@Validated
@RestController("innerNativePayController")
@Api(tags = "内部接口 - Native支付")
@RequestMapping("/inner/native")
public class NativePayController implements NativePayApi {
@Resource
private NativePayService nativePayService;
/***
* 扫码支付收银员通过收银台或商户后台调用此接口生成二维码后展示给用户由用户扫描二维码完成订单支付
*
* @param nativePayDTO 扫码支付提交参数
* @return 扫码支付响应数据其中包含二维码路径
*/
@Override
@PostMapping
@ApiOperation(value = "统一收单线下交易", notes = "统一收单线下交易")
@ApiImplicitParam(name = "nativePayDTO", value = "扫码支付提交参数", required = true)
public NativePayResDTO createDownLineTrading(@RequestBody NativePayReqDTO nativePayDTO) {
Trading tradingEntity = BeanUtil.toBean(nativePayDTO, Trading.class);
Trading trading = this.nativePayService.createDownLineTrading(nativePayDTO.isChangeChannel(),tradingEntity);
return BeanUtil.toBean(trading, NativePayResDTO.class);
}
}

View File

@ -0,0 +1,53 @@
package com.jzo2o.trade.controller.inner;
import cn.hutool.core.bean.BeanUtil;
import com.jzo2o.api.trade.RefundRecordApi;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.service.BasicPayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.math.BigDecimal;
/**
* 基础支付控制器
*
* @author itcast
*/
@RequestMapping("/inner/refund-record")
@RestController("innerRefundRecordController")
@Api(tags = "内部接口 - 退款")
public class RefundRecordController implements RefundRecordApi {
@Resource
private BasicPayService basicPayService;
/***
* 统一收单交易退款接口
* 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家
* 将在收到退款请求并且验证成功之后按照退款规则将支付款按原路退到买家帐号上
* @param tradingOrderNo 交易单号
* @param refundAmount 退款金额
* @return
*/
@Override
@PostMapping("refund")
@ApiOperation(value = "统一收单交易退款", notes = "统一收单交易退款")
@ApiImplicitParams({
@ApiImplicitParam(name = "tradingOrderNo", value = "交易单号", required = true),
@ApiImplicitParam(name = "refundAmount", value = "退款金额", required = true)
})
public ExecutionResultResDTO refundTrading(@RequestParam("tradingOrderNo") Long tradingOrderNo,
@RequestParam("refundAmount") BigDecimal refundAmount) {
RefundRecord refundRecord = this.basicPayService.refundTrading(tradingOrderNo, refundAmount);
return BeanUtil.toBean(refundRecord,ExecutionResultResDTO.class);
}
}

View File

@ -0,0 +1,84 @@
package com.jzo2o.trade.controller.inner;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.model.dto.TradingDTO;
import com.jzo2o.trade.service.BasicPayService;
import com.jzo2o.trade.service.TradingService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @author zzj
* @version 1.0
*/
@RestController("innerTradingController")
@Api(tags = "内部接口 - 交易单服务")
@RequestMapping("/inner/tradings")
public class TradingController implements TradingApi {
@Resource
private TradingService tradingService;
@Resource
private BasicPayService basicPayService;
// /**
// * 根据订单号查询已结算交易单
// *
// * @param productOrderNo 订单号
// * @return 交易单数据
// */
// @Override
// @GetMapping("/findYjsTradByProductOrderNo")
// @ApiOperation(value = "根据订单号查询已结算交易单", notes = "根据业务订单号查询交易单")
// @ApiImplicitParams({
// @ApiImplicitParam(name = "productOrderNo", value = "业务订单号", required = true, dataTypeClass = Long.class)
// })
// public TradingResDTO findYjsTradByProductOrderNo(@RequestParam("productOrderNo") Long productOrderNo) {
// List<Trading> yjsTradByProductOrderNo = tradingService.findYjsTradByProductOrderNo(productOrderNo);
// if(ObjectUtil.isNotEmpty(yjsTradByProductOrderNo)){
// Trading trading = yjsTradByProductOrderNo.get(0);
// return BeanUtil.toBean(trading, TradingResDTO.class);
// }
// return null;
// }
// @Override
// @GetMapping("/findTradByTradingOrderNo")
// @ApiOperation(value = "根据交易单号查询交易单", notes = "根据交易单号查询交易单")
// @ApiImplicitParams({
// @ApiImplicitParam(name = "tradingOrderNo", value = "交易单号", required = true, dataTypeClass = Long.class)
// })
// public TradingResDTO findTradByTradingOrderNo(Long tradingOrderNo) {
// Trading tradByTradingOrderNo = tradingService.findTradByTradingOrderNo(tradingOrderNo);
// TradingResDTO tradingResDTO = BeanUtil.toBean(tradByTradingOrderNo, TradingResDTO.class);
// return tradingResDTO;
// }
@Override
@GetMapping("/findTradResultByTradingOrderNo")
@ApiOperation(value = "根据交易单号查询交易单的交易结果", notes = "根据交易单号查询交易单的交易结果")
@ApiImplicitParams({
@ApiImplicitParam(name = "tradingOrderNo", value = "交易单号", required = true, dataTypeClass = Long.class)
})
public TradingResDTO findTradResultByTradingOrderNo(Long tradingOrderNo) {
TradingDTO tradingDTO = basicPayService.queryTradingResult(tradingOrderNo);
TradingResDTO tradingResDTO = BeanUtil.toBean(tradingDTO, TradingResDTO.class);
return tradingResDTO;
}
}

View File

@ -0,0 +1,86 @@
package com.jzo2o.trade.controller.open;
import cn.hutool.core.map.MapUtil;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.service.NotifyService;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import io.swagger.annotations.Api;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 支付结果的通知
*/
@RestController("openNotifyController")
@Api(tags = "白名单接口 - 支付通知")
@RequestMapping("/open/notify")
public class NotifyController {
@Resource
private NotifyService notifyService;
/**
* 微信支付成功回调成功后无需响应内容
*
* @param httpEntity 微信请求信息
* @param enterpriseId 商户id
* @return 正常响应200否则响应500
*/
@PostMapping("wx/{enterpriseId}")
public ResponseEntity<Object> wxPayNotify(HttpEntity<String> httpEntity, @PathVariable("enterpriseId") Long enterpriseId) {
try {
//获取请求头
HttpHeaders headers = httpEntity.getHeaders();
//构建微信请求数据对象
NotificationRequest request = new NotificationRequest.Builder()
.withSerialNumber(headers.getFirst("Wechatpay-Serial")) //证书序列号微信平台
.withNonce(headers.getFirst("Wechatpay-Nonce")) //随机串
.withTimestamp(headers.getFirst("Wechatpay-Timestamp")) //时间戳
.withSignature(headers.getFirst("Wechatpay-Signature")) //签名字符串
.withBody(httpEntity.getBody())
.build();
//微信通知的业务处理
this.notifyService.wxPayNotify(request, enterpriseId);
} catch (CommonException e) {
Map<String, Object> result = MapUtil.<String, Object>builder()
.put("code", "FAIL")
.put("message", e.getMessage())
.build();
//响应500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
return ResponseEntity.ok(null);
}
/**
* 支付宝支付成功回调成功后需要响应success
*
* @param enterpriseId 商户id
* @return 正常响应200否则响应500
*/
@PostMapping("alipay/{enterpriseId}")
public ResponseEntity<String> aliPayNotify(HttpServletRequest request,
@PathVariable("enterpriseId") Long enterpriseId) {
try {
//支付宝通知的业务处理
this.notifyService.aliPayNotify(request, enterpriseId);
} catch (CommonException e) {
//响应500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return ResponseEntity.ok("success");
}
}

View File

@ -0,0 +1,36 @@
package com.jzo2o.trade.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* 退款状态枚举
*
* @author zzj
* @version 1.0
*/
public enum RefundStatusEnum {
APPLY_REFUND(0, "发起退款"),
SENDING(1, "退款中"),
SUCCESS(2, "成功"),
FAIL(3, "失败");
@EnumValue
@JsonValue
private final Integer code;
private final String value;
RefundStatusEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
public Integer getCode() {
return this.code;
}
public String getValue() {
return this.value;
}
}

View File

@ -0,0 +1,66 @@
package com.jzo2o.trade.enums;
/**
* 交易枚举
*/
public enum TradingEnum {
SUCCEED(1001, 200, "操作成功"),
ERROR(1002, "操作失败"),
CHECK_TRADING_FAIL(1003, "交易单校验失败"),
TRY_LOCK_TRADING_FAIL(1004, "交易单加锁失败"),
PAYING_TRADING_FAIL(1005, "交易单支付失败"),
TRADING_STATE_SUCCEED(1006, "交易单已完成"),
TRADING_STATE_PAYING(1007, "交易单交易中"),
CONFIG_EMPTY(1008, "支付配置为空"),
CONFIG_ERROR(1009, "支付配置错误"),
NATIVE_PAY_FAIL(1010, "统一下单交易失败"),
NATIVE_QRCODE_FAIL(1011, "生成二维码失败"),
REFUND_FAIL(1012, "查询统一下单交易退款失败"),
SAVE_OR_UPDATE_FAIL(1013, "交易单保存或修改失败"),
TRADING_TYPE_FAIL(1014, "未定义的交易类型"),
NATIVE_QUERY_FAIL(1015, "查询统一下单交易失败"),
NATIVE_REFUND_FAIL(1016, "统一下单退款交易失败"),
NATIVE_QUERY_REFUND_FAIL(1017, "统一下单查询退款失败"),
CASH_PAY_FAIL(1018, "现金交易失败"),
CASH_REFUND_FAIL(1019, "统一下单退款交易失败"),
CREDIT_PAY_FAIL(1020, "信用交易失败"),
LIST_TRADE_STATE_FAIL(1021, "按交易状态查询交易单失败"),
NOT_FOUND(1022, "交易单不存在"),
CLOSE_FAIL(1023, "关闭交易单失败"),
BASIC_REFUND_OUT_FAIL(1024, "退款金额超过订单总金额"),
REFUND_NOT_FOUND(1025, "退款记录不存在"),
REFUND_ALREADY_COMPLETED(1026, "退款记录已经完成"),
BASIC_REFUND_COUNT_OUT_FAIL(1027, "退款次数超出限制最多20次"),
TRADING_QUERY_PARAM_ERROR(1028, "查询交易单错误,交易单号为空"),
REFUND_QUERY_PARAM_ERROR(1029, "查询退款单错误,订单号或交易单号至少传递一个"),
REFUND_DURING(1030, "退款进行中");
private final Integer code;
private final Integer status;
private final String value;
TradingEnum(Integer code, String value) {
this.code = code;
this.value = value;
this.status = 500;
}
TradingEnum(Integer code, Integer status, String value) {
this.code = code;
this.value = value;
this.status = status;
}
public Integer getCode() {
return code;
}
public String getValue() {
return this.value;
}
public Integer getStatus() {
return this.status;
}
}

View File

@ -0,0 +1,39 @@
package com.jzo2o.trade.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* 交易单状态枚举
*
* @author zzj
* @version 1.0
*/
public enum TradingStateEnum{
// DFK(1, "待付款"),
FKZ(2, "付款中"),
FKSB(3, "付款失败"),
YJS(4, "已付款"),
QXDD(5, "取消订单"),
MD(6, "免单"),
GZ(7, "挂账");
@EnumValue
@JsonValue
private final Integer code;
private final String value;
TradingStateEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
public Integer getCode() {
return this.code;
}
public String getValue() {
return this.value;
}
}

View File

@ -0,0 +1,45 @@
package com.jzo2o.trade.handler;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.model.domain.Trading;
/**
* 基础支付功能的定义具体业务由不同的支付渠道实现
*
* @author zzj
* @version 1.0
*/
public interface BasicPayHandler {
/***
* 统一收单线下交易查询
* 该接口提供所有支付订单的查询商户可以通过该接口主动查询订单状态完成下一步的业务逻辑
* @return 是否有变化
*/
Boolean queryTrading(Trading trading) throws CommonException;
/***
* 关闭交易
* @return 是否成功
*/
Boolean closeTrading(Trading trading) throws CommonException;
/***
* 统一收单交易退款接口
* 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家
* 将在收到退款请求并且验证成功之后按照退款规则将支付款按原路退到买家帐号上
* @param refundRecord 退款记录对象
* @return 是否有变化
*/
Boolean refundTrading(RefundRecord refundRecord) throws CommonException;
/***
* 统一收单交易退款查询接口
*
* @param refundRecord 退款交易单号
* @return 是否有变化
*/
Boolean queryRefundTrading(RefundRecord refundRecord) throws CommonException;
}

View File

@ -0,0 +1,52 @@
package com.jzo2o.trade.handler;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.model.dto.TradingDTO;
import java.math.BigDecimal;
/**
* 交易前置处理接口
*
* @author itcast
*/
public interface BeforePayHandler {
/***
* 交易单参数校验
* @param tradingEntity 交易订单
* @return 是否符合要求
*/
void checkCreateTrading(Trading tradingEntity);
/***
* QueryTrading交易单参数校验
* @param trading 交易订单
*/
void checkQueryTrading(Trading trading);
/***
* RefundTrading退款交易单参数校验
* @param trading 交易订单
* @param refundAmount 退款金额
*/
void checkRefundTrading(Trading trading,BigDecimal refundAmount);
/***
* QueryRefundTrading交易单参数校验
* @param refundRecord 退款记录
*/
void checkQueryRefundTrading(RefundRecord refundRecord);
/***
* CloseTrading交易单参数校验
* @param trading 交易订单
*/
void checkCloseTrading(Trading trading);
}

View File

@ -0,0 +1,33 @@
package com.jzo2o.trade.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.jzo2o.trade.annotation.PayChannel;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import java.util.Map;
/**
* Handler工厂用于获取指定类型的具体渠道的实例对象
*/
public class HandlerFactory {
private HandlerFactory() {
}
public static <T> T get(PayChannelEnum payChannel, Class<T> handler) {
Map<String, T> beans = SpringUtil.getBeansOfType(handler);
for (Map.Entry<String, T> entry : beans.entrySet()) {
PayChannel payChannelAnnotation = entry.getValue().getClass().getAnnotation(PayChannel.class);
if (ObjectUtil.isNotEmpty(payChannelAnnotation) && ObjectUtil.equal(payChannel, payChannelAnnotation.type())) {
return entry.getValue();
}
}
return null;
}
public static <T> T get(String payChannel, Class<T> handler) {
return get(PayChannelEnum.valueOf(payChannel), handler);
}
}

View File

@ -0,0 +1,18 @@
package com.jzo2o.trade.handler;
import com.jzo2o.trade.model.domain.Trading;
/**
* jsapi下单处理
*
* @author itcast
*/
public interface JsapiPayHandler {
/**
* 创建交易
*
* @param tradingEntity 交易单
*/
void createJsapiTrading(Trading tradingEntity);
}

View File

@ -0,0 +1,21 @@
package com.jzo2o.trade.handler;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.model.domain.Trading;
/**
* @author itcast
* @ClassName NativePayHandler.java
* @Description Native支付方式Handler商户生成二维码用户扫描支付
*/
public interface NativePayHandler {
/***
* @description 统一收单线下交易预创建
* 收银员通过收银台或商户后台调用此接口生成二维码后展示给用户由用户扫描二维码完成订单支付
* @param tradingEntity 交易单
*/
void createDownLineTrading(Trading tradingEntity) throws CommonException;
}

View File

@ -0,0 +1,165 @@
package com.jzo2o.trade.handler.alipay;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeCloseResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.annotation.PayChannel;
import com.jzo2o.trade.constant.TradingConstant;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.enums.RefundStatusEnum;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.BasicPayHandler;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.model.domain.Trading;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 支付宝基础支付功能的实现
*
* @author zzj
* @version 1.0
*/
@Slf4j
@Component("aliBasicPayHandler")
@PayChannel(type = PayChannelEnum.ALI_PAY)
public class AliBasicPayHandler implements BasicPayHandler {
@Override
public Boolean queryTrading(Trading trading) throws CommonException {
//查询配置
Config config = AlipayConfig.getConfig(trading.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
AlipayTradeQueryResponse queryResponse;
try {
//调用支付宝API通用查询支付情况
queryResponse = Factory
.Payment
.Common()
.query(String.valueOf(trading.getTradingOrderNo()));
} catch (Exception e) {
String msg = StrUtil.format("查询支付宝统一下单失败trading = {}", trading);
log.error(msg, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
}
//修改交易单状态
trading.setTransactionId(queryResponse.getTradeNo());
trading.setResultCode(queryResponse.getSubCode());
trading.setResultMsg(queryResponse.getSubMsg());
trading.setResultJson(JSONUtil.toJsonStr(queryResponse));
boolean success = ResponseChecker.success(queryResponse);
//响应成功分析交易状态
if (success) {
String tradeStatus = queryResponse.getTradeStatus();
if (StrUtil.equals(TradingConstant.ALI_TRADE_CLOSED, tradeStatus)) {
//支付取消TRADE_CLOSED未付款交易超时关闭或支付完成后全额退款
trading.setTradingState(TradingStateEnum.QXDD);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.ALI_TRADE_SUCCESS, TradingConstant.ALI_TRADE_FINISHED)) {
// TRADE_SUCCESS交易支付成功
// TRADE_FINISHED交易结束不可退款
trading.setTradingState(TradingStateEnum.YJS);
} else {
//非最终状态不处理当前交易状态WAIT_BUYER_PAY交易创建等待买家付款不处理
return false;
}
return true;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
}
@Override
public Boolean closeTrading(Trading trading) throws CommonException {
//查询配置
Config config = AlipayConfig.getConfig(trading.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
try {
//调用支付宝API通用查询支付情况
AlipayTradeCloseResponse closeResponse = Factory
.Payment
.Common()
.close(String.valueOf(trading.getTradingOrderNo()));
boolean success = ResponseChecker.success(closeResponse);
if (success) {
trading.setTradingState(TradingStateEnum.QXDD);
return true;
}
return false;
} catch (Exception e) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.CLOSE_FAIL.getValue());
}
}
@Override
public Boolean refundTrading(RefundRecord refundRecord) throws CommonException {
//查询配置
Config config = AlipayConfig.getConfig(refundRecord.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
//调用支付宝API通用查询支付情况
AlipayTradeRefundResponse refundResponse;
try {
// 支付宝easy sdk
refundResponse = Factory
.Payment
.Common()
//扩展参数退款单号
.optional("out_request_no", refundRecord.getRefundNo())
.refund(Convert.toStr(refundRecord.getTradingOrderNo()),
Convert.toStr(refundRecord.getRefundAmount()));
} catch (Exception e) {
String msg = StrUtil.format("调用支付宝退款接口出错refundRecord = {}", refundRecord);
log.error(msg, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, msg);
}
refundRecord.setRefundId(null);
refundRecord.setRefundCode(refundResponse.getSubCode());
refundRecord.setRefundMsg(JSONUtil.toJsonStr(refundResponse));
boolean success = ResponseChecker.success(refundResponse);
if (success) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
return true;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_REFUND_FAIL.getValue());
}
@Override
public Boolean queryRefundTrading(RefundRecord refundRecord) throws CommonException {
//查询配置
Config config = AlipayConfig.getConfig(refundRecord.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
AlipayTradeFastpayRefundQueryResponse response;
try {
response = Factory.Payment.Common().queryRefund(
Convert.toStr(refundRecord.getTradingOrderNo()),
Convert.toStr(refundRecord.getRefundNo()));
} catch (Exception e) {
log.error("调用支付宝查询退款接口出错refundRecord = {}", refundRecord, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_REFUND_FAIL.getValue());
}
refundRecord.setRefundCode(response.getSubCode());
refundRecord.setRefundMsg(JSONUtil.toJsonStr(response));
boolean success = ResponseChecker.success(response);
if (success) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
return true;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_REFUND_FAIL.getValue());
}
}

View File

@ -0,0 +1,63 @@
package com.jzo2o.trade.handler.alipay;
import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONUtil;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.facetoface.models.AlipayTradePrecreateResponse;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.annotation.PayChannel;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.NativePayHandler;
import com.jzo2o.trade.model.domain.Trading;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 支付宝的扫描支付的具体实现
*/
@Slf4j
@Component("aliNativePayHandler")
@PayChannel(type = PayChannelEnum.ALI_PAY)
public class AliNativePayHandler implements NativePayHandler {
@Override
public void createDownLineTrading(Trading tradingEntity) throws CommonException {
//查询配置
Config config = AlipayConfig.getConfig(tradingEntity.getEnterpriseId());
//Factory使用配置
Factory.setOptions(config);
AlipayTradePrecreateResponse response;
try {
//调用支付宝API面对面支付
response = Factory
.Payment
.FaceToFace()
.preCreate(tradingEntity.getMemo(), //订单描述
Convert.toStr(tradingEntity.getTradingOrderNo()), //业务订单号
Convert.toStr(tradingEntity.getTradingAmount())); //金额
} catch (Exception e) {
log.error("支付宝统一下单创建失败tradingEntity = {}", tradingEntity, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
}
//受理结果只表示请求是否成功而不是支付是否成功
boolean isSuccess = ResponseChecker.success(response);
//6.1受理成功修改交易单
if (isSuccess) {
String subCode = response.getSubCode();
String subMsg = response.getQrCode();
tradingEntity.setPlaceOrderCode(subCode); //返回的编码
tradingEntity.setPlaceOrderMsg(subMsg); //二维码需要展现的信息
tradingEntity.setPlaceOrderJson(JSONUtil.toJsonStr(response));
tradingEntity.setTradingState(TradingStateEnum.FKZ);
return;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
}
}

View File

@ -0,0 +1,50 @@
package com.jzo2o.trade.handler.alipay;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alipay.easysdk.kernel.Config;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.constant.TradingConstant;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.model.domain.PayChannel;
import com.jzo2o.trade.service.PayChannelService;
/**
* @author zzj
* @version 1.0
*/
public class AlipayConfig {
/**
* 将支付渠道配置转化为支付宝的配置
*
* @param enterpriseId 商户ID
* @return 支付宝的配置
*/
public static Config getConfig(Long enterpriseId) {
// 查询配置
PayChannelService payChannelService = SpringUtil.getBean(PayChannelService.class);
PayChannel payChannel = payChannelService.findByEnterpriseId(enterpriseId, TradingConstant.TRADING_CHANNEL_ALI_PAY);
if (ObjectUtil.isEmpty(payChannel)) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.CONFIG_EMPTY.getValue());
}
Config config = new Config();
config.protocol = "https";
config.gatewayHost = payChannel.getDomain();
config.signType = "RSA2";
config.appId = payChannel.getAppId();
//配置应用私钥
config.merchantPrivateKey = payChannel.getMerchantPrivateKey();
//配置支付宝公钥
config.alipayPublicKey = payChannel.getPublicKey();
//可设置异步通知接收服务地址可选
config.notifyUrl = payChannel.getNotifyUrl();
//设置AES密钥调用AES加解密相关接口时需要可选
config.encryptKey = payChannel.getEncryptKey();
return config;
}
}

View File

@ -0,0 +1,126 @@
package com.jzo2o.trade.handler.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.enums.RefundStatusEnum;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.BasicPayHandler;
import com.jzo2o.trade.handler.BeforePayHandler;
import com.jzo2o.trade.handler.HandlerFactory;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.model.dto.TradingDTO;
import com.jzo2o.trade.service.RefundRecordService;
import com.jzo2o.trade.service.TradingService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 交易前置处理接口
*
* @author zzj
* @version 1.0
*/
@Component
public class BeforePayHandlerImpl implements BeforePayHandler {
@Resource
private TradingService tradingService;
@Resource
private IdentifierGenerator identifierGenerator;
@Resource
private RefundRecordService refundRecordService;
@Override
public void checkCreateTrading(Trading tradingEntity) {
//校验不为为空订单备注订单号企业号交易金额支付渠道
boolean flag = ObjectUtil.isAllNotEmpty(tradingEntity,
tradingEntity.getMemo(),
tradingEntity.getProductOrderNo(),
tradingEntity.getEnterpriseId(),
tradingEntity.getTradingAmount(),
tradingEntity.getTradingChannel());
//金额不能小于等于0
boolean flag2 = !NumberUtil.isLessOrEqual(tradingEntity.getTradingAmount(), BigDecimal.valueOf(0));
if (!flag || !flag2) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.CONFIG_ERROR.getValue());
}
List<Trading> tradings = tradingService.queryByProductOrder(tradingEntity.getProductAppId(),tradingEntity.getProductOrderNo());
if (ObjectUtil.isEmpty(tradings)) {
//新交易单生成交易号
tradingEntity.setTradingOrderNo((Long) identifierGenerator.nextId(tradingEntity));
return ;
}
//找到已付款的记录
Trading finishedTrading = tradingService.findFinishedTrading(tradingEntity.getProductAppId(),tradingEntity.getProductOrderNo());
if (ObjectUtil.isNotEmpty(finishedTrading)) {
//存在已付款单子直接抛出重复支付异常
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.TRADING_STATE_SUCCEED.getValue());
}
//找到该支付渠道支付中的单子
Trading trading = tradingService.queryDuringTrading(tradingEntity.getProductAppId(),tradingEntity.getProductOrderNo(), tradingEntity.getTradingChannel());
if (ObjectUtil.isNotEmpty(trading)) {
//存在相同支付渠道的付款中单子
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.TRADING_STATE_PAYING.getValue());
}
//新交易单生成交易号
tradingEntity.setTradingOrderNo((Long) identifierGenerator.nextId(tradingEntity));
}
@Override
public void checkQueryTrading(Trading trading) {
if (ObjectUtil.isEmpty(trading)) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NOT_FOUND.getValue());
}
}
@Override
public void checkRefundTrading(Trading trading,BigDecimal refundAmount) {
if (ObjectUtil.isEmpty(trading)) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NOT_FOUND.getValue());
}
if (trading.getTradingState() != TradingStateEnum.YJS) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_REFUND_FAIL.getValue());
}
//退款总金额不可超实付总金额
if (NumberUtil.isGreater(refundAmount, trading.getTradingAmount())) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.BASIC_REFUND_OUT_FAIL.getValue());
}
}
@Override
public void checkQueryRefundTrading(RefundRecord refundRecord) {
if (ObjectUtil.isEmpty(refundRecord)) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.REFUND_NOT_FOUND.getValue());
}
if (ObjectUtil.equals(refundRecord.getRefundStatus(), RefundStatusEnum.SUCCESS)) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.REFUND_ALREADY_COMPLETED.getValue());
}
}
@Override
public void checkCloseTrading(Trading trading) {
if (ObjectUtil.notEqual(TradingStateEnum.FKZ, trading.getTradingState())) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.CLOSE_FAIL.getValue());
}
}
}

View File

@ -0,0 +1,198 @@
package com.jzo2o.trade.handler.wechat;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.annotation.PayChannel;
import com.jzo2o.trade.constant.TradingConstant;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.enums.RefundStatusEnum;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.BasicPayHandler;
import com.jzo2o.trade.handler.wechat.response.WeChatResponse;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.model.domain.Trading;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import static com.jzo2o.trade.enums.TradingEnum.NATIVE_REFUND_FAIL;
/**
* 微信基础支付功能的实现
*
* @author zzj
* @version 1.0
*/
@Slf4j
@Component("weChatBasicPayHandler")
@PayChannel(type = PayChannelEnum.WECHAT_PAY)
public class WeChatBasicPayHandler implements BasicPayHandler {
@Override
public Boolean queryTrading(Trading trading) throws CommonException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get(trading.getEnterpriseId());
//请求地址
String apiPath = StrUtil.format("/v3/pay/transactions/out-trade-no/{}", trading.getTradingOrderNo());
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("mchid", client.getMchId())
.build();
WeChatResponse response;
try {
response = client.doGet(apiPath, params);
} catch (Exception e) {
log.error("调用微信接口出错apiPath = {}, params = {}", apiPath, JSONUtil.toJsonStr(params), e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, NATIVE_REFUND_FAIL.getValue());
}
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// 交易状态枚举值
// SUCCESS支付成功
// REFUND转入退款
// NOTPAY未支付
// CLOSED已关闭
// REVOKED已撤销仅付款码支付会返回
// USERPAYING用户支付中仅付款码支付会返回
// PAYERROR支付失败仅付款码支付会返回
String tradeStatus = jsonObject.getStr("trade_state");
//已关闭或已撤单的更新状态为取消订单
if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_TRADE_CLOSED, TradingConstant.WECHAT_TRADE_REVOKED)) {
trading.setTradingState(TradingStateEnum.QXDD);
//支付成功或转入退款的更新为已付款
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_TRADE_SUCCESS, TradingConstant.WECHAT_TRADE_REFUND)) {
trading.setTradingState(TradingStateEnum.YJS);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_TRADE_PAYERROR)) {
trading.setTradingState(TradingStateEnum.FKSB);
} else {
//非最终状态不处理
return false;
}
//修改交易单状态
trading.setTransactionId(jsonObject.getStr("transaction_id"));
trading.setResultCode(tradeStatus);
trading.setResultMsg(jsonObject.getStr("trade_state_desc"));
trading.setResultJson(response.getBody());
return true;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, NATIVE_REFUND_FAIL.getValue());
}
@Override
public Boolean closeTrading(Trading trading) throws CommonException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get(trading.getEnterpriseId());
//请求地址
String apiPath = StrUtil.format("/v3/pay/transactions/out-trade-no/{}/close", trading.getTradingOrderNo());
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("mchid", client.getMchId())
.build();
try {
WeChatResponse response = client.doPost(apiPath, params);
if (response.getStatus() == 204) {
trading.setTradingState(TradingStateEnum.QXDD);
return true;
}
return false;
} catch (Exception e) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.CLOSE_FAIL.getValue());
}
}
@Override
public Boolean refundTrading(RefundRecord refundRecord) throws CommonException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get(refundRecord.getEnterpriseId());
//请求地址
String apiPath = "/v3/refund/domestic/refunds";
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("out_refund_no", Convert.toStr(refundRecord.getRefundNo()))
.put("out_trade_no", Convert.toStr(refundRecord.getTradingOrderNo()))
.put("amount", MapUtil.<String, Object>builder()
.put("refund", NumberUtil.mul(refundRecord.getRefundAmount(), 100)) //本次退款金额
.put("total", NumberUtil.mul(refundRecord.getTotal(), 100)) //原订单金额
.put("currency", "CNY") //币种
.build())
.build();
WeChatResponse response;
try {
response = client.doPost(apiPath, params);
} catch (Exception e) {
log.error("调用微信接口出错apiPath = {}, params = {}", apiPath, JSONUtil.toJsonStr(params), e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, NATIVE_REFUND_FAIL.getValue());
}
refundRecord.setRefundCode(Convert.toStr(response.getStatus()));
refundRecord.setRefundMsg(response.getBody());
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
refundRecord.setRefundId(jsonObject.getStr("refund_id"));
// SUCCESS退款成功
// CLOSED退款关闭
// PROCESSING退款处理中
// ABNORMAL退款异常
String status = jsonObject.getStr("status");
if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_PROCESSING)) {
refundRecord.setRefundStatus(RefundStatusEnum.SENDING);
} else if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_SUCCESS)) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
} else {
refundRecord.setRefundStatus(RefundStatusEnum.FAIL);
}
return true;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, NATIVE_REFUND_FAIL.getValue());
}
@Override
public Boolean queryRefundTrading(RefundRecord refundRecord) throws CommonException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get(refundRecord.getEnterpriseId());
//请求地址
String apiPath = StrUtil.format("/v3/refund/domestic/refunds/{}", refundRecord.getRefundNo());
WeChatResponse response;
try {
response = client.doGet(apiPath);
} catch (Exception e) {
log.error("调用微信接口出错apiPath = {}", apiPath, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_REFUND_FAIL.getValue());
}
refundRecord.setRefundCode(Convert.toStr(response.getStatus()));
refundRecord.setRefundMsg(response.getBody());
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
refundRecord.setRefundId(jsonObject.getStr("refund_id"));
// SUCCESS退款成功
// CLOSED退款关闭
// PROCESSING退款处理中
// ABNORMAL退款异常
String status = jsonObject.getStr("status");
if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_PROCESSING)) {
refundRecord.setRefundStatus(RefundStatusEnum.SENDING);
} else if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_SUCCESS)) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
} else {
refundRecord.setRefundStatus(RefundStatusEnum.FAIL);
}
return true;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_REFUND_FAIL.getValue());
}
}

View File

@ -0,0 +1,117 @@
package com.jzo2o.trade.handler.wechat;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.annotation.PayChannel;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.JsapiPayHandler;
import com.jzo2o.trade.handler.wechat.bean.JsapiPayParam;
import com.jzo2o.trade.handler.wechat.response.WeChatResponse;
import com.jzo2o.trade.model.domain.Trading;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.Map;
/**
* 微信jsapi的实现
*
* @author zzj
* @version 1.0
*/
@Component("wechatJsapiPayHandler")
@PayChannel(type = PayChannelEnum.WECHAT_PAY)
public class WechatJsapiPayHandler implements JsapiPayHandler {
@Override
public void createJsapiTrading(Trading tradingEntity) {
// 查询配置
WechatPayHttpClient client = WechatPayHttpClient.get(tradingEntity.getEnterpriseId());
//请求地址
String apiPath = "/v3/pay/transactions/jsapi";
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("mchid", client.getMchId())
.put("appid", client.getAppId())
.put("description", tradingEntity.getMemo())
.put("notify_url", client.getNotifyUrl())
.put("out_trade_no", Convert.toStr(tradingEntity.getTradingOrderNo()))
.put("amount", MapUtil.<String, Object>builder()
.put("total", Convert.toInt(NumberUtil.mul(tradingEntity.getTradingAmount(), 100))) //金额单位
.put("currency", "CNY") //人民币
.build())
.put("payer", MapUtil.<String, Object>builder()
.put("openid", tradingEntity.getOpenId()) //用户识别标识
.build())
.build();
try {
WeChatResponse response = client.doPost(apiPath, params);
if (!response.isOk()) {
//下单失败
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
}
//指定统一下单code
tradingEntity.setPlaceOrderCode(Convert.toStr(response.getStatus()));
//jsapi发起支付需要的预支付id
tradingEntity.setPlaceOrderMsg(JSONUtil.parseObj(response.getBody()).getStr("prepay_id"));
//指定交易状态
tradingEntity.setTradingState(TradingStateEnum.FKZ);
//封装JSAPI调起支付的参数给前端使用
Long timeStamp = System.currentTimeMillis() / 1000;
String nonceStr = IdUtil.simpleUUID();
String packages = "prepay_id=" + tradingEntity.getPlaceOrderMsg();
JsapiPayParam jsapiPayParam = JsapiPayParam.builder()
.timeStamp(timeStamp)
.appId(client.getAppId())
.nonceStr(nonceStr)
.packages(packages)
.paySign(this.createPaySign(client, timeStamp, nonceStr, packages))
.build();
//设置jsapi调起支付的参数
tradingEntity.setPlaceOrderJson(JSONUtil.toJsonStr(jsapiPayParam));
} catch (Exception e) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
}
}
/**
* 生成
*
* @param client 微信client对象
* @param timeStamp 时间戳
* @param nonceStr 随机数
* @param packages 预支付字符串
* @return 签名字符串
* @throws Exception 不处理异常全部抛出
*/
private String createPaySign(WechatPayHttpClient client, Long timeStamp, String nonceStr, String packages) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
// 加载商户私钥
PrivateKey privateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(client.getPrivateKey().getBytes(CharsetUtil.CHARSET_UTF_8)));
sign.initSign(privateKey);
String message = StrUtil.format("{}\n{}\n{}\n{}\n",
client.getAppId(),
timeStamp,
nonceStr,
packages);
sign.update(message.getBytes());
return Base64.getEncoder().encodeToString(sign.sign());
}
}

View File

@ -0,0 +1,67 @@
package com.jzo2o.trade.handler.wechat;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.annotation.PayChannel;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.NativePayHandler;
import com.jzo2o.trade.handler.wechat.response.WeChatResponse;
import com.jzo2o.trade.model.domain.Trading;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 微信二维码支付
*
* @author zzj
* @version 1.0
*/
@Component("wechatNativePayHandler")
@PayChannel(type = PayChannelEnum.WECHAT_PAY)
public class WechatNativePayHandler implements NativePayHandler {
@Override
public void createDownLineTrading(Trading tradingEntity) throws CommonException {
// 查询配置
WechatPayHttpClient client = WechatPayHttpClient.get(tradingEntity.getEnterpriseId());
//请求地址
String apiPath = "/v3/pay/transactions/native";
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("mchid", client.getMchId())
.put("appid", client.getAppId())
.put("description", tradingEntity.getMemo())
.put("notify_url", client.getNotifyUrl())
.put("out_trade_no", Convert.toStr(tradingEntity.getTradingOrderNo()))
.put("amount", MapUtil.<String, Object>builder()
.put("total", Convert.toInt(NumberUtil.mul(tradingEntity.getTradingAmount(), 100))) //金额单位
.put("currency", "CNY") //人民币
.build())
.build();
try {
WeChatResponse response = client.doPost(apiPath, params);
if (!response.isOk()) {
//下单失败
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
}
//指定统一下单code
tradingEntity.setPlaceOrderCode(Convert.toStr(response.getStatus()));
//二维码需要展现的信息
tradingEntity.setPlaceOrderMsg(JSONUtil.parseObj(response.getBody()).getStr("code_url"));
//指定统一下单json字符串
tradingEntity.setPlaceOrderJson(JSONUtil.toJsonStr(response));
} catch (Exception e) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
}
}
}

View File

@ -0,0 +1,161 @@
package com.jzo2o.trade.handler.wechat;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlPath;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.constant.TradingConstant;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.handler.wechat.response.WeChatResponse;
import com.jzo2o.trade.model.domain.PayChannel;
import com.jzo2o.trade.service.PayChannelService;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.security.PrivateKey;
import java.util.Map;
/**
* 微信支付远程调用对象
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WechatPayHttpClient {
private String mchId; //商户号
private String appId; //商户号
private String privateKey; //私钥字符串
private String mchSerialNo; //商户证书序列号
private String apiV3Key; //V3密钥
private String domain; //请求域名
private String notifyUrl; //请求地址
public static WechatPayHttpClient get(Long enterpriseId) {
// 查询配置
PayChannelService payChannelService = SpringUtil.getBean(PayChannelService.class);
PayChannel payChannel = payChannelService.findByEnterpriseId(enterpriseId, TradingConstant.TRADING_CHANNEL_WECHAT_PAY);
if (ObjectUtil.isEmpty(payChannel)) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.CONFIG_EMPTY.getValue());
}
//通过渠道对象转化成微信支付的client对象
JSONObject otherConfig = JSONUtil.parseObj(payChannel.getOtherConfig());
return WechatPayHttpClient.builder()
.appId(payChannel.getAppId())
.domain(payChannel.getDomain())
.privateKey(payChannel.getMerchantPrivateKey())
.mchId(otherConfig.getStr("mchId"))
.mchSerialNo(otherConfig.getStr("mchSerialNo"))
.apiV3Key(otherConfig.getStr("apiV3Key"))
.notifyUrl(payChannel.getNotifyUrl())
.build();
}
/***
* 构建CloseableHttpClient远程请求对象
* @return {@link CloseableHttpClient}
*/
public CloseableHttpClient createHttpClient() throws Exception {
// 加载商户私钥privateKey私钥字符串
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书mchId商户号,mchSerialNo商户证书序列号,apiV3KeyV3密钥
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, merchantPrivateKey);
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(
mchId, privateKeySigner);
// 向证书管理器增加需要自动更新平台证书的商户信息
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes("utf-8"));
// 初始化httpClient
return com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(mchId)))
.build();
}
/***
* 支持post请求的远程调用
* @param apiPath api地址
* @param params 携带请求参数
* @return 返回字符串
*/
public WeChatResponse doPost(String apiPath, Map<String, Object> params) throws Exception {
String url = StrUtil.format("https://{}{}", this.domain, apiPath);
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
String body = JSONUtil.toJsonStr(params);
httpPost.setEntity(new StringEntity(body, CharsetUtil.UTF_8));
CloseableHttpResponse response = this.createHttpClient().execute(httpPost);
return new WeChatResponse(response);
}
/***
* 支持get请求的远程调用
* @param apiPath api地址
* @param params 在路径中请求的参数
* @return 返回字符串
*/
public WeChatResponse doGet(String apiPath, Map<String, Object> params) throws Exception {
URI uri = UrlBuilder.create()
.setHost(this.domain)
.setScheme("https")
.setPath(UrlPath.of(apiPath, CharsetUtil.CHARSET_UTF_8))
.setQuery(UrlQuery.of(params))
.setCharset(CharsetUtil.CHARSET_UTF_8)
.toURI();
return this.doGet(uri);
}
/***
* 支持get请求的远程调用
* @param apiPath api地址
* @return 返回字符串
*/
public WeChatResponse doGet(String apiPath) throws Exception {
URI uri = UrlBuilder.create()
.setHost(this.domain)
.setScheme("https")
.setPath(UrlPath.of(apiPath, CharsetUtil.CHARSET_UTF_8))
.setCharset(CharsetUtil.CHARSET_UTF_8)
.toURI();
return this.doGet(uri);
}
private WeChatResponse doGet(URI uri) throws Exception {
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = this.createHttpClient().execute(httpGet);
return new WeChatResponse(response);
}
}

View File

@ -0,0 +1,50 @@
package com.jzo2o.trade.handler.wechat.bean;
import cn.hutool.core.annotation.Alias;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class JsapiPayParam {
/**
* 由微信生成的应用ID全局唯一
* 请求基础下单接口时请注意APPID的应用属性例如公众号场景下需使用应用属性为公众号的服务号APPID
*/
private String appId;
/**
* 时间戳标准北京时间时区为东八区
* 自1970年1月1日 0点0分0秒以来的秒数
* 注意部分系统取到的值为毫秒级需要转换成秒(10位数字)
*/
private Long timeStamp;
/**
* 随机字符串不长于32位
*/
private String nonceStr;
/**
* JSAPI下单接口返回的prepay_id参数值提交格式如
* prepay_id=wx201410272009395522657a690389285100
*/
@Alias("package")
private String packages;
/**
* 签名类型默认为RSA仅支持RSA
*/
private String signType = "RSA";
/**
* 签名使用字段appIdtimeStampnonceStrpackage计算得出的签名值
*/
private String paySign;
}

View File

@ -0,0 +1,35 @@
package com.jzo2o.trade.handler.wechat.response;
import cn.hutool.core.util.CharsetUtil;
import lombok.Data;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
/**
* @author zzj
* @version 1.0
*/
@Data
public class WeChatResponse {
private int status; //响应状态码
private String body; //响应体数据
public WeChatResponse() {
}
public WeChatResponse(CloseableHttpResponse response) {
this.status = response.getStatusLine().getStatusCode();
try {
this.body = EntityUtils.toString(response.getEntity(), CharsetUtil.UTF_8);
} catch (Exception e) {
// 如果出现异常响应体为null
}
}
public Boolean isOk() {
return this.status == 200;
}
}

View File

@ -0,0 +1,163 @@
package com.jzo2o.trade.job;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.jzo2o.common.constants.MqConstants;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.rabbitmq.client.RabbitClient;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.BasicPayHandler;
import com.jzo2o.trade.handler.HandlerFactory;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.model.dto.TradingDTO;
import com.jzo2o.trade.service.BasicPayService;
import com.jzo2o.trade.service.RefundRecordService;
import com.jzo2o.trade.service.TradingService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
/**
* 交易任务主要是查询订单的支付状态 退款的成功状态
*
* @author zzj
* @version 1.0
*/
@Slf4j
@Component
public class TradeJob {
@Value("${jzo2o.job.trading.count:100}")
private Integer tradingCount;
@Value("${jzo2o.job.refund.count:100}")
private Integer refundCount;
@Resource
private TradingService tradingService;
@Resource
private RefundRecordService refundRecordService;
@Resource
private BasicPayService basicPayService;
@Resource
private RabbitClient rabbitClient;
/**
* 分片广播方式查询支付状态
* 逻辑每次最多查询{tradingCount}个未完成的交易单交易单id与shardTotal取模值等于shardIndex进行处理
*/
@XxlJob("tradingJob")
public void tradingJob() {
// 分片参数
int shardIndex = NumberUtil.max(XxlJobHelper.getShardIndex(), 0);
int shardTotal = NumberUtil.max(XxlJobHelper.getShardTotal(), 1);
List<Trading> list = this.tradingService.findListByTradingState(TradingStateEnum.FKZ, tradingCount);
if (CollUtil.isEmpty(list)) {
XxlJobHelper.log("查询到交易单列表为空shardIndex = {}, shardTotal = {}", shardIndex, shardTotal);
return;
}
//定义消息通知列表只要是状态不为付款中就需要通知其他系统
List<TradeStatusMsg> tradeMsgList = new ArrayList<>();
for (Trading trading : list) {
if (trading.getTradingOrderNo() % shardTotal != shardIndex) {
continue;
}
try {
//查询交易单
TradingDTO tradingDTO = this.basicPayService.queryTradingResult(trading.getTradingOrderNo());
if (TradingStateEnum.FKZ != tradingDTO.getTradingState()) {
TradeStatusMsg tradeStatusMsg = TradeStatusMsg.builder()
.tradingOrderNo(trading.getTradingOrderNo())
.productOrderNo(trading.getProductOrderNo())
.productAppId(trading.getProductAppId())
.transactionId(tradingDTO.getTransactionId())
.tradingChannel(tradingDTO.getTradingChannel())
.statusCode(tradingDTO.getTradingState().getCode())
.statusName(tradingDTO.getTradingState().name())
.info(tradingDTO.getMemo())//备注信息
.build();
tradeMsgList.add(tradeStatusMsg);
}else{
//如果是未支付需要判断下时间超过20分钟未支付的订单需要关闭订单以及设置状态为QXDD
long between = LocalDateTimeUtil.between(trading.getCreateTime(), LocalDateTimeUtil.now(), ChronoUnit.MINUTES);
if (between >= 20) {
try {
basicPayService.closeTrading(trading.getTradingOrderNo());
} catch (Exception e) {
log.error("超过20分钟未支付自动关单出现异常,交易单号:{}",trading.getTradingOrderNo());
}
}
}
} catch (Exception e) {
XxlJobHelper.log("查询交易单出错shardIndex = {}, shardTotal = {}, trading = {}", shardIndex, shardTotal, trading, e);
}
}
if (CollUtil.isEmpty(tradeMsgList)) {
return;
}
//发送消息通知其他系统
String msg = JSONUtil.toJsonStr(tradeMsgList);
rabbitClient.sendMsg(MqConstants.Exchanges.TRADE, MqConstants.RoutingKeys.TRADE_UPDATE_STATUS, msg);
}
/**
* 分片广播方式查询退款状态
*/
// @XxlJob("refundJob")
// public void refundJob() {
// // 分片参数
// int shardIndex = NumberUtil.max(XxlJobHelper.getShardIndex(), 0);
// int shardTotal = NumberUtil.max(XxlJobHelper.getShardTotal(), 1);
//
// List<RefundRecordEntity> list = this.refundRecordService.findListByRefundStatus(RefundStatusEnum.SENDING, refundCount);
// if (CollUtil.isEmpty(list)) {
// XxlJobHelper.log("查询到退款单列表为空shardIndex = {}, shardTotal = {}", shardIndex, shardTotal);
// return;
// }
//
// //定义消息通知列表只要是状态不为退款中就需要通知其他系统
// List<TradeStatusMsg> tradeMsgList = new ArrayList<>();
//
// for (RefundRecordEntity refundRecord : list) {
// if (refundRecord.getRefundNo() % shardTotal != shardIndex) {
// continue;
// }
// try {
// //查询退款单
// RefundRecordDTO refundRecordDTO = this.basicPayService.queryRefundTrading(refundRecord.getRefundNo());
// if (RefundStatusEnum.SENDING != refundRecordDTO.getRefundStatus()) {
// TradeStatusMsg tradeStatusMsg = TradeStatusMsg.builder()
// .tradingOrderNo(refundRecord.getTradingOrderNo())
// .productOrderNo(refundRecord.getProductOrderNo())
// .refundNo(refundRecord.getRefundNo())
// .statusCode(refundRecord.getRefundStatus().getCode())
// .statusName(refundRecord.getRefundStatus().name())
// .build();
// tradeMsgList.add(tradeStatusMsg);
// }
// } catch (Exception e) {
// XxlJobHelper.log("查询退款单出错shardIndex = {}, shardTotal = {}, refundRecord = {}", shardIndex, shardTotal, refundRecord, e);
// }
// }
//
// if (CollUtil.isEmpty(tradeMsgList)) {
// return;
// }
//
// //发送消息通知其他系统
// String msg = JSONUtil.toJsonStr(tradeMsgList);
// this.mqFeign.sendMsg(Constants.MQ.Exchanges.TRADE, Constants.MQ.RoutingKeys.REFUND_UPDATE_STATUS, msg);
// }
}

View File

@ -0,0 +1,13 @@
package com.jzo2o.trade.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jzo2o.trade.model.domain.PayChannel;
import org.apache.ibatis.annotations.Mapper;
/**
* 交易渠道表Mapper接口
*/
@Mapper
public interface PayChannelMapper extends BaseMapper<PayChannel> {
}

View File

@ -0,0 +1,13 @@
package com.jzo2o.trade.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jzo2o.trade.model.domain.RefundRecord;
import org.apache.ibatis.annotations.Mapper;
/**
* 退款记录表Mapper接口
*/
@Mapper
public interface RefundRecordMapper extends BaseMapper<RefundRecord> {
}

View File

@ -0,0 +1,13 @@
package com.jzo2o.trade.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jzo2o.trade.model.domain.Trading;
import org.apache.ibatis.annotations.Mapper;
/**
* 交易订单表Mapper接口
*/
@Mapper
public interface TradingMapper extends BaseMapper<Trading> {
}

View File

@ -0,0 +1,96 @@
package com.jzo2o.trade.model.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author itcast
* @Description交易渠道表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("pay_channel")
public class PayChannel implements Serializable {
private static final long serialVersionUID = -1452774366739615656L;
/**
* 主键
*/
private Long id;
/**
* 通道名称
*/
private String channelName;
/**
* 通道唯一标记
*/
private String channelLabel;
/**
* 域名
*/
private String domain;
/**
* 商户appid
*/
private String appId;
/**
* 支付公钥
*/
private String publicKey;
/**
* 商户私钥
*/
private String merchantPrivateKey;
/**
* 其他配置
*/
private String otherConfig;
/**
* AES混淆密钥
*/
private String encryptKey;
/**
* 说明
*/
private String remark;
/**
* 回调地址
*/
private String notifyUrl;
/**
* 是否有效
*/
protected String enableFlag;
/**
* 商户号
*/
private Long enterpriseId;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,103 @@
package com.jzo2o.trade.model.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jzo2o.trade.enums.RefundStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* @author itcast
* @Description退款记录表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("refund_record")
public class RefundRecord implements Serializable {
private static final long serialVersionUID = -3998253241655800061L;
/**
* 主键
*/
private Long id;
/**
* 交易系统订单号对于三方来说商户订单
*/
private Long tradingOrderNo;
/**
* 业务系统应用标识
*/
private String productAppId;
/**
* 业务系统订单号
*/
private Long productOrderNo;
/**
* 本次退款订单号
*/
private Long refundNo;
/**
* 第三方支付的退款单号
*/
private String refundId;
/**
* 商户号
*/
private Long enterpriseId;
/**
* 退款渠道支付宝微信现金
*/
private String tradingChannel;
/**
* 退款状态
*/
private RefundStatusEnum refundStatus;
/**
* 返回编码
*/
private String refundCode;
/**
* 返回信息
*/
private String refundMsg;
/**
* 备注订单门店桌台信息
*/
private String memo;
/**
* 本次退款金额
*/
private BigDecimal refundAmount;
/**
* 原订单金额
*/
private BigDecimal total;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,162 @@
package com.jzo2o.trade.model.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jzo2o.trade.enums.TradingStateEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* @author itcast
* @Description交易订单表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("trading")
public class Trading implements Serializable {
private static final long serialVersionUID = -3427581867070559590L;
/**
* 主键
*/
private Long id;
/**
* openId标识
*/
private String openId;
/**
* 业务系统应用标识
*/
private String productAppId;
/**
* 业务系统订单号
*/
private Long productOrderNo;
/**
* 交易系统订单号对于三方来说商户订单
*/
private Long tradingOrderNo;
/**
* 第三方支付的交易号
*/
private String transactionId;
/**
* 支付渠道支付宝微信现金免单挂账
*/
private String tradingChannel;
/**
* 交易类型付款退款免单挂账
*/
private String tradingType;
/**
* 交易单状态DFK待付款,FKZ付款中,QXDD取消订单,YJS已结算,MD免单,GZ挂账
*/
private TradingStateEnum tradingState;
/**
* 收款人姓名
*/
private String payeeName;
/**
* 收款人账户ID
*/
private Long payeeId;
/**
* 付款人姓名
*/
private String payerName;
/**
* 付款人Id
*/
private Long payerId;
/**
* 交易金额
*/
private BigDecimal tradingAmount;
/**
* 退款金额付款后
*/
private BigDecimal refund;
/**
* 是否有退款YESNO
*/
private String isRefund;
/**
* 第三方交易返回编码最终确认交易结果
*/
private String resultCode;
/**
* 第三方交易返回提示消息最终确认交易信息
*/
private String resultMsg;
/**
* 第三方交易返回信息json分析交易最终信息
*/
private String resultJson;
/**
* 统一下单返回编码
*/
private String placeOrderCode;
/**
* 统一下单返回信息
*/
private String placeOrderMsg;
/**
* 统一下单返回信息json用于生产二维码Android ios唤醒支付等
*/
private String placeOrderJson;
/**
* 商户号
*/
private Long enterpriseId;
/**
* 备注订单门店桌台信息
*/
private String memo;
/**
* 二维码base64数据
*/
private String qrCode;
/**
* 是否有效
*/
protected String enableFlag;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,55 @@
package com.jzo2o.trade.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @ClassName PayChannelVo.java
* @Description 支付通道
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("支付通道对象")
public class PayChannelDTO {
@ApiModelProperty(value = "主键")
private Long id;
@ApiModelProperty(value = "通道名称")
private String channelName;
@ApiModelProperty(value = "通道唯一标记")
private String channelLabel;
@ApiModelProperty(value = "域名")
private String domain;
@ApiModelProperty(value = "商户appid")
private String appId;
@ApiModelProperty(value = "公钥")
private String publicKey;
@ApiModelProperty(value = "商户私钥")
private String merchantPrivateKey;
@ApiModelProperty(value = "其他配置")
private String otherConfig;
@ApiModelProperty(value = "AES混淆密钥")
private String encryptKey;
@ApiModelProperty(value = "说明")
private String remark;
@ApiModelProperty(value = "选中节点")
private String[] checkedIds;
@ApiModelProperty(value = "回调地址")
private String notifyUrl;
@ApiModelProperty(value = "是否有效")
protected String enableFlag;
@ApiModelProperty(value = "商户号")
private Long enterpriseId;
@ApiModelProperty(value = "创建时间")
protected LocalDateTime createdTime;
@ApiModelProperty(value = "更新时间")
protected LocalDateTime updatedTime;
}

View File

@ -0,0 +1,53 @@
package com.jzo2o.trade.model.dto;
import com.jzo2o.trade.enums.RefundStatusEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* @Description
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("退款对象")
public class RefundRecordDTO {
@ApiModelProperty(value = "主键")
private Long id;
@ApiModelProperty(value = "交易系统订单号【对于三方来说:商户订单】")
private Long tradingOrderNo;
@ApiModelProperty(value = "业务系统应用标识")
private String productAppId;
@ApiModelProperty(value = "业务系统订单号")
private Long productOrderNo;
@ApiModelProperty(value = "本次退款订单号")
private String refundNo;
@ApiModelProperty(value = "商户号")
private Long enterpriseId;
@ApiModelProperty(value = "退款渠道【支付宝、微信、现金】")
private String tradingChannel;
@ApiModelProperty(value = "退款状态")
private RefundStatusEnum refundStatus;
@ApiModelProperty(value = "返回编码")
private String refundCode;
@ApiModelProperty(value = "返回信息")
private String refundMsg;
@ApiModelProperty(value = "备注【订单门店,桌台信息】")
private String memo;
@ApiModelProperty(value = "原订单金额")
private BigDecimal total;
@ApiModelProperty(value = "创建时间")
protected LocalDateTime created;
@ApiModelProperty(value = "更新时间")
protected LocalDateTime updated;
}

View File

@ -0,0 +1,82 @@
package com.jzo2o.trade.model.dto;
import com.jzo2o.trade.enums.TradingStateEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("交易数据对象")
public class TradingDTO {
@ApiModelProperty(value = "主键")
private Long id;
@ApiModelProperty(value = "openId标识")
private String openId;
@ApiModelProperty(value = "业务系统应用标识")
private String productAppId;
@ApiModelProperty(value = "业务系统订单号")
private Long productOrderNo;
@ApiModelProperty(value = "交易系统订单号【对于三方来说:商户订单】")
private Long tradingOrderNo;
@ApiModelProperty(value = "第三方支付的交易号")
private String transactionId;
@ApiModelProperty(value = "支付渠道【支付宝、微信、现金、免单挂账】")
private String tradingChannel;
@ApiModelProperty(value = "交易类型【付款、退款、免单、挂账】")
private String tradingType;
@ApiModelProperty(value = "交易单状态【DFK待付款,FKZ付款中,QXDD取消订单,YJS已结算,MD免单,GZ挂账】")
private TradingStateEnum tradingState;
@ApiModelProperty(value = "收款人姓名")
private String payeeName;
@ApiModelProperty(value = "收款人账户ID")
private Long payeeId;
@ApiModelProperty(value = "付款人姓名")
private String payerName;
@ApiModelProperty(value = "付款人Id")
private Long payerId;
@ApiModelProperty(value = "交易金额,单位:元")
private BigDecimal tradingAmount;
@ApiModelProperty(value = "退款金额【付款后】,单位:元")
private BigDecimal refund;
@ApiModelProperty(value = "是否有退款YESNO")
private String isRefund;
@ApiModelProperty(value = "第三方交易返回编码【最终确认交易结果】")
private String resultCode;
@ApiModelProperty(value = "第三方交易返回提示消息【最终确认交易信息】")
private String resultMsg;
@ApiModelProperty(value = "第三方交易返回信息json【分析交易最终信息】")
private String resultJson;
@ApiModelProperty(value = "统一下单返回编码")
private String placeOrderCode;
@ApiModelProperty(value = "统一下单返回信息")
private String placeOrderMsg;
@ApiModelProperty(value = "统一下单返回信息json【用于生产二维码、Android ios唤醒支付等】")
private String placeOrderJson;
@ApiModelProperty(value = "商户号")
private Long enterpriseId;
@ApiModelProperty(value = "备注,如:运费")
private String memo;
@ApiModelProperty(value = "二维码base64数据")
private String qrCode;
@ApiModelProperty(value = "是否有效")
protected String enableFlag;
@ApiModelProperty(value = "退款请求号")
private String outRequestNo;
@ApiModelProperty(value = "操作退款金额")
private BigDecimal operTionRefund;
@ApiModelProperty(value = "创建时间")
protected LocalDateTime created;
@ApiModelProperty(value = "更新时间")
protected LocalDateTime updated;
}

View File

@ -0,0 +1,32 @@
package com.jzo2o.trade.model.dto.request;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* jsapi提交支付请求对象
*
* @author zzj
* @version 1.0
*/
@Data
public class JsapiPayReqDTO {
@ApiModelProperty(value = "openId标识", required = true)
private String openId;
@ApiModelProperty(value = "商户号", required = true)
private Long enterpriseId;
@ApiModelProperty(value = "业务系统应用标识", required = true)
private String productAppId;
@ApiModelProperty(value = "业务系统订单号", required = true)
private Long productOrderNo;
@ApiModelProperty(value = "支付渠道", required = true)
private PayChannelEnum tradingChannel;
@ApiModelProperty(value = "交易金额,单位:元", required = true)
private BigDecimal tradingAmount;
@ApiModelProperty(value = "备注,如:运费", required = true)
private String memo;
}

View File

@ -0,0 +1,26 @@
package com.jzo2o.trade.model.dto.response;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 扫码支付响应数据
*
* @author zzj
* @version 1.0
*/
@Data
public class JsapiPayResDTO {
@ApiModelProperty(value = "业务系统订单号")
private Long productOrderNo;
@ApiModelProperty(value = "交易系统订单号【对于三方来说:商户订单】")
private Long tradingOrderNo;
@ApiModelProperty(value = "支付渠道【支付宝、微信、现金、免单挂账】")
private String tradingChannel;
@ApiModelProperty(value = "统一下单返回信息,预支付编号")
private String placeOrderMsg;
@ApiModelProperty(value = "统一下单返回信息json【用于生产二维码、Android ios唤醒支付等】")
private String placeOrderJson;
}

View File

@ -0,0 +1,23 @@
package com.jzo2o.trade.model.dto.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 交易单状态响应数据
*
* @author itcast
* @create 2023/9/11 10:41
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("交易单状态响应数据")
public class TradingStateResDTO {
@ApiModelProperty(value = "交易单状态2付款中4已结算")
private Integer tradingState;
}

View File

@ -0,0 +1,68 @@
package com.jzo2o.trade.service;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.model.dto.RefundRecordDTO;
import com.jzo2o.trade.model.dto.TradingDTO;
import java.math.BigDecimal;
/**
* 支付的基础功能
*
* @author zzj
* @version 1.0
*/
public interface BasicPayService {
/***
* 统一收单线下交易查询
* 该接口提供所有支付订单的查询商户可以通过该接口主动查询订单状态完成下一步的业务逻辑
* @param tradingOrderNo 交易单号
* @return 交易数据对象
*/
TradingDTO queryTradingResult(Long tradingOrderNo) throws CommonException;
/***
* 统一收单交易退款接口
* 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家
* 将在收到退款请求并且验证成功之后按照退款规则将支付款按原路退到买家帐号上
*
*
* @param tradingOrderNo 交易单号
* @param refundAmount 退款金额不能大于总支付的总金额
* @return 是否成功
*/
RefundRecord refundTrading(Long tradingOrderNo, BigDecimal refundAmount) throws CommonException;
// /**
// * 通过业务订单号进行退款方便业务系统接入
// *
// * @param tradingOrderNo 交易单号
// * @param refundAmount 退款金额不能大于总支付的总金额
// * @return
// * @throws CommonException
// */
// ExecutionResultResDTO refundTradingByTradingOrderNo(Long tradingOrderNo, BigDecimal refundAmount) throws CommonException;
/***
* 统一收单交易退款查询接口
* @param refundNo 退款单号
* @return 退款记录数据
*/
RefundRecordDTO queryRefundTrading(Long refundNo) throws CommonException;
/***
* 对于退款中的记录需要同步退款状态
* @param tradingOrderNo 交易单号
*/
void syncRefundResult(Long tradingOrderNo) throws CommonException;
/***
* 关闭交易单
* @param tradingOrderNo 交易单号
* @return 是否成功
*/
Boolean closeTrading(Long tradingOrderNo) throws CommonException;
}

View File

@ -0,0 +1,22 @@
package com.jzo2o.trade.service;
import com.jzo2o.trade.model.domain.Trading;
/**
* jsapi支付微信小程序支付
*
* @author zzj
* @version 1.0
*/
public interface JsapiPayService {
/***
* 统一jsapi交易预创建
* 商户系统先调用该接口在微信支付服务后台生成预支付交易单返回正确的预支付交易会话标识后再按Native
* JSAPIAPP等不同场景生成交易串调起支付
* @param tradingEntity 交易单
*
* @return 交易单支付串码
*/
Trading createJsapiTrading(Trading tradingEntity);
}

View File

@ -0,0 +1,19 @@
package com.jzo2o.trade.service;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.model.domain.Trading;
/**
* 二维码支付
*/
public interface NativePayService {
/***
* 扫码支付收银员通过收银台或商户后台调用此接口生成二维码后展示给用户由用户扫描二维码完成订单支付
*
* @param changeChannel 是否切换二维码
* @param tradingEntity 扫码支付提交参数
* @return 交易数据
*/
Trading createDownLineTrading(boolean changeChannel, Trading tradingEntity);
}

View File

@ -0,0 +1,34 @@
package com.jzo2o.trade.service;
import com.jzo2o.common.expcetions.CommonException;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import javax.servlet.http.HttpServletRequest;
/**
* 支付通知
*
* @author zzj
* @version 1.0
*/
public interface NotifyService {
/**
* 微信支付通知官方文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
*
* @param request 微信请求对象
* @param enterpriseId 商户id
* @throws CommonException 抛出异常通过异常决定是否响应200
*/
void wxPayNotify(NotificationRequest request, Long enterpriseId) throws CommonException;
/**
* 支付宝支付通知官方文档https://opendocs.alipay.com/open/194/103296?ref=api
*
* @param request 请求对象
* @param enterpriseId 商户id
* @throws CommonException 抛出异常通过异常决定是否响应200
*/
void aliPayNotify(HttpServletRequest request, Long enterpriseId) throws CommonException;
}

View File

@ -0,0 +1,60 @@
package com.jzo2o.trade.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jzo2o.trade.model.domain.PayChannel;
import com.jzo2o.trade.model.dto.PayChannelDTO;
import java.util.List;
/**
* @Description 支付通道服务类
*/
public interface PayChannelService extends IService<PayChannel> {
/**
* @param payChannelDTO 查询条件
* @param pageNum 当前页
* @param pageSize 当前页
* @return Page<PayChannel> 分页对象
* @Description 支付通道列表
*/
Page<PayChannel> findPayChannelPage(PayChannelDTO payChannelDTO, int pageNum, int pageSize);
/**
* 根据商户id查询渠道配置该配置会被缓存10分钟
*
* @param enterpriseId 商户id
* @param channelLabel 通道唯一标记
* @return PayChannelEntity 交易渠道对象
*/
PayChannel findByEnterpriseId(Long enterpriseId, String channelLabel);
/**
* @param payChannelDTO 对象信息
* @return PayChannelEntity 交易渠道对象
* @Description 创建支付通道
*/
PayChannel createPayChannel(PayChannelDTO payChannelDTO);
/**
* @param payChannelDTO 对象信息
* @return Boolean 是否成功
* @Description 修改支付通道
*/
Boolean updatePayChannel(PayChannelDTO payChannelDTO);
/**
* @param checkedIds 选择的支付通道ID
* @return Boolean 是否成功
* @Description 删除支付通道
*/
Boolean deletePayChannel(String[] checkedIds);
/**
* @param channelLabel 支付通道标识
* @return 支付通道列表
* @Description 查找渠道标识
*/
List<PayChannel> findPayChannelList(String channelLabel);
}

View File

@ -0,0 +1,23 @@
package com.jzo2o.trade.service;
import com.jzo2o.api.trade.enums.PayChannelEnum;
public interface QRCodeService {
/**
* 生成二维码
*
* @param content 二维码中的内容
* @return 图片base64数据
*/
String generate(String content);
/**
* 生成二维码带logo
*
* @param content 二维码中的内容
* @param payChannel 付款渠道
* @return 图片base64数据
*/
String generate(String content, PayChannelEnum payChannel);
}

View File

@ -0,0 +1,47 @@
package com.jzo2o.trade.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jzo2o.trade.enums.RefundStatusEnum;
import com.jzo2o.trade.model.domain.RefundRecord;
import java.util.List;
/**
* @Description 退款记录表服务类
*/
public interface RefundRecordService extends IService<RefundRecord> {
/**
* 根据退款单号查询退款记录
*
* @param refundNo 退款单号
* @return 退款记录数据
*/
RefundRecord findByRefundNo(Long refundNo);
/**
* 根据交易单号查询退款单
*
* @param tradingOrderNo 交易单号
* @return 退款列表
*/
List<RefundRecord> findByTradingOrderNo(Long tradingOrderNo);
/**
* 根据订单号查询退款列表
*
* @param productAppId 业务系统标识
* @param productOrderNo 订单号
* @return 退款列表
*/
List<RefundRecord> findListByProductOrderNo(String productAppId,Long productOrderNo);
/***
* 按状态查询退款单按照时间正序排序
*
* @param refundStatus 状态
* @param count 查询数量默认查询10条
* @return 退款单数据列表
*/
List<RefundRecord> findListByRefundStatus(RefundStatusEnum refundStatus, Integer count);
}

View File

@ -0,0 +1,60 @@
package com.jzo2o.trade.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.model.dto.TradingDTO;
import java.util.List;
/**
* @Description交易订单表 服务类
*/
public interface TradingService extends IService<Trading> {
/***
* 按交易单号查询交易单
*
* @param tradingOrderNo 交易单号
* @return 交易单数据
*/
Trading findTradByTradingOrderNo(Long tradingOrderNo);
/***
* 按交易状态查询交易单按照时间正序排序
* @param tradingState 状态
* @param count 查询数量默认查询10条
* @return 交易单数据列表
*/
List<Trading> findListByTradingState(TradingStateEnum tradingState, Integer count);
/**
* 根据订单id和支付方式查询付款中的交易单
* @param productAppId 业务系统标识
* @param productOrderNo 订单号
* @param tradingChannel 支付渠道代码
* @return 交易单
*/
Trading queryDuringTrading(String productAppId,Long productOrderNo, String tradingChannel);
/**
* 根据订单id查询交易单
*
* @param productAppId 业务系统标识
* @param productOrderNo 订单号
* @return 交易单
*/
List<Trading> queryByProductOrder(String productAppId,Long productOrderNo);
/**
* 根据订单id查询已付款的交易单
* @param productAppId 业务系统标识
* @param productOrderNo 订单id
* @return 交易单
*/
Trading findFinishedTrading(String productAppId,Long productOrderNo);
}

View File

@ -0,0 +1,286 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.constant.Constants;
import com.jzo2o.trade.constant.TradingCacheConstant;
import com.jzo2o.trade.enums.RefundStatusEnum;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.BasicPayHandler;
import com.jzo2o.trade.handler.BeforePayHandler;
import com.jzo2o.trade.handler.HandlerFactory;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.model.dto.RefundRecordDTO;
import com.jzo2o.trade.model.dto.TradingDTO;
import com.jzo2o.trade.service.BasicPayService;
import com.jzo2o.trade.service.RefundRecordService;
import com.jzo2o.trade.service.TradingService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 支付的基础功能
*/
@Slf4j
@Service
public class BasicPayServiceImpl implements BasicPayService {
@Resource
private BeforePayHandler beforePayHandler;
@Resource
private RedissonClient redissonClient;
@Resource
private TradingService tradingService;
@Resource
private RefundRecordService refundRecordService;
@Resource
private IdentifierGenerator identifierGenerator;
@Override
public TradingDTO queryTradingResult(Long tradingOrderNo) throws CommonException {
//通过单号查询交易单数据
Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
if(ObjectUtil.isNull(trading)){
return null;
}
//如果已付款或已取消直接返回
if(StrUtil.equalsAny(trading.getTradingState().getValue(),TradingStateEnum.YJS.getValue(),TradingStateEnum.QXDD.getValue())){
return BeanUtil.toBean(trading,TradingDTO.class);
}
//查询前置处理检测交易单参数
this.beforePayHandler.checkQueryTrading(trading);
//支付状态
TradingStateEnum tradingState = trading.getTradingState();
//如果支付成功或支付取消就直接返回
if (ObjectUtil.equal(tradingState, TradingStateEnum.YJS) || ObjectUtil.equal(tradingState, TradingStateEnum.QXDD)) {
return BeanUtil.toBean(trading, TradingDTO.class);
}
String key = TradingCacheConstant.QUERY_PAY + tradingOrderNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//选取不同的支付渠道实现
BasicPayHandler handler = HandlerFactory.get(trading.getTradingChannel(), BasicPayHandler.class);
Boolean result = handler.queryTrading(trading);
if (result) {
//如果交易单已经完成需要将二维码数据删除节省数据库空间如果有需要可以再次生成
if (ObjectUtil.equal(trading.getTradingState(), TradingStateEnum.YJS) || ObjectUtil.equal(trading.getTradingState(), TradingStateEnum.QXDD)) {
trading.setQrCode("");
}
//更新数据
this.tradingService.saveOrUpdate(trading);
}
return BeanUtil.toBean(trading, TradingDTO.class);
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
} catch (CommonException e) {
throw e;
} catch (Exception e) {
log.error("查询交易单数据异常: trading = {}", trading, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
} finally {
lock.unlock();
}
}
// @Override
// @Transactional
// public ExecutionResultResDTO refundTradingByTradingOrderNo(Long tradingOrderNo, BigDecimal refundAmount) throws CommonException {
// //根据业务订单号查看交易单信息
// Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
// if(ObjectUtil.isEmpty(trading)){
// throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NOT_FOUND.getValue());
// }
// //只有已付款的交易单方可退款
// if(ObjectUtil.notEqual(TradingStateEnum.YJS,trading.getTradingState())){
// throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.REFUND_FAIL.getValue());
// }
//
// ExecutionResultResDTO executionResultResDTO = refundTrading(trading.getTradingOrderNo(), refundAmount);
// return executionResultResDTO;
// }
@Override
@Transactional
public RefundRecord refundTrading(Long tradingOrderNo, BigDecimal refundAmount) throws CommonException {
//通过单号查询交易单数据
Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
//入库前置检查
this.beforePayHandler.checkRefundTrading(trading,refundAmount);
String key = TradingCacheConstant.REFUND_PAY + trading.getTradingOrderNo();
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//对于退款中的记录需要同步退款状态
syncRefundResult(tradingOrderNo);
//查询退款记录
List<RefundRecord> refundRecordList = this.refundRecordService.findByTradingOrderNo(trading.getTradingOrderNo());
//取出退款成功或退款中的记录
List<RefundRecord> collect = refundRecordList.stream().filter(r -> StrUtil.equalsAny(r.getRefundStatus().getValue(),RefundStatusEnum.SENDING.getValue(),RefundStatusEnum.SUCCESS.getValue())).collect(Collectors.toList());
//当没有退款成功和退款中的记录时方可继续退款
if(ObjectUtil.isEmpty(collect)){
//设置退款金额
trading.setRefund(refundAmount);
RefundRecord refundRecord = new RefundRecord();
//退款单号
refundRecord.setRefundNo(Convert.toLong(this.identifierGenerator.nextId(refundRecord)));
refundRecord.setTradingOrderNo(trading.getTradingOrderNo());
refundRecord.setProductOrderNo(trading.getProductOrderNo());
refundRecord.setProductAppId(trading.getProductAppId());
refundRecord.setRefundAmount(refundAmount);
refundRecord.setEnterpriseId(trading.getEnterpriseId());
refundRecord.setTradingChannel(trading.getTradingChannel());
refundRecord.setTotal(trading.getTradingAmount());
//初始状态为退款中
refundRecord.setRefundStatus(RefundStatusEnum.APPLY_REFUND);
this.refundRecordService.save(refundRecord);
//设置交易单是退款订单
trading.setIsRefund(Constants.YES);
this.tradingService.saveOrUpdate(trading);
//请求第三方退款
//选取不同的支付渠道实现
BasicPayHandler handler = HandlerFactory.get(refundRecord.getTradingChannel(), BasicPayHandler.class);
Boolean result = handler.refundTrading(refundRecord);
if (result) {
//更新退款记录数据
this.refundRecordService.saveOrUpdate(refundRecord);
}
return refundRecord;
}
//取出第一条记录返回
RefundRecord first = CollectionUtil.getFirst(refundRecordList);
if(ObjectUtil.isNotNull(first)){
return first;
}
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
} catch (CommonException e) {
throw e;
} catch (Exception e) {
log.error("查询交易单数据异常:{}", ExceptionUtil.stacktraceToString(e));
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
} finally {
lock.unlock();
}
}
/***
* 对于退款中的记录需要同步退款状态
* @param tradingOrderNo 交易单号
*/
@Override
public void syncRefundResult(Long tradingOrderNo) throws CommonException{
//查询退款记录
List<RefundRecord> refundRecordList = this.refundRecordService.findByTradingOrderNo(tradingOrderNo);
//存在退款中记录
List<RefundRecord> collect = refundRecordList.stream().filter(r -> r.getRefundStatus().equals(RefundStatusEnum.SENDING)).collect(Collectors.toList());
if (ObjectUtil.isNotEmpty(collect)) {
collect.forEach(v->{
queryRefundTrading(v.getRefundNo());
});
}
}
@Override
public RefundRecordDTO queryRefundTrading(Long refundNo) throws CommonException {
//通过单号查询交易单数据
RefundRecord refundRecord = this.refundRecordService.findByRefundNo(refundNo);
//查询前置处理
this.beforePayHandler.checkQueryRefundTrading(refundRecord);
String key = TradingCacheConstant.REFUND_QUERY_PAY + refundNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//选取不同的支付渠道实现
BasicPayHandler handler = HandlerFactory.get(refundRecord.getTradingChannel(), BasicPayHandler.class);
Boolean result = handler.queryRefundTrading(refundRecord);
if (result) {
//更新数据
this.refundRecordService.saveOrUpdate(refundRecord);
}
return BeanUtil.toBean(refundRecord, RefundRecordDTO.class);
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.REFUND_FAIL.getValue());
} catch (CommonException e) {
throw e;
} catch (Exception e) {
log.error("查询退款交易单数据异常: refundRecord = {}", refundRecord, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.REFUND_FAIL.getValue());
} finally {
lock.unlock();
}
}
/***
* 关闭交易单
* @param tradingOrderNo 交易单号
* @return 是否成功
*/
@Override
public Boolean closeTrading(Long tradingOrderNo) throws CommonException {
//通过单号查询交易单数据
Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
if (ObjectUtil.isEmpty(trading)) {
return true;
}
//入库前置检查
this.beforePayHandler.checkCloseTrading(trading);
String key = TradingCacheConstant.CLOSE_PAY + trading.getTradingOrderNo();
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//选取不同的支付渠道实现
BasicPayHandler handler = HandlerFactory.get(trading.getTradingChannel(), BasicPayHandler.class);
Boolean result = handler.closeTrading(trading);
if (result) {
trading.setQrCode("");
this.tradingService.saveOrUpdate(trading);
}
return true;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
} catch (CommonException e) {
throw e;
} catch (Exception e) {
log.error("查询交易单数据异常:{}", ExceptionUtil.stacktraceToString(e));
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_QUERY_FAIL.getValue());
} finally {
lock.unlock();
}
}
}

View File

@ -0,0 +1,84 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.constant.Constants;
import com.jzo2o.trade.constant.TradingCacheConstant;
import com.jzo2o.trade.constant.TradingConstant;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.handler.BeforePayHandler;
import com.jzo2o.trade.handler.HandlerFactory;
import com.jzo2o.trade.handler.JsapiPayHandler;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.service.JsapiPayService;
import com.jzo2o.trade.service.TradingService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @author itcast
*/
@Slf4j
@Service
public class JsapiPayServiceImpl implements JsapiPayService {
@Resource
private RedissonClient redissonClient;
@Resource
private TradingService tradingService;
@Resource
private BeforePayHandler beforePayHandler;
@Override
public Trading createJsapiTrading(Trading tradingEntity) {
//获取付款中的记录
Trading trading = tradingService.queryDuringTrading(tradingEntity.getProductAppId(),tradingEntity.getProductOrderNo(), tradingEntity.getTradingChannel());
//付款中的记录直接返回
if (ObjectUtil.isNotNull(trading)){
return trading;
}
//交易前置处理检测交易单参数
beforePayHandler.checkCreateTrading(tradingEntity);
tradingEntity.setEnableFlag(Constants.YES);
tradingEntity.setTradingType(TradingConstant.TRADING_TYPE_FK);
//对交易订单加锁
Long productOrderNo = tradingEntity.getProductOrderNo();
String key = TradingCacheConstant.CREATE_PAY + productOrderNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//交易前置处理幂等性处理
// this.beforePayHandler.idempotentCreateTrading(tradingEntity);
//调用不同的支付渠道进行处理
JsapiPayHandler jsapiPayHandler = HandlerFactory.get(tradingEntity.getTradingChannel(), JsapiPayHandler.class);
jsapiPayHandler.createJsapiTrading(tradingEntity);
//新增或更新交易数据
boolean flag = this.tradingService.saveOrUpdate(tradingEntity);
if (!flag) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.SAVE_OR_UPDATE_FAIL.getValue());
}
return tradingEntity;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
} catch (CommonException e) {
throw e;
} catch (Exception e) {
log.error("Jsapi预创建异常: tradingDTO = {}", tradingEntity, e);
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
} finally {
lock.unlock();
}
}
}

View File

@ -0,0 +1,132 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.trade.constant.Constants;
import com.jzo2o.trade.constant.TradingCacheConstant;
import com.jzo2o.trade.constant.TradingConstant;
import com.jzo2o.trade.enums.TradingEnum;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.BasicPayHandler;
import com.jzo2o.trade.handler.BeforePayHandler;
import com.jzo2o.trade.handler.HandlerFactory;
import com.jzo2o.trade.handler.NativePayHandler;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.service.BasicPayService;
import com.jzo2o.trade.service.NativePayService;
import com.jzo2o.trade.service.QRCodeService;
import com.jzo2o.trade.service.TradingService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Native支付方式Face接口商户生成二维码用户扫描支付
*
* @author itcast
*/
@Service
@Slf4j
public class NativePayServiceImpl implements NativePayService {
@Resource
private RedissonClient redissonClient;
@Resource
private TradingService tradingService;
@Resource
private BeforePayHandler beforePayHandler;
@Resource
private QRCodeService qrCodeService;
@Resource
private BasicPayService basicPayService;
/**
* 切换支付渠道自动关单
* @param tradingChannel 要切换的目标支付渠道
* @param productAppId 业务系统标识
* @param productOrderNo 业务订单号
*/
private void changeChannelAndCloseTrading(String productAppId,Long productOrderNo,String tradingChannel){
//根据订单号查询交易单
List<Trading> yjsTradByProductOrderNo = tradingService.queryByProductOrder(productAppId,productOrderNo);
yjsTradByProductOrderNo.forEach(v->{
//与目标支付渠道不同的渠道且支付中的进行关单
if(ObjectUtil.notEqual(v.getTradingChannel(),tradingChannel) &&
v.getTradingState().equals(TradingStateEnum.FKZ)){
//关单
Boolean result = basicPayService.closeTrading(v.getTradingOrderNo());
log.info("业务系统:{},业务订单:{},切换交易订单:{}的支付渠道为:{},关闭其它支付渠道:{}",productAppId,productOrderNo,v.getTradingOrderNo(),tradingChannel,v.getTradingChannel());
}
});
}
@Override
public Trading createDownLineTrading(boolean changeChannel,Trading tradingEntity) {
//获取付款中的记录
Trading trading = tradingService.queryDuringTrading(tradingEntity.getProductAppId(),tradingEntity.getProductOrderNo(), tradingEntity.getTradingChannel());
//如果切换二维码需要查询其它支付渠道付款中的交易单进行退款操作
if(changeChannel){
changeChannelAndCloseTrading(tradingEntity.getProductAppId(),tradingEntity.getProductOrderNo(),tradingEntity.getTradingChannel());
}
//付款中的记录直接返回无需生成二维码
if (ObjectUtil.isNotNull(trading)){
return trading;
}
//交易前置处理检测交易单参数
beforePayHandler.checkCreateTrading(tradingEntity);
tradingEntity.setTradingType(TradingConstant.TRADING_TYPE_FK);
tradingEntity.setEnableFlag(Constants.YES);
//对交易订单加锁
Long productOrderNo = tradingEntity.getProductOrderNo();
String key = TradingCacheConstant.CREATE_PAY + productOrderNo;
RLock lock = redissonClient.getFairLock(key);
try {
//获取锁
if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
//交易前置处理幂等性处理
// this.beforePayHandler.idempotentCreateTrading(tradingEntity);
//&#x8C03;&#x7528;&#x4E0D;&#x540C;&#x7684;&#x652F;&#x4ED8;&#x6E20;&#x9053;&#x8FDB;&#x884C;&#x5904;&#x7406;
PayChannelEnum payChannel = PayChannelEnum.valueOf(tradingEntity.getTradingChannel());
NativePayHandler nativePayHandler = HandlerFactory.get(payChannel, NativePayHandler.class);
nativePayHandler.createDownLineTrading(tradingEntity);
//生成统一收款二维码
String placeOrderMsg = tradingEntity.getPlaceOrderMsg();
String qrCode = this.qrCodeService.generate(placeOrderMsg, payChannel);
tradingEntity.setQrCode(qrCode);
//指定交易状态为付款中
tradingEntity.setTradingState(TradingStateEnum.FKZ);
//新增交易数据
boolean flag = this.tradingService.save(tradingEntity);
if (!flag) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.SAVE_OR_UPDATE_FAIL.getValue());
}
return tradingEntity;
}
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
} catch (CommonException e) {
throw e;
} catch (Exception e) {
log.error("统一收单线下交易预创建异常:{}", ExceptionUtil.stacktraceToString(e));
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, TradingEnum.NATIVE_PAY_FAIL.getValue());
} finally {
lock.unlock();
}
}
}

View File

@ -0,0 +1,188 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.constants.MqConstants;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.rabbitmq.client.RabbitClient;
import com.jzo2o.trade.constant.TradingCacheConstant;
import com.jzo2o.trade.constant.TradingConstant;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.handler.alipay.AlipayConfig;
import com.jzo2o.trade.handler.wechat.WechatPayHttpClient;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.service.NotifyService;
import com.jzo2o.trade.service.TradingService;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 支付成功的通知处理
*
* @author itcast
*/
@Slf4j
@Service
public class NotifyServiceImpl implements NotifyService {
@Resource
private TradingService tradingService;
@Resource
private RedissonClient redissonClient;
// @Resource
// private MQFeign mqFeign;
@Resource
private RabbitClient rabbitClient;
@Override
public void wxPayNotify(NotificationRequest request, Long enterpriseId) throws CommonException {
// 查询配置
WechatPayHttpClient client = WechatPayHttpClient.get(enterpriseId);
JSONObject jsonData;
//验证签名确保请求来自微信
try {
//确保在管理器中存在自动更新的商户证书
client.createHttpClient();
CertificatesManager certificatesManager = CertificatesManager.getInstance();
Verifier verifier = certificatesManager.getVerifier(client.getMchId());
//验签和解析请求数据
NotificationHandler notificationHandler = new NotificationHandler(verifier, client.getApiV3Key().getBytes(StandardCharsets.UTF_8));
Notification notification = notificationHandler.parse(request);
//通知的类型支付成功通知的类型为TRANSACTION.SUCCESS只处理此类通知
if (!StrUtil.equals("TRANSACTION.SUCCESS", notification.getEventType())) {
return;
}
//获取解密后的数据
jsonData = JSONUtil.parseObj(notification.getDecryptData());
} catch (Exception e) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, "验签失败");
}
//交易状态
String tradeStateFromWX = jsonData.getStr("trade_state");
/**
交易状态枚举值
SUCCESS支付成功
REFUND转入退款
NOTPAY未支付
CLOSED已关闭
REVOKED已撤销付款码支付
USERPAYING用户支付中付款码支付
PAYERROR支付失败(其他原因如银行返回失败)
*/
String tradeStatus = TradingStateEnum.FKZ.getValue();
if (StrUtil.equalsAny(tradeStateFromWX, TradingConstant.WECHAT_TRADE_CLOSED, TradingConstant.WECHAT_TRADE_REVOKED)) {
tradeStatus = TradingStateEnum.QXDD.getValue();
//支付成功或转入退款的更新为已付款
} else if (StrUtil.equalsAny(tradeStateFromWX, TradingConstant.WECHAT_TRADE_SUCCESS, TradingConstant.WECHAT_TRADE_REFUND)) {
tradeStatus = TradingStateEnum.YJS.getValue();
} else if (StrUtil.equalsAny(tradeStateFromWX, TradingConstant.WECHAT_TRADE_PAYERROR)) {
tradeStatus = TradingStateEnum.FKSB.getValue();
}
//交易单号
Long tradingOrderNo = jsonData.getLong("out_trade_no");
log.info("微信支付通知tradingOrderNo = {}, data = {}", tradingOrderNo, jsonData);
//更新交易单
this.updateTrading(tradingOrderNo, jsonData.getStr("transaction_id"),tradeStatus, jsonData.getStr("trade_state_desc"), jsonData.toString());
}
private void updateTrading(Long tradingOrderNo, String transactionId,String tradeStatus, String resultMsg, String resultJson) {
try {
Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
trading.setTradingState(TradingStateEnum.valueOf(tradeStatus));
//清空二维码数据
trading.setQrCode("");
trading.setTransactionId(transactionId);
trading.setResultMsg(resultMsg);
trading.setResultJson(resultJson);
this.tradingService.saveOrUpdate(trading);
// 发消息通知其他系统
TradeStatusMsg tradeStatusMsg = TradeStatusMsg.builder()
.tradingOrderNo(trading.getTradingOrderNo())
.productOrderNo(trading.getProductOrderNo())
.productAppId(trading.getProductAppId())
.transactionId(trading.getTransactionId())
.tradingChannel(trading.getTradingChannel())
.statusCode(TradingStateEnum.YJS.getCode())
.statusName(TradingStateEnum.YJS.name())
.info(trading.getMemo())//备注信息
.build();
String msg = JSONUtil.toJsonStr(Collections.singletonList(tradeStatusMsg));
rabbitClient.sendMsg(MqConstants.Exchanges.TRADE, MqConstants.RoutingKeys.TRADE_UPDATE_STATUS, msg);
return;
} catch (Exception e) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, "处理业务失败");
}
}
@Override
public void aliPayNotify(HttpServletRequest request, Long enterpriseId) throws CommonException {
//获取参数
Map<String, String[]> parameterMap = request.getParameterMap();
Map<String, String> param = new HashMap<>();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
param.put(entry.getKey(), StrUtil.join(",", entry.getValue()));
}
String tradeStatusFromAli = param.get("trade_status");
String tradeStatus = TradingStateEnum.FKZ.getValue();
if (StrUtil.equals(TradingConstant.ALI_TRADE_CLOSED, tradeStatusFromAli)) {
//支付取消TRADE_CLOSED未付款交易超时关闭或支付完成后全额退款
tradeStatus = TradingStateEnum.QXDD.getValue();
} else if (StrUtil.equalsAny(tradeStatusFromAli, TradingConstant.ALI_TRADE_SUCCESS, TradingConstant.ALI_TRADE_FINISHED)) {
// TRADE_SUCCESS交易支付成功
// TRADE_FINISHED交易结束不可退款
tradeStatus = TradingStateEnum.YJS.getValue();
}
//查询配置
Config config = AlipayConfig.getConfig(enterpriseId);
Factory.setOptions(config);
try {
Boolean result = Factory
.Payment
.Common().verifyNotify(param);
if (!result) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, "验签失败");
}
} catch (Exception e) {
throw new CommonException(ErrorInfo.Code.TRADE_FAILED, "验签失败");
}
//获取交易单号
Long tradingOrderNo = Convert.toLong(param.get("out_trade_no"));
String transactionId = param.get("trade_no");
//更新交易单
this.updateTrading(tradingOrderNo, transactionId, tradeStatus,"", JSONUtil.toJsonStr(param));
}
}

View File

@ -0,0 +1,78 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.trade.constant.Constants;
import com.jzo2o.trade.mapper.PayChannelMapper;
import com.jzo2o.trade.model.domain.PayChannel;
import com.jzo2o.trade.model.dto.PayChannelDTO;
import com.jzo2o.trade.service.PayChannelService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* @author itcast
* @Description 服务实现类
*/
@Service
public class PayChannelServiceImpl extends ServiceImpl<PayChannelMapper, PayChannel> implements PayChannelService {
@Override
public Page<PayChannel> findPayChannelPage(PayChannelDTO payChannelDTO, int pageNum, int pageSize) {
Page<PayChannel> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<PayChannel> queryWrapper = new LambdaQueryWrapper<>();
//设置条件
queryWrapper.eq(StrUtil.isNotEmpty(payChannelDTO.getChannelLabel()), PayChannel::getChannelLabel, payChannelDTO.getChannelLabel());
queryWrapper.likeRight(StrUtil.isNotEmpty(payChannelDTO.getChannelName()), PayChannel::getChannelName, payChannelDTO.getChannelName());
queryWrapper.eq(StrUtil.isNotEmpty(payChannelDTO.getEnableFlag()), PayChannel::getEnableFlag, payChannelDTO.getEnableFlag());
//设置排序
queryWrapper.orderByAsc(PayChannel::getCreateTime);
return super.page(page, queryWrapper);
}
@Override
public PayChannel findByEnterpriseId(Long enterpriseId, String channelLabel) {
LambdaQueryWrapper<PayChannel> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(PayChannel::getEnterpriseId, enterpriseId)
.eq(PayChannel::getChannelLabel, channelLabel)
.eq(PayChannel::getEnableFlag, Constants.YES);
return super.getOne(queryWrapper);
}
@Override
public PayChannel createPayChannel(PayChannelDTO payChannelDTO) {
PayChannel payChannel = BeanUtil.toBean(payChannelDTO, PayChannel.class);
boolean flag = super.save(payChannel);
if (flag) {
return payChannel;
}
return null;
}
@Override
public Boolean updatePayChannel(PayChannelDTO payChannelDTO) {
PayChannel payChannel = BeanUtil.toBean(payChannelDTO, PayChannel.class);
return super.updateById(payChannel);
}
@Override
public Boolean deletePayChannel(String[] checkedIds) {
List<String> ids = Arrays.asList(checkedIds);
return super.removeByIds(ids);
}
@Override
public List<PayChannel> findPayChannelList(String channelLabel) {
LambdaQueryWrapper<PayChannel> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(PayChannel::getChannelLabel, channelLabel)
.eq(PayChannel::getEnableFlag, Constants.YES);
return list(queryWrapper);
}
}

View File

@ -0,0 +1,51 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.jzo2o.trade.config.QRCodeConfig;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.service.QRCodeService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author itcast
*/
@Service
public class QRCodeServiceImpl implements QRCodeService {
@Resource
private QRCodeConfig qrCodeConfig;
@Override
public String generate(String content, PayChannelEnum payChannel) {
QrConfig qrConfig = new QrConfig();
//设置边距
qrConfig.setMargin(this.qrCodeConfig.getMargin());
//二维码颜色
qrConfig.setForeColor(HexUtil.decodeColor(this.qrCodeConfig.getForeColor()));
//设置背景色
qrConfig.setBackColor(HexUtil.decodeColor(this.qrCodeConfig.getBackColor()));
//纠错级别
qrConfig.setErrorCorrection(ErrorCorrectionLevel.valueOf(this.qrCodeConfig.getErrorCorrectionLevel()));
//设置宽
qrConfig.setWidth(this.qrCodeConfig.getWidth());
//设置高
qrConfig.setHeight(this.qrCodeConfig.getHeight());
if (ObjectUtil.isNotEmpty(payChannel)) {
//设置logo
qrConfig.setImg(this.qrCodeConfig.getLogo(payChannel));
}
return QrCodeUtil.generateAsBase64(content, qrConfig, ImgUtil.IMAGE_TYPE_PNG);
}
@Override
public String generate(String content) {
return generate(content, null);
}
}

View File

@ -0,0 +1,54 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.trade.enums.RefundStatusEnum;
import com.jzo2o.trade.mapper.RefundRecordMapper;
import com.jzo2o.trade.model.domain.RefundRecord;
import com.jzo2o.trade.service.RefundRecordService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author itcast
* @Description 退款记录服务实现类
*/
@Service
public class RefundRecordServiceImpl extends ServiceImpl<RefundRecordMapper, RefundRecord> implements RefundRecordService {
@Override
public RefundRecord findByRefundNo(Long refundNo) {
LambdaQueryWrapper<RefundRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(RefundRecord::getRefundNo, refundNo);
return super.getOne(queryWrapper);
}
@Override
public List<RefundRecord> findByTradingOrderNo(Long tradingOrderNo) {
LambdaQueryWrapper<RefundRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(RefundRecord::getTradingOrderNo, tradingOrderNo);
queryWrapper.orderByDesc(RefundRecord::getCreateTime);
return super.list(queryWrapper);
}
@Override
public List<RefundRecord> findListByProductOrderNo(String productAppId,Long productOrderNo) {
LambdaQueryWrapper<RefundRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(RefundRecord::getProductOrderNo, productOrderNo)
.eq(RefundRecord::getProductAppId, productAppId);
queryWrapper.orderByDesc(RefundRecord::getCreateTime);
return super.list(queryWrapper);
}
@Override
public List<RefundRecord> findListByRefundStatus(RefundStatusEnum refundStatus, Integer count) {
count = NumberUtil.max(count, 10);
LambdaQueryWrapper<RefundRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(RefundRecord::getRefundStatus, refundStatus)
.orderByAsc(RefundRecord::getCreateTime)
.last("LIMIT " + count);
return list(queryWrapper);
}
}

View File

@ -0,0 +1,98 @@
package com.jzo2o.trade.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.trade.constant.Constants;
import com.jzo2o.trade.enums.TradingStateEnum;
import com.jzo2o.trade.mapper.TradingMapper;
import com.jzo2o.trade.model.domain.Trading;
import com.jzo2o.trade.model.dto.TradingDTO;
import com.jzo2o.trade.service.BasicPayService;
import com.jzo2o.trade.service.TradingService;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @author itcast
* @Description交易订单表 服务实现类
*/
@Service
public class TradingServiceImpl extends ServiceImpl<TradingMapper, Trading> implements TradingService {
@Resource
private BasicPayService basicPayService;
@Override
public Trading findTradByTradingOrderNo(Long tradingOrderNo) {
LambdaQueryWrapper<Trading> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Trading::getTradingOrderNo, tradingOrderNo);
Trading one = super.getOne(queryWrapper);
return one;
}
@Override
public List<Trading> findListByTradingState(TradingStateEnum tradingState, Integer count) {
count = NumberUtil.max(count, 10);
LambdaQueryWrapper<Trading> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Trading::getTradingState, tradingState)
.eq(Trading::getEnableFlag, Constants.YES)
.orderByAsc(Trading::getCreateTime)
.last("LIMIT " + count);
return list(queryWrapper);
}
/**
* 根据订单id和支付方式查询付款中的交易单
*
* @param productOrderNo 订单号
* @param tradingChannel 支付渠道代码
* @return 交易单
*/
@Override
public Trading queryDuringTrading(String productAppId,Long productOrderNo, String tradingChannel){
LambdaQueryWrapper<Trading> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Trading::getProductOrderNo, productOrderNo)
.eq(Trading::getProductAppId,productAppId)
.eq(Trading::getTradingState,TradingStateEnum.FKZ)
.eq(Trading::getTradingChannel, tradingChannel);
List<Trading> list = list(queryWrapper);
Trading trading = CollectionUtil.getFirst(list);
return trading;
}
/**
* 根据订单id查询交易单
*
* @param productOrderNo 订单号
* @return 交易单
*/
@Override
public List<Trading> queryByProductOrder(String productAppId,Long productOrderNo){
LambdaQueryWrapper<Trading> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Trading::getProductOrderNo, productOrderNo)
.eq(Trading::getProductAppId, productAppId);
return list(queryWrapper);
}
/**
* 根据订单id查询已付款的交易单
*
* @param productOrderNo 订单id
* @return 交易单
*/
@Override
public Trading findFinishedTrading(String productAppId,Long productOrderNo){
LambdaQueryWrapper<Trading> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Trading::getProductOrderNo, productOrderNo)
.eq(Trading::getProductAppId,productAppId)
.eq(Trading::getTradingState,TradingStateEnum.YJS);
List<Trading> list = list(queryWrapper);
Trading first = CollectionUtil.getFirst(list);
return first;
}
}

View File

@ -0,0 +1,17 @@
spring:
cloud:
nacos:
username: nacos
password: nacos
server-addr: 192.168.122.135:8848
config:
namespace: 75a593f5-33e6-4c65-b2a0-18c403d20f63
file-extension: yaml
discovery:
namespace: 75a593f5-33e6-4c65-b2a0-18c403d20f63
ip: ${ACCESS_IP:}
################# 日志配置 #################
logging:
level:
com.jzo2o: debug

View File

@ -0,0 +1,16 @@
spring:
cloud:
nacos:
username: ${NACOS_USERNAME}
password: ${NACOS_PASSWORD}
server-addr: ${NACOS_ADDR}
config:
namespace: ${NACOS_NAMESPACE}
file-extension: yaml
discovery:
namespace: ${NACOS_NAMESPACE}
logging:
level:
com.jzo2o: debug
server:
port: 8080

View File

@ -0,0 +1,16 @@
spring:
cloud:
nacos:
username: ${NACOS_USERNAME}
password: ${NACOS_PASSWORD}
server-addr: ${NACOS_ADDR}
config:
namespace: ${NACOS_NAMESPACE}
file-extension: yaml
discovery:
namespace: ${NACOS_NAMESPACE}
logging:
level:
com.jzo2o: debug
server:
port: 8080

View File

@ -0,0 +1,88 @@
################# 服务器配置 #################
server:
port: 11505
undertow:
accesslog:
enabled: true
pattern: "%t %a &quot;%r&quot; %s (%D ms)"
dir: /data/logs/undertow/${spring.application.name}/access-logs/
servlet:
context-path: /trade
################# spring公共配置 #################
spring:
mvc:
path-match:
matching-strategy: ant_path_matcher
format:
date: yyyy-MM-dd HH:mm:ss
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
profiles:
active: dev
application:
name: jzo2o-trade
main:
# 支持循环依赖注入
allow-circular-references: true
# bean名相同覆盖
allow-bean-definition-overriding: true
cloud:
nacos:
username: ${NACOS_USERNAME}
password: ${NACOS_PASSWORD}
server-addr: ${NACOS_ADDR}
discovery:
namespace: ${NACOS_NAMESPACE}
config:
namespace: ${NACOS_NAMESPACE}
file-extension: yaml
shared-configs: # 共享配置
- data-id: shared-redis-cluster.yaml # 共享redis配置
refresh: false
- data-id: shared-xxl-job.yaml # xxl-job配置
refresh: false
- data-id: shared-rabbitmq.yaml # rabbitmq配置
refresh: false
- data-id: shared-mysql.yaml # mysql配置
refresh: false
jzo2o:
job:
trading:
count: ${job.trading.count} #每次查询交易单的数量
refund:
count: ${job.refund.count} #每次查询退款单的数量
qrcode: #二维码生成参数
margin: ${qrcode.margin} #边距,二维码和背景之间的边距
fore-color: ${qrcode.fore-color} #二维码颜色,默认黑色
back-color: ${qrcode.back-color} #背景色,默认白色
#低级别的像素块更大,可以远距离识别,但是遮挡就会造成无法识别。高级别则相反,像素块小,允许遮挡一定范围,但是像素块更密集。
error-correction-level: ${qrcode.error-correction-level} #纠错级别可选参数L、M、Q、H默认M
width: ${qrcode.width} #宽
height: ${qrcode.height} #高
################# 项目独有配置 #################
mysql:
db-name: jzo2o-trade
mybatis:
mapper-locations: mapper/*.xml
type-aliases-package: com.jzo2o.trade.mapper
swagger:
enable: true
package-path: com.jzo2o.trade.controller
title: 家政服务-支付服务接口文档
description: 该微服务完记录交易、对接支付宝微信支付平台。
contact-name: 传智教育·研究院
contact-url: http://www.itcast.cn/
contact-email: yjy@itcast.cn
version: v1.0
################# 日志配置 #################
logging:
level:
com.jzo2o: debug
org.apache.http: info #es请求日志
feign:
enable: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB