This commit is contained in:
韩鹏辉
2024-05-23 15:23:08 +08:00
parent 49fe8fe877
commit d969550aa3
2477 changed files with 340990 additions and 0 deletions

152
jeepay-payment/pom.xml Normal file
View File

@@ -0,0 +1,152 @@
<?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> <!-- POM模型版本 -->
<!-- 组织名, 类似于包名 -->
<artifactId>jeepay-9216-payment</artifactId> <!-- 项目名称 -->
<packaging>jar</packaging> <!-- 项目的最终打包类型/发布形式, 可选[jar, war, pom, maven-plugin]等 -->
<version>${isys.version}</version> <!-- 项目当前版本号 -->
<description>Jeepay计全支付系统 [统一支付网关]</description> <!-- 项目描述 -->
<url>https://www.jeequan.com</url>
<parent>
<groupId>com.jeequan</groupId>
<artifactId>jeepay</artifactId>
<version>Final</version>
</parent>
<!-- 项目属性 -->
<properties>
<projectRootDir>${basedir}/../</projectRootDir>
</properties>
<!-- 项目依赖声明 -->
<dependencies>
<!-- 显式 依赖[ service ]包, 会自动传递依赖[ core ]包。 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-db</artifactId>
</dependency>
<!-- 依赖[ rpc-thirdparty ]包, 会自动传递依赖[ core , service ]包。 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-3rd</artifactId>
</dependency>
<!-- 依赖[ oss ]包 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-oss</artifactId>
</dependency>
<!-- 依赖[ mq ]包 -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-components-mq</artifactId>
</dependency>
<!-- 依赖 sping-boot-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion> <!-- 删除spring boot默认json映射器 Jackson 引入fastJSON -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- hibernate.validator插件 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- 添加redis支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 生成二维码工具包 zxing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<!-- 引入 jeepay-sdk-java -->
<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-sdk-java</artifactId>
</dependency>
</dependencies>
<!-- 作为可执行jar -->
<build>
<finalName>${project.artifactId}</finalName>
<!-- resources资源配置项 -->
<resources>
<!-- 通用资源文件 -->
<resource><directory>src/main/resources</directory><includes><include>**/*.*</include></includes></resource>
<!-- 放置通用配置yml文件 开发时仅配置一套参数即可。 实际生产环境下应在每个项目下 与jar同级目录下新建application.yml覆写对应参数。 -->
<resource>
<directory>../conf/devCommons</directory>
<includes><include>**/*.yml</include></includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
<outputDirectory>${session.executionRootDirectory}/target/</outputDirectory>
</configuration> <!-- 包含本地jar文件 -->
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,18 @@
package com.jeequan.jeepay.pay.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 开放接口权限配置注解
* @author deng
* @since 2024-04-08
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenLabel {
String value() default "";
}

View File

@@ -0,0 +1,126 @@
package com.jeequan.jeepay.pay.aop;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.beans.RequestKitBean;
import com.jeequan.jeepay.core.exception.BizException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/***
* 方法级日志切面组件
*
* @author terrfly
* @since 2023/9/7 8:29
*/
@Component
@Aspect
public class MethodLogAop {
private static final Logger logger = LoggerFactory.getLogger(MethodLogAop.class);
@Autowired private RequestKitBean requestKitBean;
/**
* 切点
*/
@Pointcut("@annotation(com.jeequan.jeepay.core.aop.MethodLog)")
public void methodCachePointcut() { }
/**
* 切面
* @param point
* @return
* @throws Throwable
*/
@Around("methodCachePointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
//处理切面任务 发生异常将向外抛出 不记录日志
Object result = point.proceed();
try {
// 基础日志信息
this.setBaseLogInfo(point, result);
} catch (Exception e) {
logger.error("methodLogError", e);
}
return result;
}
/**
* @author: pangxiaoyu
* @date: 2021/6/7 14:04
* @describe: 记录异常操作请求信息
*/
@AfterThrowing(pointcut = "methodCachePointcut()", throwing="e")
public void doException(JoinPoint joinPoint, Throwable e) throws Exception{
// 避免记录日志异常!
try {
// 基础日志信息
this.setBaseLogInfo(joinPoint, ( e instanceof BizException ? ((BizException)e).getApiRes().toJSONString(): e.getMessage() ) );
} catch (Exception exception) {
logger.error("methodLogError", exception);
}
}
/**
* 获取方法中的中文备注
* @param joinPoint
* @return
* @throws Exception
*/
public static String getAnnotationRemark(JoinPoint joinPoint) throws Exception {
Signature sig = joinPoint.getSignature();
Method m = joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName(), ((MethodSignature) sig).getParameterTypes());
MethodLog methodCache = m.getAnnotation(MethodLog.class);
if (methodCache != null) {
return methodCache.remark();
}
return "";
}
/**
* @author: pangxiaoyu
* @date: 2021/6/7 14:12
* @describe: 日志基本信息 公共方法
*/
private void setBaseLogInfo(JoinPoint joinPoint, Object resObject) throws Exception {
// 使用point.getArgs()可获取request仅限于spring MVC参数包含request改为通过contextHolder获取。
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 日志名称
String pointRemark = getAnnotationRemark(joinPoint);
// 请求路径
String requestURL = request.getRequestURL().toString();
// 请求IP
String clientIp = requestKitBean.getClientIp();
logger.info("[APILOG|{}], url={}, ip={} \r\n RQ:::{}\r\n RS:::{}", pointRemark, requestURL, clientIp,
requestKitBean.getReqParamJSON(), resObject);
}
}

View File

@@ -0,0 +1,188 @@
package com.jeequan.jeepay.pay.aop;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.converter.MchInfoConverter;
import com.jeequan.jeepay.core.beans.RequestKitBean;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.openapi.AccessReq;
import com.jeequan.jeepay.core.model.openapi.AccessResp;
import com.jeequan.jeepay.core.utils.Base64;
import com.jeequan.jeepay.db.entity.MchAppEntity;
import com.jeequan.jeepay.db.entity.PackageOrder;
import com.jeequan.jeepay.db.entity.ProductInfo;
import com.jeequan.jeepay.pay.anno.OpenLabel;
import com.jeequan.jeepay.service.impl.MchAppService;
import com.jeequan.jeepay.service.impl.MchConfigService;
import com.jeequan.jeepay.service.impl.ProductAppService;
import com.jeequan.jeepay.service.impl.ProductInfoService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.List;
/***
* 方法级日志切面组件
*
* @author terrfly
* @since 2023/9/7 8:29
*/
@Slf4j
@Component
@Aspect
public class OpenLabelAop {
@Autowired private RequestKitBean requestKitBean;
@Autowired private MchAppService mchAppService;
@Autowired private MchConfigService mchConfigService;
@Autowired private MchInfoConverter mchInfoConverter;
@Autowired private ProductAppService productAppService;
@Autowired private ProductInfoService productInfoService;
/**
* 切点
*/
@Pointcut("@annotation(com.jeequan.jeepay.pay.anno.OpenLabel)")
public void methodCachePointcut() { }
/**
* 切面
* @param point
* @return
* @throws Throwable
*/
@Around("methodCachePointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
JSONObject reqParamJSON = requestKitBean.getReqParamJSON();
AccessReq accessReq = reqParamJSON.toJavaObject(AccessReq.class);
String appId = accessReq.getAppId();
if (ObjUtil.isEmpty(appId)) {
return AccessResp.fail(accessReq, "非法请求, 缺少应用[appId]参数");
}
MchAppEntity mchApp = mchAppService.getById(appId);
if (mchApp == null) {
return AccessResp.fail(accessReq, "应用[appId]参数非法");
}
// 先校验签名
try {
checkSign(accessReq, mchApp);
} catch (Exception e) {
return AccessResp.fail(accessReq, e.getMessage());
}
// 权限的Key值
String permissionKey = getAnnotationValue(point);
accessReq.setAppInfo(mchInfoConverter.toModel(mchApp));
if (!ObjUtil.isEmpty(permissionKey)) {
//校验应用的产品权限 TODO 方案的暂时没处理
//判断指定产品和指定应用是否开通
ProductInfo productInfo = productInfoService.getOne(ProductInfo.gw().eq(ProductInfo::getApiCode, permissionKey));
if (productInfo == null){
return AccessResp.forbidden("未获取到商品信息!");
}
List<PackageOrder> packageOrderList = productAppService.list(PackageOrder.gw().eq(PackageOrder::getAppId, mchApp.getAppId())
.eq(PackageOrder::getProductId, productInfo.getProductId())// TODO: 如果兼容方案的话加上or语句 packageId
.eq(PackageOrder::getMchNo,mchApp.getMchNo())
.eq(PackageOrder::getState,3));
if (packageOrderList.isEmpty()){
return AccessResp.forbidden("当前应用未开通对应的产品功能, 请前往商家端后台开通对应的产品");
}
boolean hasApiEnt = mchConfigService.queryMchAppHasApiEnt(mchApp.getMchNo(), mchApp.getAppId(), permissionKey);
if (!hasApiEnt) {
return AccessResp.forbidden("应用暂未开通接口权限");
}
}
try {
Object proceed = point.proceed();
if (proceed instanceof AccessResp) {
return proceed;
}
return AccessResp.success(accessReq, proceed);
} catch (Exception e) {
log.warn("请求异常", e);
return AccessResp.fail(accessReq, e.getMessage());
}
}
private void checkSign(AccessReq accessReq, MchAppEntity mchApp) {
String sign = accessReq.getSign();
String signType = accessReq.getSignType();
if (ObjUtil.isEmpty(sign)) {
throw new BizException("缺少签名[sign]");
}
if (ObjUtil.isEmpty(signType)) {
throw new BizException("缺少签名类型[signType]");
}
String appSecret = mchApp.getAppSecret();
if ("MD5".equals(signType)) {
String signContent = accessReq.signContent() + "&appSecret=" + appSecret;
log.info("验签串: {}", signContent);
String signVerify = SecureUtil.md5(signContent);
Assert.isTrue(signVerify.equals(sign), "验签失败");
} else if ("RSA2".equals(signType)) {
String pubKey = mchApp.getAppRsa2PublicKey();
Sign signUtil = SecureUtil.sign(SignAlgorithm.SHA256withRSA, null, pubKey);
String signContent = accessReq.signContent();
byte[] signBytes = Base64.decodeBase64(accessReq.getSign().getBytes(StandardCharsets.UTF_8));
try {
boolean verify = signUtil.verify(signContent.getBytes(StandardCharsets.UTF_8), signBytes);
Assert.isTrue(verify, "验签失败");
} catch (Exception e) {
log.info("验签异常", e);
throw new BizException("验签异常");
}
} else {
throw new BizException("非法的签名类型[signType]");
}
}
/**
* 获取方法中的中文备注
* @param joinPoint 请求节点信息
*/
public static String getAnnotationValue(JoinPoint joinPoint) throws Exception {
Signature sig = joinPoint.getSignature();
Method m = joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName(), ((MethodSignature) sig).getParameterTypes());
OpenLabel methodCache = m.getAnnotation(OpenLabel.class);
if (methodCache != null) {
return methodCache.value();
}
return "";
}
}

View File

@@ -0,0 +1,61 @@
package com.jeequan.jeepay.pay.bootstrap;
import cn.hutool.core.date.DatePattern;
import cn.hutool.crypto.SmUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.SimpleDateFormatSerializer;
import com.jeequan.jeepay.pay.config.SystemYmlConfig;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Date;
/*
* 项目初始化操作
* 比如初始化配置文件, 读取基础数据, 资源初始化等。 避免在Main函数中写业务代码。
* CommandLineRunner / ApplicationRunner都可以达到要求 只是调用参数有所不同。
*
*
* @author terrfly
*
* @date 2021/6/8 17:17
*/
@Component
public class InitRunner implements CommandLineRunner {
@Autowired private SystemYmlConfig systemYmlConfig;
@Autowired private SysConfigService sysConfigService;
@Override
public void run(String... args) throws Exception {
// 配置是否使用缓存模式
SysConfigService.IS_USE_CACHE = systemYmlConfig.getCacheConfig();
// 初始化系统秘钥
SysConfigService.DB_ENCRYPT_SECRET = systemYmlConfig.getDbEncryptSecret();
SysConfigService.DB_ENCRYPT_SM4 = SmUtil.sm4(SysConfigService.DB_ENCRYPT_SECRET.getBytes());
SysConfigService.HTTP_MESSAGE_ENCRYPT_SECRET = systemYmlConfig.getHttpMessageEncryptSecret();
SysConfigService.HTTP_MESSAGE_ENCRYPT_SM4 = SmUtil.sm4(SysConfigService.HTTP_MESSAGE_ENCRYPT_SECRET.getBytes());
// 配置是否通信加密 和 密码修改
SysConfigService.HTTP_MSG_IS_ENCRYPT = sysConfigService.getDBSecurityConfig().httpMsgIsEncrypt();
SysConfigService.PWD_EXPIRED_MUST_RESET = sysConfigService.getDBSecurityConfig().passwordExpiredIsMustModify();
// 配置 平台通信秘钥
SysConfigService.PLATFORM_API_SECRET = sysConfigService.getDBSecurityConfig().getPlatformApiSecret();
//初始化处理fastjson格式
SerializeConfig serializeConfig = SerializeConfig.getGlobalInstance();
serializeConfig.put(Date.class, new SimpleDateFormatSerializer(DatePattern.NORM_DATETIME_PATTERN));
//解决json 序列化时候的 $ref问题
JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();
}
}

View File

@@ -0,0 +1,152 @@
package com.jeequan.jeepay.pay.bootstrap;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.jeequan.jeepay.core.task.XxlJobExecutorProp;
import com.jeequan.jeepay.pay.config.SystemYmlConfig;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.hibernate.validator.HibernateValidator;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Arrays;
/**
* @Author terrfly
* @Date 2019/11/7 15:19
* @Description spring-boot 主启动程序
**/
@SpringBootApplication
@EnableScheduling
@MapperScan("com.jeequan.jeepay.service.mapper") //Mybatis mapper接口路径
@ComponentScan(basePackages = "com.jeequan.jeepay.*") //由于MainApplication没有在项目根目录 需要配置basePackages属性使得成功扫描所有Spring组件
@Configuration
public class JeepayPayApplication {
@Autowired private SystemYmlConfig systemYmlConfig;
/** main启动函数 **/
public static void main(String[] args) {
//启动项目
SpringApplication.run(JeepayPayApplication.class, args);
}
/** fastJson 配置信息 **/
@Bean
public HttpMessageConverters fastJsonConfig(){
// 开启 FastJSON 安全模式!
ParserConfig.getGlobalInstance().setSafeMode(true);
//新建fast-json转换器
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
//fast-json 配置信息
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd HH:mm:ss");
converter.setFastJsonConfig(config);
//设置响应的 Content-Type
converter.setSupportedMediaTypes(Arrays.asList(new MediaType[]{MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8}));
return new HttpMessageConverters(converter);
}
/** Mybatis plus 分页插件 **/
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
@Bean
public LocalValidatorFactoryBean validatorFactoryBean(){
return new LocalValidatorFactoryBean();
}
/** 默认为 失败快速返回模式 **/
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
return validatorFactory.getValidator();
}
/** 允许跨域请求 **/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
if(systemYmlConfig.getAllowCors()){
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); //带上cookie信息
// config.addAllowedOrigin(CorsConfiguration.ALL); //允许跨域的域名, *表示允许任何域名使用
config.addAllowedOriginPattern(CorsConfiguration.ALL); //使用addAllowedOriginPattern 避免出现 When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
config.addAllowedHeader(CorsConfiguration.ALL); //允许任何请求头
config.addAllowedMethod(CorsConfiguration.ALL); //允许任何方法post、get等
source.registerCorsConfiguration("/**", config); // CORS 配置对所有接口都有效
}
return new CorsFilter(source);
}
/*** 注入 定时任务 执行器 **/
/** xxl-job执行器配置信息 **/
@ConfigurationProperties(prefix = "xxl-job.executor")
@Bean
public XxlJobExecutorProp xxlJobExecutorProp() { return new XxlJobExecutorProp(); }
@Autowired
private XxlJobExecutorProp xxlJobExecutorProp;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(xxlJobExecutorProp.getAdminAddress());
xxlJobSpringExecutor.setAppname(xxlJobExecutorProp.getAppname());
if(xxlJobExecutorProp.getPort() != null){
xxlJobSpringExecutor.setPort(xxlJobExecutorProp.getPort());
}
xxlJobSpringExecutor.setAccessToken(xxlJobExecutorProp.getAccessToken());
xxlJobSpringExecutor.setLogPath(xxlJobExecutorProp.getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(xxlJobExecutorProp.getLogretentiondays());
return xxlJobSpringExecutor;
}
}

View File

@@ -0,0 +1,70 @@
package com.jeequan.jeepay.pay.config;
import com.jeequan.jeepay.core.cache.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
/*
* RedisConfig
*
* @author terrfly
*
* @date 2021/6/8 17:25
*/
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private Integer port;
@Value("${spring.redis.timeout}")
private Integer timeout;
@Value("${spring.redis.database}")
private Integer defaultDatabase;
@Value("${spring.redis.password}")
private String password;
/** 作用:不同系统的前缀。 a.当连接不同的database时可以为空(物理隔离) b.当redis集群时因为必须同一个database所以需通过前缀区分不同系统的业务。 **/
@Value("${spring.redis.sys-prefix-key}")
private String sysPrefixKey;
/** 当前系统的redis缓存操作对象 (主对象) **/
@Primary
@Bean(name = "defaultStringRedisTemplate")
public StringRedisTemplate sysStringRedisTemplate() {
// 赋值前缀key
RedisUtil.SYS_PREFIX_KEY = sysPrefixKey;
StringRedisTemplate template = new StringRedisTemplate();
LettuceConnectionFactory jedisConnectionFactory = new LettuceConnectionFactory();
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
jedisConnectionFactory.setTimeout(timeout);
if (!StringUtils.isEmpty(password)) {
jedisConnectionFactory.setPassword(password);
}
if (defaultDatabase != 0) {
jedisConnectionFactory.setDatabase(defaultDatabase);
}
jedisConnectionFactory.afterPropertiesSet();
template.setConnectionFactory(jedisConnectionFactory);
return template;
}
}

View File

@@ -0,0 +1,37 @@
package com.jeequan.jeepay.pay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 系统Yml配置参数定义Bean
*
* @author terrfly
*
* @date 2021-04-27 15:50
*/
@Component
@ConfigurationProperties(prefix="isys")
@Data
public class SystemYmlConfig {
/** 是否允许跨域请求 [生产环境建议关闭, 若api与前端项目没有在同一个域名下时应开启此配置或在nginx统一配置允许跨域] **/
private Boolean allowCors;
/** DB SM4 加解密秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!] **/
private String dbEncryptSecret;
/** web传输加解密 秘钥 (必须16位) [每个系统配置必须相同,否则加解密不一致导致业务异常!] **/
private String httpMessageEncryptSecret;
/** 支付网关的公钥和私钥(系统级别!), 请妥善保存,用于回调商户的商户侧的验证, 首次设置好之后不可随意变更! **/
private String sysRSA2PrivateKey;
/**支付网关的公钥和私钥(系统级别!), 请妥善保存,用于回调商户的商户侧的验证, 首次设置好之后不可随意变更! **/
private String sysRSA2PublicKey;
/** 是否内存缓存配置信息: true表示开启如支付网关地址/商户应用配置/服务商配置等, 开启后需检查MQ的广播模式是否正常 false表示直接查询DB. **/
private Boolean cacheConfig;
}

View File

@@ -0,0 +1,28 @@
package com.jeequan.jeepay.pay.config;
import com.jeequan.jeepay.core.config.WebConfig;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/*
* webmvc配置
*
* @author terrfly
*
* @date 2021/6/8 17:12
*/
@Configuration
public class WebmvcConfig extends WebConfig {
@Bean
@Role(2)
public static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
}

View File

@@ -0,0 +1,133 @@
package com.jeequan.jeepay.pay.ctrl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.AbstractMchAppRQ;
import com.jeequan.jeepay.core.model.rqrs.AbstractRQ;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.JeepayRSA2Kit;
import com.jeequan.jeepay.pay.service.ValidateService;
import com.jeequan.jeepay.service.impl.MchConfigService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/*
* api 抽象接口, 公共函数
*
* @author terrfly
*
* @date 2021/6/8 17:28
*/
public abstract class ApiController extends AbstractCtrl {
@Autowired private ValidateService validateService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private MchConfigService mchConfigService;
/** 获取请求参数并转换为对象,通用验证 **/
protected <T extends AbstractRQ> T getRQ(Class<T> cls){
T bizRQ = getObject(cls);
// [1]. 验证通用字段规则
validateService.validate(bizRQ);
return bizRQ;
}
/** 获取请求参数并转换为对象,商户通用验证 **/
protected <T extends AbstractRQ> T getRQByWithMchSign(Class<T> cls){
//获取请求RQ, and 通用验证
T bizRQ = getRQ(cls);
AbstractMchAppRQ abstractMchAppRQ = (AbstractMchAppRQ)bizRQ;
//业务校验, 包括: 验签, 商户状态是否可用, 是否支持该支付方式下单等。
String mchNo = abstractMchAppRQ.getMchNo();
String appId = abstractMchAppRQ.getAppId();
String sign = bizRQ.getSign();
if(StringUtils.isAnyBlank(mchNo, appId, sign)){
throw new BizException("参数有误!");
}
// pas非空 验证通信秘钥
if(StringUtils.isNotEmpty(abstractMchAppRQ.getPas())){
if(!abstractMchAppRQ.getPas().equals(SysConfigService.PLATFORM_API_SECRET)){
throw new BizException("通信秘钥错误!");
}
}else{ // pas是空的说明商户自调用 检查是否有接口权限
if(!mchConfigService.queryMchHasApiEnt(mchNo, abstractMchAppRQ.apiName())){
throw new BizException("商户无此接口["+abstractMchAppRQ.apiName()+"]权限!");
}
}
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchNo, appId);
if(mchAppConfigContext == null){
throw new BizException("商户或商户应用不存在");
}
if(mchAppConfigContext.getMchInfo() == null || mchAppConfigContext.getMchInfo().getState() != CS.YES){
throw new BizException("商户信息不存在或商户状态不可用");
}
MchApp mchApp = mchAppConfigContext.getMchApp();
if(mchApp == null || mchApp.getState() != CS.YES){
throw new BizException("商户应用不存在或应用状态不可用");
}
if(!mchApp.getMchNo().equals(mchNo)){
throw new BizException("参数appId与商户号不匹配");
}
if(StringUtils.isEmpty(mchApp.getAppSignType())){
throw new BizException("商户未配置签名方式");
}
List<String> appSignType = JSONArray.parseArray(mchApp.getAppSignType(), String.class);
if(!appSignType.contains(abstractMchAppRQ.getSignType().toUpperCase())){
throw new BizException("商户不支持["+abstractMchAppRQ.getSignType()+"]签名方式");
}
// 转换为 JSON
JSONObject bizReqJSON = (JSONObject)JSONObject.toJSON(bizRQ);
bizReqJSON.remove("sign");
// 验签
if(MchApp.SIGN_TYPE_MD5.equalsIgnoreCase(abstractMchAppRQ.getSignType())){
String appSecret = mchApp.getAppSecret();
if(!sign.equalsIgnoreCase(JeepayKit.getSign(bizReqJSON, appSecret))){
throw new BizException("验签失败");
}
}else if(MchApp.SIGN_TYPE_RSA2.equalsIgnoreCase(abstractMchAppRQ.getSignType())){
String appRsa2PublicKey = mchApp.getAppRsa2PublicKey();
try {
if(!JeepayRSA2Kit.verify(bizReqJSON, appRsa2PublicKey, abstractMchAppRQ.getSign())){
throw new BizException("验签失败");
}
} catch (Exception e) {
throw new BizException("验签异常");
}
}else{
throw new BizException("不支持的签名方式");
}
return bizRQ;
}
}

View File

@@ -0,0 +1,46 @@
package com.jeequan.jeepay.pay.ctrl;
import cn.hutool.core.codec.Base64;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/*
* 通用处理
*
* @author jmdhappy
*
* @date 2021/12/2 17:26
*/
@Slf4j
@Controller
@RequestMapping("/api/common")
public class CommonController extends AbstractCtrl {
/**
* 跳转到支付页面(适合网关支付form表单输出)
* @param payData
* @return
*/
@RequestMapping(value = "/payForm/{payData}")
private String toPayForm(@PathVariable("payData") String payData){
request.setAttribute("payHtml", Base64.decodeStr(payData));
return "common/toPay";
}
/**
* 跳转到支付页面(适合微信H5跳转与referer一致)
* @param payData
* @return
*/
@RequestMapping(value = "/payUrl/{payData}")
private String toPayUrl(@PathVariable("payData") String payData) {
String payUrl = Base64.decodeStr(payData);
request.setAttribute("payHtml", "<script>window.location.href = '"+payUrl+"';</script>");
return "common/toPay";
}
}

View File

@@ -0,0 +1,80 @@
package com.jeequan.jeepay.pay.ctrl;
import cn.hutool.core.util.ObjUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.jeequan.jeepay.converter.MchInfoConverter;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.AccessMsgException;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.openapi.ParamException;
import com.jeequan.jeepay.core.model.openapi.AccessError;
import com.jeequan.jeepay.core.model.openapi.AccessReq;
import com.jeequan.jeepay.core.model.openapi.AccessResp;
import com.jeequan.jeepay.db.entity.MchAppEntity;
import com.jeequan.jeepay.pay.service.ValidateService;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.MchAppService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* api 抽象接口, 公共函数
* @author crystal
*/
@Slf4j
public abstract class OpenApiController extends AbstractCtrl {
@Autowired private ValidateService validateService;
@Autowired private MchAppService mchAppService;
@Autowired private MchInfoConverter mchInfoConverter;
/** 获取请求参数并转换为对象,通用验证 **/
protected AccessReq getRQ(){
AccessReq bizRQ = getObject(AccessReq.class);
// [1]. 验证通用字段规则
validateService.checkV1(bizRQ);
preCheck(bizRQ);
return bizRQ;
}
/** 获取请求参数并转换为对象,通用验证 **/
protected AccessResp getRS(AccessReq bizRQ, Object bizRet){
return AccessResp.success(bizRQ, bizRet);
}
/**
* 公共参数校验
* @param bizRQ
*/
protected void preCheck(AccessReq bizRQ){
log.info("【apiV2】接口请求参数:{}", JSON.toJSONString(bizRQ));
MchAppEntity appEntity = mchAppService.getById(bizRQ.getAppId());
if (!ObjUtil.isEmpty(bizRQ.getBizData())) {
try {
bizRQ.setBizDataJSON(JSON.parseObject(bizRQ.getBizData()));
} catch (JSONException e) {
log.info("业务参数格式异常, 业务参数: {}", bizRQ.getBizData());
throw new BizException("业务参数格式异常");
}
}
if(appEntity == null){
throw new ParamException("未知的应用[appId]参数");
}
bizRQ.setAppInfo(mchInfoConverter.toModel(appEntity));
bizRQ.setPlatPriKey(ApiResKit.getKey());
if(MchAppEntity.NORMAL.byteValue() != appEntity.getState()){
throw new AccessMsgException(AccessError.stateError("应用已被停用或已被关闭,不支持当前操作"));
}
List<String> appSignType = JSON.parseArray(appEntity.getAppSignType(), String.class);
if(!appSignType.contains(bizRQ.getSignType())){
throw new ParamException("非法的签名类型[signType]参数");
}
}
}

View File

@@ -0,0 +1,23 @@
package com.jeequan.jeepay.pay.ctrl;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/***
* 静态资源ctrl
* 建议使用Nginx进行跳转 若没有配置Nginx则使用该ctrl进行跳转到 对应的static静态页面。 比如cashier的vue history模式
*
* @author terrfly
*
* @date 2021/12/17 19:29
*/
@Controller
public class StaticController {
/** 聚合码收银台页面 **/
@RequestMapping(value = "/pages/**")
public String cashier(){
return "forward://cashier/index.html";
}
}

View File

@@ -0,0 +1,47 @@
package com.jeequan.jeepay.pay.ctrl.advert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.db.entity.SysAdvertConfig;
import com.jeequan.jeepay.service.impl.SysAdvertConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 广告
*
* @author xiaoyu
*
* @date 2023/2/21 16:13
*/
@RestController
@RequestMapping("/api/advert")
public class AdvertController extends AbstractCtrl {
@Autowired SysAdvertConfigService advertConfigService;
/** 支付后广告 **/
@GetMapping("")
public ApiRes afterDevice() {
Byte appPlace = getValByteDefault("appPlace", SysAdvertConfig.APP_PLACE_LITE);
// 查询平台全局广告
LambdaQueryWrapper<SysAdvertConfig> wrapper = SysAdvertConfig.gw();
wrapper.eq(SysAdvertConfig::getAdvertType, SysAdvertConfig.ADVERT_TYPE_AFTER);
wrapper.eq(SysAdvertConfig::getReleaseState, CS.YES);
wrapper.eq(appPlace != null, SysAdvertConfig::getAppPlace, appPlace);
wrapper.orderByDesc(SysAdvertConfig::getCreatedAt);
List<SysAdvertConfig> list = advertConfigService.list(wrapper);
if (list.size() > 0) {
return ApiRes.ok(list.get(0));
}
return ApiRes.ok();
}
}

View File

@@ -0,0 +1,142 @@
package com.jeequan.jeepay.pay.ctrl.applyment;
import com.jeequan.jeepay.components.mq.model.MchAuditThirdNotifyMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.converter.MchInfoConverter;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.paychannel.IIsvmchApplymentNotifyService;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchApplyment;
import com.jeequan.jeepay.service.impl.MchApplymentService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* 进件接口渠道侧的通知入口Controller
*
* @author terrfly
* @date 2022/1/8 15:26
*/
@Slf4j
@Controller
public class MchApplymentChannelNotifyController extends AbstractCtrl {
@Autowired
private MchApplymentService mchApplymentService;
@Autowired
private IMQSender mqSender;
@Autowired
private MchInfoConverter mchInfoConverter;
/**
* 异步回调入口
*
* @param request
* @param ifCode
* @param customApplyId 通道方返回的参数,一般来说,该值由我方自定义。若回调地址不可变更,该参数将不存在
* @return
*/
@ResponseBody
@RequestMapping(value = {"/api/mchApplyment/notify/{ifCode}", "/api/mchApplyment/notify/{ifCode}/{customApplyId}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "customApplyId", required = false) String customApplyId) {
String applyId = null;
String logPrefix = "进入[" + ifCode + "]进件回调urlApplyId[" + StringUtils.defaultIfEmpty(customApplyId, "") + "] ";
log.info("===== {} =====", logPrefix);
IIsvmchApplymentNotifyService notifyService = null;
try {
// 参数有误
if (StringUtils.isEmpty(ifCode)) {
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询支付接口是否存在
notifyService = SpringBeansUtil.getBean(ifCode + "IsvmchApplymentNotifyService", IIsvmchApplymentNotifyService.class);
// 解析申请单号 和 请求参数
MutablePair<String, Object> mutablePair = notifyService.parseParams(request, customApplyId);
if (mutablePair == null) { // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//解析到订单号
applyId = mutablePair.left;
log.info("{}, 解析数据为applyId:{}, params:{}", logPrefix, applyId, mutablePair.getRight());
//获取进件数据 和 订单数据
MchApplyment dbTbMchApplyment = mchApplymentService.getById(applyId);
// 订单不存在
if (dbTbMchApplyment == null) {
log.error("{}, 进件数据不存在. applyId={} ", logPrefix, applyId);
return ResponseEntity.ok("SUCCESS");
}
// 状态不是待审核, 先去掉看看效果
// if(dbTbMchApplyment.getState() != MchApplyment.STATE_AUDITING){
// log.error("{}, 进件状态不是审核中. applyId={}, state={}", logPrefix, applyId, dbTbMchApplyment.getState());
// return notifyService.retOk(mutablePair.getRight());
// }
//调起接口的回调判断
MutablePair<com.jeequan.jeepay.core.entity.MchApplyment, ResponseEntity> notifyResult = notifyService.doNotify(request, mutablePair.getRight(), mchInfoConverter.toModel(dbTbMchApplyment));
if (notifyResult.left == null) {
return notifyService.retOk(null);
}
MchApplyment result = mchInfoConverter.toDbEntity(notifyResult.left);
result.setApplyId(dbTbMchApplyment.getApplyId());
mchApplymentService.updateById(result);
// 自动配置商户应用参数,进件成功 && 状态发生变化 && 自动配置应用不为空
if ((result.getState() == MchApplyment.STATE_SUCCESS || result.getState() == MchApplyment.STATE_SUCCESS_NEED_SECOND_VERIFY) && !result.getState().equals(dbTbMchApplyment.getState())) {
mchApplymentService.onApplymentSuccess(result.getApplyId());
mqSender.send(MchAuditThirdNotifyMQ.build(result.getApplyId(), MchAuditThirdNotifyMQ.TYPE_AUDIT));
}
log.info("===== {}, 进件通知完成。 applyId={}, parseState = {} =====", logPrefix, applyId, result.getState());
return notifyService.retOk(mutablePair.getRight());
} catch (BizException e) {
log.error("{}, applyId={}, BizException", logPrefix, applyId, e);
if (notifyService != null) {
return notifyService.retOk(null);
}
} catch (ResponseException e) {
log.error("{}, applyId={}, ResponseException", logPrefix, applyId, e);
if (notifyService != null) {
return notifyService.retOk(null);
}
} catch (Exception e) {
log.error("{}, applyId={}, 系统异常", logPrefix, applyId, e);
if (notifyService != null) {
return notifyService.retOk(null);
}
}
return ResponseEntity.ok("SUCCESS");
}
}

View File

@@ -0,0 +1,262 @@
package com.jeequan.jeepay.pay.ctrl.aqf;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.params.alipay.AlipayConfig;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.TransferInterfaceConfigEntity;
import com.jeequan.jeepay.db.entity.TransferOrderEntity;
import com.jeequan.jeepay.db.entity.TransferSubjectEntity;
import com.jeequan.jeepay.service.impl.SysConfigService;
import com.jeequan.jeepay.service.impl.TransferInterfaceConfigService;
import com.jeequan.jeepay.service.impl.TransferOrderService;
import com.jeequan.jeepay.service.impl.TransferSubjectService;
import com.jeequan.jeepay.thirdparty.channel.alipay.AliAqfV2Service;
import com.jeequan.jeepay.thirdparty.util.ChannelCertConfigKitBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.Map;
@Slf4j
@RestController
public class AliAqfController extends AbstractCtrl {
@Autowired
protected TransferInterfaceConfigService transferInterfaceConfigService;
@Autowired
private TransferSubjectService transferSubjectService;
@Autowired
private AliAqfV2Service aliAqfV2Service;
@Autowired
private SysConfigService sysConfigService;
@Autowired
private TransferOrderService transferOrderService;
/**
* 解约回调方法
* @param request
* @throws AlipayApiException
*/
@RequestMapping("/api/anf/reqUnsign")
public String reqUnsign(HttpServletRequest request) {
// TODO: 2024/4/25 回调参数暂时先不处理!!
/* logger.info("进入了安全发的解约回调!!!");
JSONObject reqParamJSON = getReqParamJSON();
TransferSubjectEntity subjectEntity = transferSubjectService.getOne(TransferSubjectEntity.gw().eq(TransferSubjectEntity::getExternalAgreementNo, reqParamJSON.getString("external_agreement_no")));
try {
//验签
boolean resut = getcertAliPayRequest(reqParamJSON);
if (!resut){
logger.error("安全发回调验签失败:"+ reqParamJSON.toJSONString());
subjectEntity.setRemark("安全发解约回调验签失败");
}else {
logger.info("签约回调通知参数为:"+ reqParamJSON.toJSONString());
//已解约
subjectEntity.setSignState(3);
}
}catch (Exception e){
logger.error("安全发签约回调处理数据出现异常!!");
subjectEntity.setRemark("安全发签约回调处理数据出现异常");
}
logger.info("解约回调通知参数为:"+ reqParamJSON.toJSONString());
transferSubjectService.updateById(subjectEntity);*/
return "success";
}
/**
* 资金专项拨入,代发回调
* @param request
* @return
*/
@RequestMapping("/api/anf/orderChanged")
public ApiRes orderChanged(String request) {
JSONObject reqParamJSON = getReqParamJSON();
log.info("【】回调参数"+reqParamJSON);
String input = reqParamJSON.getString("input");
String[] requestSplit = input.split("&");
for (String requestSplitFor : requestSplit) {
String[] real = requestSplitFor.split("=");
if (real.length == 2){
try {
reqParamJSON.put(URLDecoder.decode(real[0], "UTF-8"),URLDecoder.decode(real[1], "UTF-8"));
} catch (UnsupportedEncodingException e) {
log.error("解析回调参数异常!!");
return ApiRes.customFail("解析回调参数异常!");
}
}else {
return ApiRes.customFail("参数格式不正确!");
}
}
log.info("解析后的回调参数"+reqParamJSON);
JSONObject bizContent = reqParamJSON.getJSONObject("biz_content");
TransferOrderEntity transferOrder = transferOrderService.getById(bizContent.getString("out_biz_no"));
switch (bizContent.getString("status")){
case "SUCCESS" :
transferOrder.setState(TransferOrderEntity.STATE_SUCCESS);
transferOrder.setSuccessTime(new Date());
if (bizContent.containsKey("order_id")){
transferOrder.setChannelOrderNo(bizContent.getString("order_id"));
}
if (bizContent.containsKey("pay_fund_order_id")){
transferOrder.setFlowNo(bizContent.getString("pay_fund_order_id"));
}
break;
case "WAIT_PAY" :
transferOrder.setState(TransferOrderEntity.STATE_ING);
break;
case "CLOSED" :
transferOrder.setState(TransferOrderEntity.STATE_CLOSED);
break;
case "FAIL" :
transferOrder.setState(TransferOrderEntity.STATE_FAIL);
break;
case "DEALING" :
transferOrder.setState(TransferOrderEntity.STATE_ING);
break;
default:
transferOrder.setState(TransferOrderEntity.STATE_ING);
break;
}
transferOrder.setChannelExtra(reqParamJSON.toJSONString());
transferOrderService.updateById(transferOrder);
return ApiRes.ok();
}
/**
* 代发到户回调 暂时无用!
* @param request
* @return
*/
@RequestMapping("/api/anf/accountOrderChanged")
public ApiRes accountOrderChanged(String request) {
logger.info("进入了代发到户回调!!!");
log.info("代发到户回调参数"+request);
JSONObject reqParamJSON = new JSONObject();
String[] requestSplit = request.split("&");
for (String requestSplitFor : requestSplit) {
String[] real = requestSplitFor.split("=");
if (real.length == 2){
try {
reqParamJSON.put(URLDecoder.decode(real[0], "UTF-8"),URLDecoder.decode(real[1], "UTF-8"));
} catch (UnsupportedEncodingException e) {
log.error("解析回调参数异常!!");
return ApiRes.customFail("解析回调参数异常!");
}
}
}
log.info("解析后的代发到户回调参数"+reqParamJSON);
TransferOrderEntity transferOrder = transferOrderService.getOne(TransferOrderEntity.gw().eq(TransferOrderEntity::getChannelOrderNo, reqParamJSON.getJSONObject("biz_content").getString("order_id")));
switch (reqParamJSON.getJSONObject("biz_content").getString("status")){
case "SUCCESS" :
transferOrder.setState((byte) 2);
transferOrder.setSuccessTime(new Date());
break;
case "WAIT_PAY" :
transferOrder.setState((byte) 1);
break;
case "CLOSED" :
transferOrder.setState((byte) 4);
break;
case "FAIL" :
transferOrder.setState((byte) 3);
break;
case "DEALING" :
transferOrder.setState((byte) 1);
break;
default:
transferOrder.setState((byte) 1);
break;
}
transferOrder.setChannelExtra(reqParamJSON.toJSONString());
transferOrderService.updateById(transferOrder);
return ApiRes.ok();
}
/**
* 代发到卡 暂时无用!
* @param request
* @return
*/
@RequestMapping("/api/anf/cardOrderChanged")
public ApiRes cardOrderChanged(String request) {
logger.info("进入了资金专项拨入回调!!!");
log.info("专项资金拨入回调参数"+request);
JSONObject reqParamJSON = new JSONObject();
String[] requestSplit = request.split("&");
for (String requestSplitFor : requestSplit) {
String[] real = requestSplitFor.split("=");
if (real.length == 2){
try {
reqParamJSON.put(URLDecoder.decode(real[0], "UTF-8"),URLDecoder.decode(real[1], "UTF-8"));
} catch (UnsupportedEncodingException e) {
log.error("解析回调参数异常!!");
return ApiRes.customFail("解析回调参数异常!");
}
}
}
log.info("解析后的专项资金拨入回调参数"+reqParamJSON);
TransferOrderEntity transferOrder = transferOrderService.getOne(TransferOrderEntity.gw().eq(TransferOrderEntity::getChannelOrderNo, reqParamJSON.getJSONObject("biz_content").getString("order_id")));
switch (reqParamJSON.getJSONObject("biz_content").getString("status")){
case "SUCCESS" :
transferOrder.setState((byte) 2);
transferOrder.setSuccessTime(new Date());
break;
case "WAIT_PAY" :
transferOrder.setState((byte) 1);
break;
case "CLOSED" :
transferOrder.setState((byte) 4);
break;
case "FAIL" :
transferOrder.setState((byte) 3);
break;
case "DEALING" :
transferOrder.setState((byte) 1);
break;
default:
transferOrder.setState((byte) 1);
break;
}
transferOrder.setChannelExtra(reqParamJSON.toJSONString());
transferOrderService.updateById(transferOrder);
return ApiRes.ok();
}
public boolean getCertAliPayRequest(JSONObject reqParamJSON ){
boolean resut= true;
try {
String externalAgreementNo = reqParamJSON.getString("external_agreement_no");
TransferSubjectEntity subjectServiceOne = transferSubjectService.getOne(TransferSubjectEntity.gw().eq(TransferSubjectEntity::getTransApplyId, externalAgreementNo));
TransferInterfaceConfigEntity transferInterfaceConfigEntity = transferInterfaceConfigService.list(TransferInterfaceConfigEntity.gw().eq(TransferInterfaceConfigEntity::getTransIfCode, "AQF")
.eq(TransferInterfaceConfigEntity::getInfoId, subjectServiceOne.getIsvNo())).stream().findFirst().orElse(null);
if (transferInterfaceConfigEntity != null){
ChannelCertConfigKitBean channelCertConfigKitBean = SpringBeansUtil.getBean(ChannelCertConfigKitBean.class);
JSONObject jsonTransIfParams = JSONObject.parseObject(transferInterfaceConfigEntity.getTransIfParams());
resut= AlipaySignature.rsaCertCheckV1(reqParamJSON.toJavaObject(Map.class), channelCertConfigKitBean.getCertFilePath(jsonTransIfParams.getString("alipayPublicKey")),
AlipayConfig.CHARSET, "RSA2");
}
}catch (Exception e){
resut=false;
}
return resut;
}
}

View File

@@ -0,0 +1,139 @@
package com.jeequan.jeepay.pay.ctrl.cashout;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.cashout.IChannelCashoutNoticeService;
import com.jeequan.jeepay.core.model.cashout.CashoutRetMsg;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.ChannelAccountCashoutRecord;
import com.jeequan.jeepay.service.impl.ChannelAccountCashoutRecordService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* 渠道提现通知解析实现
*
* @author zx
*
* @date 2021/6/8 17:26
*/
@Slf4j
@Controller
public class MchChannelCashoutNoticeController extends AbstractCtrl {
@Autowired private ChannelAccountCashoutRecordService cashoutRecordService;
@Autowired private IConfigContextQueryService configContextQueryService;
/** 提现回调入口 **/
@ResponseBody
@RequestMapping(value= {"/api/channel/cashout/notify/{ifCode}", "/api/channel/cashout/notify/{ifCode}/{rid}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "rid", required = false) String urlRid){
String rid = null;
String logPrefix = "进入[" +ifCode+ "]提现回调提现urlRid["+ StringUtils.defaultIfEmpty(urlRid, "") + "] ";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询通知接口是否存在
IChannelCashoutNoticeService cashoutNoticeService = SpringBeansUtil.getBean(ifCode + "ChannelCashoutNoticeService", IChannelCashoutNoticeService.class);
// 提现通知接口实现不存在
if(cashoutNoticeService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 解析提现单号 和 请求参数
MutablePair<String, Object> mutablePair = cashoutNoticeService.parseParams(request, urlRid);
if(mutablePair == null){ // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//解析到提现单号
rid = mutablePair.left;
log.info("{}, 解析数据为rid:{}, params:{}", logPrefix, rid, mutablePair.getRight());
//获取提现单号 和 提现单数据
ChannelAccountCashoutRecord cashoutRecord = cashoutRecordService.getById(rid);
// 提现单不存在
if(cashoutRecord == null){
log.error("{}, 提现单不存在. rid={} ", logPrefix, rid);
return ResponseEntity.badRequest().body("cashout record not exists");
}
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(cashoutRecord.getMchNo(), cashoutRecord.getAppId(),cashoutRecord.getMchExtNo());
//调起接口的回调判断
CashoutRetMsg cashoutRetMsg = cashoutNoticeService.doNotice(request, mutablePair.getRight(), cashoutRecord, mchAppConfigContext);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(cashoutRetMsg == null || cashoutRetMsg.getChannelState() == null || cashoutRetMsg.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, cashoutRetMsg);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
boolean updateOrderSuccess = true; //默认更新成功
// 提现单是 【提现中状态】
if(cashoutRecord.getState() == ChannelAccountCashoutRecord.CASHOUT_STATE_ING) {
// 渠道提现单号
cashoutRecord.setChannelRid(cashoutRetMsg.getChannelOrderId());
// 明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == cashoutRetMsg.getChannelState()) {
updateOrderSuccess = cashoutRecordService.updateIng2Success(rid, cashoutRetMsg.getChannelOrderId(), cashoutRetMsg.getBankName(), cashoutRetMsg.getBankAccount(), cashoutRetMsg.getBankAccountName());
//明确失败
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == cashoutRetMsg.getChannelState()) {
String failMsg = String.format("failInfo=[code=%s, msg=%s]", cashoutRetMsg.getChannelErrCode(), cashoutRetMsg.getChannelErrMsg());
updateOrderSuccess = cashoutRecordService.updateIng2Fail(rid, failMsg);
}
}
// 更新提现单 异常
if(!updateOrderSuccess){
log.error("{}, updateOrderSuccess = {} ",logPrefix, updateOrderSuccess);
return ResponseEntity.badRequest().body("update error");
}
log.info("===== {}, 提现单通知完成。 rid={}, parseState = {} =====", logPrefix, rid, cashoutRetMsg.getChannelState());
return cashoutRetMsg.getResponseEntity();
} catch (BizException e) {
log.error("{}, rid={}, BizException", logPrefix, rid, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, rid={}, ResponseException", logPrefix, rid, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, rid={}, 系统异常", logPrefix, rid, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}

View File

@@ -0,0 +1,244 @@
package com.jeequan.jeepay.pay.ctrl.channel;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConstants;
import com.alipay.api.internal.util.AlipaySignature;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IMchAutoAuthService;
import com.jeequan.jeepay.core.model.DBApplicationConfig;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.params.alipay.AlipayConfig;
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchAppEntity;
import com.jeequan.jeepay.db.entity.PayInterfaceConfig;
import com.jeequan.jeepay.service.impl.MchAppService;
import com.jeequan.jeepay.service.impl.PayInterfaceConfigService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 渠道侧自定义业务ctrl
*
* @author terrfly
*
* @date 2021/7/15 11:49
*/
@Slf4j
@Controller
@RequestMapping("/api/channelbiz/alipay")
public class AlipayBizController extends AbstractCtrl {
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private SysConfigService sysConfigService;
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
@Autowired private MchAppService mchAppService;
@Autowired private IMQSender mqSender;
/** 跳转到支付宝的授权页面 统一从pay项目获取到isv配置信息
* isvAndMchNo 格式: ISVNO_MCHAPPID
* example: https://pay.jeepay.cn/api/channelbiz/alipay/redirectAppToAppAuth/V1623998765_60cc41694ee0e6685f57eb1f
* **/
@RequestMapping("/redirectAppToAppAuth/{isvAndMchAppId}")
public void redirectAppToAppAuth(@PathVariable("isvAndMchAppId") String isvAndMchAppId) throws IOException {
String isvNo = isvAndMchAppId.split("_")[0];
AlipayIsvParams alipayIsvParams = (AlipayIsvParams) configContextQueryService.queryIsvParams(isvNo, CS.IF_CODE.ALIPAY);
alipayIsvParams.getSandbox();
String oauthUrl = AlipayConfig.PROD_APP_TO_APP_AUTH_URL;
if(alipayIsvParams.getSandbox() != null && alipayIsvParams.getSandbox() == CS.YES){
oauthUrl = AlipayConfig.SANDBOX_APP_TO_APP_AUTH_URL;
}
String redirectUrl = sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/api/channelbiz/alipay/appToAppAuthCallback";
response.sendRedirect(String.format(oauthUrl, alipayIsvParams.getAppId(), URLUtil.encodeAll(redirectUrl), isvAndMchAppId));
}
/** 支付宝授权回调地址 **/
@RequestMapping("/appToAppAuthCallback")
public String appToAppAuthCallback() {
String errMsg = null;
boolean isAlipaySysAuth = true; //是否 服务商登录支付宝后台系统发起的商户授权, 此时无法获取authCode和商户的信息。
try {
// isvAndMchAppId 格式: ISVNO_MCHAPPID, 如果isvAndMchNo为空说明是 支付宝后台的二维码授权之后的跳转链接。
String isvAndMchAppId = getValString("state");
String appAuthCode = getValString("app_auth_code"); // 支付宝授权code
if(StringUtils.isNotEmpty(isvAndMchAppId) && StringUtils.isNotEmpty(appAuthCode)){
isAlipaySysAuth = false;
String isvNo = isvAndMchAppId.split("_")[0];
String mchAppId = isvAndMchAppId.split("_")[1];
MchAppEntity mchAppEntity = mchAppService.getById(mchAppId);
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchAppEntity.getMchNo(), mchAppId);
IMchAutoAuthService mchAutoAuthService = SpringBeansUtil.getBean(CS.IF_CODE.ALIPAY + "MchAutoAuthService", IMchAutoAuthService.class);
JSONObject result = mchAutoAuthService.mchAuth(mchAppConfigContext, appAuthCode);
String appAuthToken = result.getString("appAuthToken");
JSONObject ifParams = new JSONObject();
ifParams.put("appAuthToken", appAuthToken); ifParams.put("refreshToken", result.getString("appRefreshToken")); ifParams.put("expireTimestamp", result.getString("expiresIn"));
PayInterfaceConfig dbRecord = payInterfaceConfigService.getByInfoIdAndIfCode(CS.SYS_ROLE_TYPE.MCH_APP, mchAppId, CS.IF_CODE.ALIPAY);
if(dbRecord != null){
PayInterfaceConfig updateRecord = new PayInterfaceConfig();
updateRecord.setId(dbRecord.getId()); updateRecord.setIfParams(ifParams.toJSONString());
payInterfaceConfigService.updateById(updateRecord);
}else{
dbRecord = new PayInterfaceConfig();
dbRecord.setInfoType(CS.SYS_ROLE_TYPE.MCH_APP);
dbRecord.setInfoId(mchAppId);
dbRecord.setIfCode(CS.IF_CODE.ALIPAY);
dbRecord.setIfParams(ifParams.toJSONString());
dbRecord.setState(CS.YES);
dbRecord.setCreatedBy("SYS");
dbRecord.setCreatedUid(0L);
payInterfaceConfigService.save(dbRecord);
}
}
} catch (Exception e) {
log.error("error", e);
errMsg = StringUtils.defaultIfBlank(e.getMessage(), "系统异常!");
}
request.setAttribute("errMsg", errMsg);
request.setAttribute("isAlipaySysAuth", isAlipaySysAuth);
// 静态CDN地址
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
request.setAttribute("staticCdnHost", dbApplicationConfig.getStaticCdnHost());
return "channel/alipay/isvsubMchAuth";
}
/**
* 支付宝公共方法 加密函数。
* **/
private static String encryptAndSign(String bizContent, String alipayPublicKey, String cusPrivateKey, String charset,
boolean isEncrypt, boolean isSign, String signType) throws AlipayApiException {
StringBuilder sb = new StringBuilder();
if (StringUtils.isEmpty(charset)) {
charset = AlipayConstants.CHARSET_GBK;
}
sb.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
if (isEncrypt) {// 加密
sb.append("<alipay>");
String encrypted = AlipaySignature.rsaEncrypt(bizContent, alipayPublicKey, charset);
sb.append("<response>" + encrypted + "</response>");
sb.append("<encryption_type>AES</encryption_type>");
if (isSign) {
String sign = AlipaySignature.rsaSign(encrypted, cusPrivateKey, charset, signType);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>");
sb.append(signType);
sb.append("</sign_type>");
}
sb.append("</alipay>");
} else if (isSign) {// 不加密,但需要签名
sb.append("<alipay>");
sb.append("<response>" + bizContent + "</response>");
String sign = AlipaySignature.rsaSign(bizContent, cusPrivateKey, charset, signType);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>");
sb.append(signType);
sb.append("</sign_type>");
sb.append("</alipay>");
} else {// 不加密,不加签
sb.append(bizContent);
}
return sb.toString();
}
/**
* 接收 支付宝 应用 配置中: 【应用网关地址 用于接收支付宝沙箱异步通知消息(例如 From蚂蚁消息等需要传入http(s)公网可访问的网页地址。选填,若不设置,则无法接收相应的异步通知消息。】
*
* **/
@RequestMapping("/appGatewayMsgReceive")
public ModelAndView alipayAppGatewayMsgReceive() {
JSONObject reqJSON = getReqParamJSON();
// 获取到报文信息, 然后 转发到对应的ctrl
log.error("支付宝应用网关接收消息参数:{}", reqJSON);
// // 分账交易通知
// if("alipay.trade.order.settle.notify123123".equals(reqJSON.getString("msg_method"))){
//
// // 直接转发到 分账通知的 URL去。
// ModelAndView mv = new ModelAndView();
// mv.setViewName("forward:/api/divisionRecordChannelNotify/" + CS.IF_CODE.ALIPAY);
// return mv;
// }
// 测试。
if(true){
AlipayIsvParams isvParams = (AlipayIsvParams)configContextQueryService.queryIsvParams("V1648865874", CS.IF_CODE.ALIPAY);
String publicKey = isvParams.getAlipayPublicKey();
String PRIVATE_KEY = isvParams.getPrivateKey();
//支付宝响应消息
String bizContent = "<success>true</success>";
try {
//对响应内容加签
String responseMsg = encryptAndSign(bizContent, publicKey, PRIVATE_KEY, "UTF-8", false, true, "RSA2");
log.error("响应支付宝:{}", responseMsg);
//http 内容应答
response.reset();
response.setContentType("text/xml;charset=GBK");
PrintWriter printWriter = response.getWriter();
printWriter.print(responseMsg);
response.flushBuffer();
return null;
} catch (Exception e) {
e.printStackTrace();
}
}
throw new BizException("无此事件["+ reqJSON.getString("msg_method") +"]处理器");
}
}

View File

@@ -0,0 +1,55 @@
package com.jeequan.jeepay.pay.ctrl.channel;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Enumeration;
/**
* 渠道侧自定义业务ctrl
* 盛付通
*
* @author terrfly
*
* @date 2022/10/21 14:29
*/
@Slf4j
@Controller
@RequestMapping("/api/channelbiz/shengpay")
public class ShengpayBizController extends AbstractCtrl {
/** 修改费率通知地址 **/
@RequestMapping("/modifyRateNotify")
public ResponseEntity modifyRateNotify() throws Exception {
String logPrefix = "接收盛付通费率修改通知";
try {
log.info("{} method: {}", logPrefix, request.getMethod());
log.info("{} QUERYSTRING: {}", logPrefix, request.getQueryString());
// 查询到所有的header
Enumeration<String> list = request.getHeaderNames();
while (list.hasMoreElements()){
String key = list.nextElement();
log.info("{} Header: {}={}", logPrefix, key, request.getHeader(key));
}
log.info("{} reqParamJSON: {}", logPrefix, getReqParamJSON());
} catch (Exception e) {
log.error("{}", logPrefix, e);
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity("SUCCESS", httpHeaders, HttpStatus.OK);
}
}

View File

@@ -0,0 +1,159 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.db.entity.MchStoreDevice;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiRes;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.SpiBaseResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.service.AlipaySpiRuyiCashierService;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.service.AlipaySpiRuyiPayOrderService;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.service.AlipaySpiRuyiRefundOrderService;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.util.AlipayNotifyUtil;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.service.impl.MchStoreDeviceService;
import com.jeequan.jeepay.thirdparty.util.ChannelCertConfigKitBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 支付宝From消息接收ctrl
*
* @author zx
*
* @date 2022-03-21 15:50
*/
@Slf4j
@RestController
@RequestMapping("/api/from/alipay/notify")
public class AlipayNotifyController extends AbstractPayOrderController {
@Autowired private AlipaySpiRuyiCashierService alipaySpiRuyiCashierService;
@Autowired private AlipaySpiRuyiPayOrderService alipaySpiRuyiPayOrderService;
@Autowired private AlipaySpiRuyiRefundOrderService alipaySpiRuyiRefundOrderService;
@Autowired private MchStoreDeviceService mchStoreDeviceService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private ChannelCertConfigKitBean channelCertConfigKitBean;
/** spi入口 **/
@PostMapping
public AlipaySpiRes entry() {
String logPrefix = "【支付宝异步通知消息】";
AlipayIsvParams alipayParams = null;
try {
JSONObject reqParamJSON = getReqParamJSON();
String method = getValStringDefault("method", AlipayNotifyUtil.NOTIFY_UNKNOWN);
log.info("{}请求参数:{}", logPrefix + AlipayNotifyUtil.notifyMap.get(method), reqParamJSON.toJSONString());
String terminalBindInfo = getValStringRequired("terminal_bind_info"); // 绑定设备传的自定义参数
UnifiedOrderRQ.DeviceInfo deviceInfo = JSONObject.parseObject(terminalBindInfo, UnifiedOrderRQ.DeviceInfo.class);
// 查询设备信息
MchStoreDevice mchStoreDevice = mchStoreDeviceService.getByUniqueKey(deviceInfo.getProvider(), Byte.valueOf(deviceInfo.getDeviceType()), deviceInfo.getDeviceNo());
if (mchStoreDevice == null || mchStoreDevice.getBindState() != CS.YES) {
throw new BizException("设备不存在");
}
// 验签&&获取服务商参数
alipayParams = this.alipaySpiCheckSign(reqParamJSON, mchStoreDevice.getMchNo(), mchStoreDevice.getAppId());
// 分发spi请求
SpiBaseResponse response;
switch (method) {
case AlipayNotifyUtil.NOTIFY_SP_OPERATION_RESULT:
response = spOperationResult(mchStoreDevice);
break;
case AlipayNotifyUtil.NOTIFY_SHOP_SAVE_PASSED:
response = shopSavePassed(mchStoreDevice);
break;
case AlipayNotifyUtil.NOTIFY_SHOP_SAVE_REJECTED:
response = shopSaveRejected(mchStoreDevice);
break;
default:
throw new BizException("支付宝异步通知消息未接入");
}
return AlipaySpiRes.okWithSign(logPrefix, response, alipayParams.getPrivateKey());
}catch (AlipayApiException e) {
log.error("{}异常:{}", logPrefix, e.getMessage(), e);
if (alipayParams == null) {
return AlipaySpiRes.fail(logPrefix, "BIZ_ERROR", StringUtils.defaultString(e.getMessage(), "服务商参数未配置"));
}
return AlipaySpiRes.failWithSign(logPrefix, alipayParams.getPrivateKey(), StringUtils.defaultString(e.getErrCode(), "SYSTEM_ERROR"),
StringUtils.defaultString(e.getErrMsg(), "系统繁忙"));
}catch (BizException e) {
if (alipayParams == null) {
return AlipaySpiRes.fail(logPrefix, "BIZ_ERROR", StringUtils.defaultString(e.getMessage(), "服务商参数未配置"));
}
log.error("{}异常:{}", logPrefix, e.getMessage(), e);
return AlipaySpiRes.failWithSign(logPrefix, alipayParams.getPrivateKey(), "BIZ_ERROR", StringUtils.defaultString(e.getMessage(), "系统繁忙"));
}catch (Exception e) {
log.error("{}异常:{}", logPrefix, e.getMessage(), e);
return AlipaySpiRes.failWithSign(logPrefix, alipayParams.getPrivateKey(), "SYSTEM_ERROR", "系统繁忙");
}
}
private SpiBaseResponse shopSaveRejected(MchStoreDevice mchStoreDevice) {
return null;
}
private SpiBaseResponse shopSavePassed(MchStoreDevice mchStoreDevice) {
return null;
}
private SpiBaseResponse spOperationResult(MchStoreDevice mchStoreDevice) {
return null;
}
/** spi请求参数验签 **/
public AlipayIsvParams alipaySpiCheckSign(JSONObject reqParamJSON, String mchNo, String appId) throws AlipayApiException {
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchNo, appId);
if(!mchAppConfigContext.isIsvsubMch()){
throw new AlipayApiException("BIZ_ERROR", "仅支持特约商户");
}
// 获取支付参数
AlipayIsvParams alipayParams = (AlipayIsvParams) configContextQueryService.queryIsvParams(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.ALIPAY);
if (alipayParams == null) {
throw new AlipayApiException("BIZ_ERROR", "服务商参数未配置");
}
boolean verifyResult;
if(alipayParams.getUseCert() != null && alipayParams.getUseCert() == CS.YES){ //证书方式
verifyResult = AlipaySignature.rsaCertCheckV1(reqParamJSON.toJavaObject(Map.class), getCertFilePath(alipayParams.getAlipayPublicCert()),
com.jeequan.jeepay.core.model.params.alipay.AlipayConfig.CHARSET, alipayParams.getSignType());
}else{
verifyResult = AlipaySignature.rsaCheckV1(reqParamJSON.toJavaObject(Map.class),
alipayParams.getAlipayPublicKey(), com.jeequan.jeepay.core.model.params.alipay.AlipayConfig.CHARSET, alipayParams.getSignType());
}
//验签失败
if(!verifyResult){
throw new AlipayApiException("BIZ_ERROR", "alipay spi请求验签失败");
}
return alipayParams;
}
/** 获取文件路径 **/
protected String getCertFilePath(String certFilePath) {
return channelCertConfigKitBean.getCertFilePath(certFilePath);
}
}

View File

@@ -0,0 +1,176 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.params.alipay.AlipayIsvParams;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.db.entity.MchStoreDevice;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiRes;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.SpiBaseResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.service.AlipaySpiRuyiCashierService;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.service.AlipaySpiRuyiPayOrderService;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.service.AlipaySpiRuyiRefundOrderService;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.util.AlipaySpiRuyiUtil;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.service.impl.MchStoreDeviceService;
import com.jeequan.jeepay.thirdparty.util.ChannelCertConfigKitBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 支付宝如意spi接口
*
* @author zx
*
* @date 2021-04-27 15:50
*/
@Slf4j
@RestController
@RequestMapping("/api/alipay/ruyi/spi")
public class AlipayRuyiSpiController extends AbstractPayOrderController {
@Autowired private AlipaySpiRuyiCashierService alipaySpiRuyiCashierService;
@Autowired private AlipaySpiRuyiPayOrderService alipaySpiRuyiPayOrderService;
@Autowired private AlipaySpiRuyiRefundOrderService alipaySpiRuyiRefundOrderService;
@Autowired private MchStoreDeviceService mchStoreDeviceService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private ChannelCertConfigKitBean channelCertConfigKitBean;
/** 支付宝如意 spi入口 **/
@PostMapping
public AlipaySpiRes ruyiSpiEntry() {
String logPrefix = "【支付宝如意Lite】";
AlipayIsvParams alipayParams = null;
try {
JSONObject reqParamJSON = getReqParamJSON();
String method = getValStringDefault("method", AlipaySpiRuyiUtil.SPI_RUYI_UNKNOWN);
log.info("{}请求参数:{}", logPrefix + AlipaySpiRuyiUtil.spiMap.get(method), reqParamJSON.toJSONString());
String terminalBindInfo = getValStringRequired("terminal_bind_info"); // 绑定设备传的自定义参数
UnifiedOrderRQ.DeviceInfo deviceInfo = JSON.parseObject(terminalBindInfo, UnifiedOrderRQ.DeviceInfo.class);
// 查询设备信息
MchStoreDevice mchStoreDevice = mchStoreDeviceService.getByUniqueKey(deviceInfo.getProvider(), Byte.valueOf(deviceInfo.getDeviceType()), deviceInfo.getDeviceNo());
if (mchStoreDevice == null || mchStoreDevice.getBindState() != CS.YES) {
throw new BizException("设备不存在");
}
// 验签&&获取服务商参数
alipayParams = this.alipaySpiCheckSign(reqParamJSON, mchStoreDevice.getMchNo(), mchStoreDevice.getAppId());
// 分发spi请求
SpiBaseResponse response;
switch (method) {
case AlipaySpiRuyiUtil.SPI_RUYI_PAY:
response = alipaySpiRuyiPayOrderService.pay(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_PAY_QUERY:
response = alipaySpiRuyiPayOrderService.payQuery(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_BILL_DETAIL_QUERY:
response = alipaySpiRuyiPayOrderService.billdetailQuery(mchStoreDevice,
getValIntegerDefault("page_num", 1), getValIntegerDefault("page_size", 1), getValString("date"));
break;
case AlipaySpiRuyiUtil.SPI_RUYI_BILL_STATISTICS_QUERY:
response = alipaySpiRuyiPayOrderService.billstatisticsQuery(mchStoreDevice, getValString("date"));
break;
case AlipaySpiRuyiUtil.SPI_RUYI_BILL_QUERY:
response = alipaySpiRuyiRefundOrderService.billQuery(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_REFUND_AUTHCODE_APPLY:
response = alipaySpiRuyiRefundOrderService.refundAuthCodeApply(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_REFUND:
response = alipaySpiRuyiRefundOrderService.refund(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_REFUND_QUERY:
response = alipaySpiRuyiRefundOrderService.refundQuery(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_CASHIER_SIGN:
response = alipaySpiRuyiCashierService.cashierSign(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_CASHIER_UNSIGN:
response = alipaySpiRuyiCashierService.cashierUnsign(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_CASHIER_BATCH_QUERY:
response = alipaySpiRuyiCashierService.batchQuery(mchStoreDevice);
break;
case AlipaySpiRuyiUtil.SPI_RUYI_CASHIER_QUERY:
response = alipaySpiRuyiCashierService.cashierQuery(mchStoreDevice);
break;
default:
throw new BizException("如意Lite SPI请求未接入");
}
return AlipaySpiRes.okWithSign(logPrefix, response, alipayParams.getPrivateKey());
}catch (AlipayApiException e) {
log.error("{}异常:{}", logPrefix, e.getMessage(), e);
if (alipayParams == null) {
return AlipaySpiRes.fail(logPrefix, "BIZ_ERROR", StringUtils.defaultString(e.getMessage(), "服务商参数未配置"));
}
return AlipaySpiRes.failWithSign(logPrefix, alipayParams.getPrivateKey(), StringUtils.defaultString(e.getErrCode(), "SYSTEM_ERROR"),
StringUtils.defaultString(e.getErrMsg(), "系统繁忙"));
}catch (BizException e) {
if (alipayParams == null) {
return AlipaySpiRes.fail(logPrefix, "BIZ_ERROR", StringUtils.defaultString(e.getMessage(), "服务商参数未配置"));
}
log.error("{}异常:{}", logPrefix, e.getMessage(), e);
return AlipaySpiRes.failWithSign(logPrefix, alipayParams.getPrivateKey(), "BIZ_ERROR", StringUtils.defaultString(e.getMessage(), "系统繁忙"));
}catch (Exception e) {
log.error("{}异常:{}", logPrefix, e.getMessage(), e);
return AlipaySpiRes.failWithSign(logPrefix, alipayParams.getPrivateKey(), "SYSTEM_ERROR", "系统繁忙");
}
}
/** spi请求参数验签 **/
public AlipayIsvParams alipaySpiCheckSign(JSONObject reqParamJSON, String mchNo, String appId) throws AlipayApiException {
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchNo, appId);
if(!mchAppConfigContext.isIsvsubMch()){
throw new AlipayApiException("BIZ_ERROR", "仅支持特约商户");
}
// 获取支付参数
AlipayIsvParams alipayParams = (AlipayIsvParams) configContextQueryService.queryIsvParams(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.ALIPAY);
if (alipayParams == null) {
throw new AlipayApiException("BIZ_ERROR", "服务商参数未配置");
}
boolean verifyResult;
if(alipayParams.getUseCert() != null && alipayParams.getUseCert() == CS.YES){ //证书方式
verifyResult = AlipaySignature.rsaCertCheckV1(reqParamJSON.toJavaObject(Map.class), getCertFilePath(alipayParams.getAlipayPublicCert()),
com.jeequan.jeepay.core.model.params.alipay.AlipayConfig.CHARSET, alipayParams.getSignType());
}else{
verifyResult = AlipaySignature.rsaCheckV1(reqParamJSON.toJavaObject(Map.class),
alipayParams.getAlipayPublicKey(), com.jeequan.jeepay.core.model.params.alipay.AlipayConfig.CHARSET, alipayParams.getSignType());
}
//验签失败
if(!verifyResult){
throw new AlipayApiException("BIZ_ERROR", "alipay spi请求验签失败");
}
return alipayParams;
}
/** 获取文件路径 **/
protected String getCertFilePath(String certFilePath) {
return channelCertConfigKitBean.getCertFilePath(certFilePath);
}
}

View File

@@ -0,0 +1,100 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.request;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
/*
* 支付宝如意lite 支付宝请求服务商参数对象
*
* @author zx
*
* @date 2021/6/8 17:33
*/
@Data
public class AlipaySpiPayOrderRequest {
/** 商户订单号 **/
@JSONField(name = "merchant_order_no")
private String mchOrderNo;
/** 支付金额, 单位:元 **/
@JSONField(name = "total_amount")
private String total_amount;
/** 支付金额, 单位由total_amount计算而来 **/
private Long amount;
/** 货币代码 **/
@JSONField(name = "shop_id")
private String shopId;
/** 客户端IP地址 **/
private String clientIp;
/** 商品标题 **/
@NotBlank(message="商品标题不能为空")
private String subject;
/** 商品描述信息 **/
@NotBlank(message="商品描述信息不能为空")
private String body;
/** 商户的门店ID **/
private String storeId;
/** 设备信息 **/
private String deviceInfo;
/** 商户的码牌ID **/
private Long qrcId;
/** 异步通知地址 **/
private String notifyUrl;
/** 跳转通知地址 **/
private String returnUrl;
/** 卖家备注 **/
private String sellerRemark;
/** 买家备注 **/
private String buyerRemark;
/** 订单失效时间, 单位:秒 **/
private Integer expiredTime;
/** 终端编号(商户自行录入的 trmNo 字段 **/
private String mchTrmNo;
/** 特定渠道发起额外参数 **/
private String channelExtra;
/** 商户扩展参数 **/
private String extParam;
/** 分账模式: 0-该笔订单不允许分账, 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额) **/
@Range(min = 0, max = 2, message = "分账模式设置值有误")
private Byte divisionMode;
/** 渠道特殊业务数据 **/
private String channelBizData;
@Data
public static class DeviceInfo {
/** 设备类型qr_code-码牌scan_pos-扫码POSauto_pos-智能POSother-其他 **/
private String deviceType;
/** 设备号 **/
private String deviceNo;
/** 设备厂商 智能POS厂商即为 ifCode扫码POS仅商户系统调用已通过cahnnelBizData传值 **/
private String provider;
}
}

View File

@@ -0,0 +1,91 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/*
* 支付宝SPI 账单流水返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiBilldetailQueryResponse extends SpiBaseResponse {
/** 账单总数量**/
@JSONField(name = "total_size")
private String totalSize;
/** 商家实收总金额(单位:元),精确到小数点后两位 **/
@JSONField(name = "order_list")
private List<Order> orderList;
@Data
public static class Order {
/** PAY|REFUND : 已支付 |已退款类型**/
@JSONField(name = "order_type")
private String orderType;
/** 商户外部订单号**/
@JSONField(name = "merchant_order_no")
private String merchantOrderNo;
/** ISV服务商内部订单号**/
@JSONField(name = "isv_order_no")
private String isvOrderNo;
/** 支付通道订单号**/
@JSONField(name = "channel_order_no")
private String channelOrderNo;
/** 商户外部退款单号**/
@JSONField(name = "merchant_refund_no")
private String merchantRefundNo;
/** ISV服务商内部退款订单号**/
@JSONField(name = "isv_refund_no")
private String isvRefundNo;
/** 支付通道退款单号**/
@JSONField(name = "channel_refund_no")
private String channelRefundNo;
/** 支付类型001支付宝002微信007银联二维码 100自动识别类型等识别类型等**/
@JSONField(name = "channel_type")
private String channelType;
/** 支付订单金额单位元精确到小数点后2位 (REFUND |PAY状态时必填**/
@JSONField(name = "total_amount")
private String totalAmount;
/** 实收金额单位元精确到小数点后2位 (PAY状态必填**/
@JSONField(name = "receipt_amount")
private String receiptAmount;
/** 买家付款金额单位元精确到小数点后2位PAY状态必填**/
@JSONField(name = "buyer_pay_amount")
private String buyerPayAmount;
/** 商家退款金额单位元精确到小数点后2位REFUND 状态必填)**/
@JSONField(name = "refund_amount")
private String refundAmount;
/** Pay时代表支付完成时间refund时代表退款时间。格式yyyy-MM-dd HH:mm:ss需填写UTC时间**/
@JSONField(name = "order_time")
private String orderTime;
/** 收银终端设备序列号**/
@JSONField(name = "terminal_id")
private String terminalId;
/** 交易概述**/
@JSONField(name = "subject")
private String subject;
}
}

View File

@@ -0,0 +1,42 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 退款返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiBillstatisticsQueryResponse extends SpiBaseResponse {
/** 格式yyyy-MM-dd 00:00:00 , 账单日期 **/
@JSONField(name = "statistic_time")
private String statisticTime;
/** 商家实收总金额(单位:元),精确到小数点后两位 **/
@JSONField(name = "receipt_amount")
private String receiptAmount;
/** 商家实收总笔数(单位:笔) **/
@JSONField(name = "receipt_count")
private String receiptCount;
/** 商家退款总金额(单位:元),精确到小数点后两位 **/
@JSONField(name = "refund_amount")
private String refundAmount;
/** 商家退款总笔数(单位:笔) **/
@JSONField(name = "refund_count")
private String refundCount;
/** 商家收入结余(总实收-总退款,单位:元),精确到小数点后两位 **/
@JSONField(name = "total_income")
private String totalIncome;
}

View File

@@ -0,0 +1,39 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/*
* 支付宝SPI 收银员签到接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiCashierBatchQueryResponse extends SpiBaseResponse {
/** 签到失败原因 **/
@JSONField(name = "cashier_list")
private List<Cashier> cashierList;
@Data
public static class Cashier {
/** 签到失败原因 **/
@JSONField(name = "cashier_id")
private String cashierId;
/** 签到失败原因 **/
@JSONField(name = "cashier_account")
private String cashierAccount;
/** 签到失败原因 **/
@JSONField(name = "cashier_name")
private String cashierName;
}
}

View File

@@ -0,0 +1,26 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 查询收银员状态接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiCashierQueryResponse extends SpiBaseResponse {
/** 签到状态1成功, 0失败 **/
@JSONField(name = "status")
private String status;
/** 签到失败原因 **/
@JSONField(name = "cashier_id")
private String cashierId;
}

View File

@@ -0,0 +1,26 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 收银员签到接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiCashierSignResponse extends SpiBaseResponse {
/** 签到状态1成功, 0失败 **/
@JSONField(name = "status")
private String status;
/** 签到失败原因 **/
@JSONField(name = "fail_reason")
private String failReason;
}

View File

@@ -0,0 +1,26 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 收银员签退接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiCashierUnSignResponse extends SpiBaseResponse {
/** 签到状态1成功, 0失败 **/
@JSONField(name = "status")
private String status;
/** 签到失败原因 **/
@JSONField(name = "fail_reason")
private String failReason;
}

View File

@@ -0,0 +1,70 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 查单接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiPayOrderQueryResponse extends SpiBaseResponse {
/** 商家外部订单号 **/
@JSONField(name = "merchant_order_no")
private String merchantOrderNo;
/** ISV 订单号 **/
@JSONField(name = "isv_order_no")
private String isvOrderNo;
/** 微信、支付宝等第三方支付交易号 **/
@JSONField(name = "channel_order_no")
private String channelOrderNo;
/** 支付通道类型001支付宝002微信 100自动识别类型等 **/
@JSONField(name = "channel_type")
private String channelType;
/** 收银终端设备序列号sn **/
@JSONField(name = "terminal_id")
private String terminalId;
/** 订单状态 **/
@JSONField(name = "order_state")
private String orderState;
/** 支付成功时间 **/
@JSONField(name = "pay_time")
private String payTime;
/** 订单金额 **/
@JSONField(name = "total_amount")
private String totalAmount;
/** 实付金额 **/
@JSONField(name = "receipt_amount")
private String receiptAmount;
/** 用户支付金额 **/
@JSONField(name = "buyer_pay_amount")
private String buyerPayAmount;
/** 支付宝用户ID **/
@JSONField(name = "buyer_user_id")
private String buyerUserId;
/** 支付宝登录账户 **/
@JSONField(name = "buyer_account_name")
private String buyerAccountName;
/** 支付请求参数原样返回 **/
@JSONField(name = "attach_params")
private String attachParams;
}

View File

@@ -0,0 +1,66 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiPayOrderResponse extends SpiBaseResponse {
/** 商家外部订单号 **/
@JSONField(name = "merchant_order_no")
private String merchantOrderNo;
/** ISV 订单号 **/
@JSONField(name = "isv_order_no")
private String isvOrderNo;
/** 微信、支付宝等第三方支付交易号 **/
@JSONField(name = "channel_order_no")
private String channelOrderNo;
/** 001 **/
@JSONField(name = "channel_type")
private String channelType;
/** 订单状态 **/
@JSONField(name = "order_state")
private String orderState;
/** 支付成功时间 **/
@JSONField(name = "pay_time")
private String payTime;
/** 订单金额 **/
@JSONField(name = "total_amount")
private String totalAmount;
/** 实付金额 **/
@JSONField(name = "receipt_amount")
private String receiptAmount;
/** 用户支付金额 **/
@JSONField(name = "buyer_pay_amount")
private String buyerPayAmount;
/** 支付宝用户ID **/
@JSONField(name = "buyer_user_id")
private String buyerUserId;
/** 支付宝登录账户 **/
@JSONField(name = "buyer_account_name")
private String buyerAccountName;
/** 支付请求参数原样返回 **/
@JSONField(name = "attach_params")
private String attachParams;
}

View File

@@ -0,0 +1,26 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 收银员签到接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiRefundAuthCodeApplyResponse extends SpiBaseResponse {
/** 申请授权码是否成功 true/false **/
@JSONField(name = "grant_result")
private boolean grantResult;
/** 授权方式1.邮件EMAIL 2.短信: SMS 3.电话TEL 4.其他: OTHER **/
@JSONField(name = "grant_path")
private String grantPath;
}

View File

@@ -0,0 +1,55 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/*
* 支付宝SPI 退款返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AlipaySpiRefundOrderResponse extends SpiBaseResponse {
/** 商户系统内唯一订单号对isv为外部订单号 **/
@JSONField(name = "merchant_order_no")
private String merchantOrderNo;
/** ISV 订单号 **/
@JSONField(name = "isv_order_no")
private String isvOrderNo;
/** 商户请求退款单号 **/
@JSONField(name = "merchant_refund_no")
private String merchantRefundNo;
/** ISV平台系统内退款单号 **/
@JSONField(name = "isv_refund_no")
private String isvRefundNo;
/** 支付通道退款单号 **/
@JSONField(name = "channel_refund_no")
private String channelRefundNo;
/**
* 退款单状态。
* PROCESSING申请提交中未知申请结果机具端继续轮训
* ACCEPT申请成功未明确是否处理成功
* SUCCESS申请成功且明确退款处理成功。
**/
@JSONField(name = "refund_state")
private String orderState;
/** 退款成功时间 **/
@JSONField(name = "finish_time")
private String finishTime;
/** 退款金额 **/
@JSONField(name = "refund_amount")
private String refundAmount;
}

View File

@@ -0,0 +1,63 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/*
* 支付宝SPI 接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class AlipaySpiRes {
/** response **/
private SpiBaseResponse response;
/** 签名值 **/
private String sign;
/** 业务处理成功 **/
public static AlipaySpiRes okWithSign(String logPrefix, SpiBaseResponse response, String privateKey) throws AlipayApiException {
SpiBaseResponse.ok(response);
AlipaySpiRes alipaySpiRes = new AlipaySpiRes(response, AlipaySignature.rsaSign(JSON.toJSONString(response), privateKey, "UTF-8", "RSA2"));
log.info("{}处理成功,响应:{}", logPrefix, JSON.toJSONString(alipaySpiRes));
return alipaySpiRes;
}
/** 明确失败,需要重新扫码支付 **/
public static AlipaySpiRes failWithSign(String logPrefix, String privateKey, String errCode, String errMsg) {
try {
AlipaySpiRes alipaySpiRes = new AlipaySpiRes();
alipaySpiRes.setResponse(SpiBaseResponse.fail(errCode, errMsg));
alipaySpiRes.setSign(AlipaySignature.rsaSign(JSON.toJSONString(alipaySpiRes.getResponse()), privateKey, "UTF-8", "RSA2"));
log.info("{}处理失败,响应:{}", logPrefix, JSON.toJSONString(alipaySpiRes));
return alipaySpiRes;
} catch (AlipayApiException e) {
log.error("{}响应签名异常", logPrefix, e);
return fail(logPrefix, errCode, errMsg);
}
}
/** 明确失败,需要重新扫码支付 **/
public static AlipaySpiRes fail(String logPrefix, String errCode, String errMsg) {
AlipaySpiRes alipaySpiRes = new AlipaySpiRes();
alipaySpiRes.setResponse(SpiBaseResponse.fail(errCode, errMsg));
log.info("{}处理失败,响应:{}", logPrefix, JSON.toJSONString(alipaySpiRes));
return alipaySpiRes;
}
}

View File

@@ -0,0 +1,55 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/*
* 支付宝SPI 接口返回对象
*
* @author zx
*
* @date 2023/3/7 10:35
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SpiBaseResponse {
/** 业务响应码 **/
@JSONField(name = "code")
private String code;
/** 业务响应信息 **/
@JSONField(name = "msg")
private String msg;
/** 业务响应码 **/
@JSONField(name = "sub_code")
private String subCode;
/** 业务响应信息 **/
@JSONField(name = "sub_msg")
private String subMsg;
/** ISV 自定义错误 **/
@JSONField(name = "error_code")
private String errorCode;
/** ISV 自定义错误描述 **/
@JSONField(name = "error_desc")
private String errorDesc;
/** 业务处理成功 **/
public static void ok(SpiBaseResponse response){
response.setCode("10000");
response.setMsg("Success");
}
/** 业务处理失败 **/
public static SpiBaseResponse fail(String errCode, String errMsg){
return new SpiBaseResponse("40004", "Business Failed", errCode, errMsg, errCode, errMsg);
}
}

View File

@@ -0,0 +1,68 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.service;
import com.jeequan.jeepay.db.entity.MchStoreDevice;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiCashierBatchQueryResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiCashierQueryResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiCashierSignResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiCashierUnSignResponse;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 支付宝如意spi接口收银员操作
*
* @author zx
*
* @date 2021-04-27 15:50
*/
@Slf4j
@Service
public class AlipaySpiRuyiCashierService extends AbstractPayOrderController {
/** 收银员签到 **/
public AlipaySpiCashierSignResponse cashierSign(MchStoreDevice mchStoreDevice) {
String cashierId = getValString("cashier_id");
AlipaySpiCashierSignResponse response = new AlipaySpiCashierSignResponse();
response.setStatus("1");
return response;
}
/** 收银员签退 **/
public AlipaySpiCashierUnSignResponse cashierUnsign(MchStoreDevice mchStoreDevice) {
String cashierId = getValString("cashier_id");
AlipaySpiCashierUnSignResponse response = new AlipaySpiCashierUnSignResponse();
response.setStatus("1");
return response;
}
/** 查询收银员列表 **/
public AlipaySpiCashierBatchQueryResponse batchQuery(MchStoreDevice mchStoreDevice) {
AlipaySpiCashierBatchQueryResponse response = new AlipaySpiCashierBatchQueryResponse();
/*List<AlipaySpiCashierBatchQueryResponse.Cashier> cashierList = new ArrayList<>();
AlipaySpiCashierBatchQueryResponse.Cashier cashier = new AlipaySpiCashierBatchQueryResponse.Cashier();
cashier.setCashierId("1001");
cashier.setCashierName("计全科技收银员1");
cashierList.add(cashier);
response.setCashierList(cashierList);*/
return response;
}
/** 查询收银员状态 **/
public AlipaySpiCashierQueryResponse cashierQuery(MchStoreDevice mchStoreDevice) {
AlipaySpiCashierQueryResponse response = new AlipaySpiCashierQueryResponse();
response.setStatus("0");
response.setCashierId("0");
return response;
}
}

View File

@@ -0,0 +1,241 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.entity.PayOrderCount;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.AliBarOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.WxBarOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.YsfBarOrderRQ;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.JsonKit;
import com.jeequan.jeepay.db.entity.MchStoreDevice;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.*;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.util.AlipaySpiRuyiUtil;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.StatsTradeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 支付宝如意spi接口下单、查单
*
* @author zx
*
* @date 2021-04-27 15:50
*/
@Slf4j
@Service
public class AlipaySpiRuyiPayOrderService extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private StatsTradeService statsTradeService;
/** 下单 **/
public AlipaySpiPayOrderResponse pay(MchStoreDevice mchStoreDevice) throws Exception {
String logPrefix = "【支付宝如意Lite】下单";
Long amountL = getRequiredAmountL("total_amount");
String authCode = getValStringRequired("auth_code");
String subject = getValStringDefault("subject", "支付宝如意Lite收款");
String merchantOrderNo = getValStringRequired("merchant_order_no");
String extendParams = getValString("extend_params");
Integer payTimeout = getValInteger("pay_timeout"); // 订单有效时间,需要控制未支付超时关闭,单位为分钟,格式为整数。
String attachParams = getValString("attach_params"); // 附加字段,在支付结果、查询、支付结果异步通知,原样返回。
// 端上超时会相同参数重复调用
PayOrder payOrder = payOrderService.queryMchOrder(mchStoreDevice.getMchNo(), null, merchantOrderNo);
if (payOrder != null) {
return AlipaySpiRuyiUtil.covertToAlipaySpiPayOrderResponse(payOrder);
}
// 封装下单参数
UnifiedOrderRQ rq = buildRqByAuthCode(authCode);
rq.setMchOrderNo(merchantOrderNo);
rq.setCurrency("cny");
rq.setAmount(amountL);
rq.setSubject(subject);
rq.setBody(subject);
rq.setExtParam(attachParams);
rq.setMchNo(mchStoreDevice.getMchNo());
rq.setAppId(mchStoreDevice.getAppId());
rq.setStoreId(mchStoreDevice.getStoreId());
if (payTimeout != null) {
rq.setExpiredTime(payTimeout * 60);
}
// 设备信息
UnifiedOrderRQ.DeviceInfo deviceInfo = new UnifiedOrderRQ.DeviceInfo();
deviceInfo.setDeviceNo(mchStoreDevice.getDeviceNo());
deviceInfo.setDeviceType(PayOrder.DEVICE_TYPE_ALIPAY_RUYI_LITE);
deviceInfo.setProvider(mchStoreDevice.getProvider());
rq.setDeviceInfo(JSONObject.toJSONString(deviceInfo));
rq.setSignType(MchApp.SIGN_TYPE_MD5); // 设置默认签名方式为MD5
ApiRes apiRes = this.unifiedOrder(rq.getWayCode(), rq, null,null);
logger.info("{}调起支付返回apiRes{}", logPrefix, JSON.toJSONString(apiRes));
if (ApiCodeEnum.SUCCESS.getCode() != apiRes.getCode()) {
throw new AlipayApiException("BIZ_ERROR", apiRes.getMsg());
}
UnifiedOrderRS aliBarOrderRS = (UnifiedOrderRS) apiRes.getData();
PayOrder dbRecord = payOrderService.getById(aliBarOrderRS.getPayOrderId());
AlipaySpiPayOrderResponse response = AlipaySpiRuyiUtil.covertToAlipaySpiPayOrderResponse(dbRecord);
return response;
}
/** 查单 **/
public AlipaySpiPayOrderQueryResponse payQuery(MchStoreDevice mchStoreDevice) throws AlipayApiException {
String payOrderId = getValString("isv_order_no");
String mchOrderNo = getValString("merchant_order_no");
PayOrder payOrder = payOrderService.queryMchOrder(mchStoreDevice.getMchNo(), payOrderId, mchOrderNo);
if (payOrder == null || !PayOrder.DEVICE_TYPE_ALIPAY_RUYI_LITE.equals(payOrder.getDeviceType()) || !payOrder.getDeviceNo().equals(mchStoreDevice.getDeviceNo())) {
throw new AlipayApiException("ORDER_NOT_EXIST", "支付订单不存在");
}
return AlipaySpiRuyiUtil.covertToAlipaySpiPayOrderQueryResponse(payOrder);
}
/** 账单流水查询 **/
public SpiBaseResponse billdetailQuery(MchStoreDevice mchStoreDevice, Integer pageNum, Integer pageSize, String queryDate) {
PayOrder payOrderParams = new PayOrder();
// payOrderParams.setDeviceNo(mchStoreDevice.getDeviceNo()); // 设备号
// payOrderParams.setDeviceType(PayOrder.DEVICE_TYPE_ALIPAY_RUYI_LITE); // 如意Lite设备类型
payOrderParams.setMchNo(mchStoreDevice.getMchNo()); // 当前绑定的商户
payOrderParams.setStoreId(mchStoreDevice.getStoreId()); // 当前绑定的门店
payOrderParams.addExt("queryDateRange", covertQueryDateStr(getQueryDate(queryDate)));
// 分页查询
IPage<PayOrder> page = payOrderService.listByPage(new Page(pageNum, pageSize), payOrderParams, JsonKit.newJson("unionOrderState", "2,4,5"));
List<AlipaySpiBilldetailQueryResponse.Order> orderList = new ArrayList<>();
if (CollUtil.isNotEmpty(page.getRecords())) {
page.getRecords().forEach(payOrder -> {
AlipaySpiBilldetailQueryResponse.Order alipayOrder = new AlipaySpiBilldetailQueryResponse.Order();
alipayOrder.setOrderType(payOrder.getState() == PayOrder.STATE_SUCCESS ? "PAY" : payOrder.getState() == PayOrder.STATE_REFUND ? "REFUND" : "PAY");
alipayOrder.setMerchantOrderNo(payOrder.getMchOrderNo());
alipayOrder.setIsvOrderNo(payOrder.getPayOrderId());
alipayOrder.setChannelOrderNo(payOrder.getChannelOrderNo());
alipayOrder.setChannelType(AlipaySpiRuyiUtil.covertPaywayTypeToChannelType(payOrder.getWayCodeType()));
alipayOrder.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount()));
alipayOrder.setTerminalId(payOrder.getDeviceNo());
alipayOrder.setSubject(payOrder.getSubject());
if (payOrder.getState() == PayOrder.STATE_SUCCESS) {
alipayOrder.setReceiptAmount(alipayOrder.getTotalAmount());
alipayOrder.setBuyerPayAmount(alipayOrder.getTotalAmount());
alipayOrder.setOrderTime(DateUtil.formatDateTime(payOrder.getSuccessTime()));
}else if (payOrder.getState() == PayOrder.STATE_REFUND) {
alipayOrder.setRefundAmount(AmountUtil.convertCent2Dollar(payOrder.getRefundAmount()));
alipayOrder.setOrderTime(DateUtil.formatDateTime(payOrder.getSuccessTime()));
}
// alipayOrder.setMerchantRefundNo();
// alipayOrder.setIsvRefundNo();
// alipayOrder.setChannelRefundNo();
orderList.add(alipayOrder);
});
}
AlipaySpiBilldetailQueryResponse response = new AlipaySpiBilldetailQueryResponse();
response.setTotalSize(String.valueOf(page.getTotal()));
response.setOrderList(orderList);
return response;
}
/** 账单统计查询 **/
public SpiBaseResponse billstatisticsQuery(MchStoreDevice mchStoreDevice, String queryDateStr) {
// 查询条件
JSONObject paramJSON = new JSONObject();
// 门店ID条件
List<String> storeIdList = new ArrayList<>();
storeIdList.add(mchStoreDevice.getStoreId());
paramJSON.put("storeIdList", storeIdList);
// 时间条件
DateTime queryDate = getQueryDate(queryDateStr);
paramJSON.put("queryDateRange", covertQueryDateStr(queryDate));
PayOrderCount orderCount = statsTradeService.selectTotalByStore(paramJSON, null);
AlipaySpiBillstatisticsQueryResponse response = new AlipaySpiBillstatisticsQueryResponse();
response.setStatisticTime(DateUtil.formatDateTime(queryDate));
Long payAmount = orderCount.getTotalSuccAmt();
response.setReceiptAmount(BigDecimal.valueOf(payAmount).divide(new BigDecimal("100")).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
response.setReceiptCount(orderCount.getTotalNum().toString());
Long refundAmt = orderCount.getTotalRefundAmt();
response.setRefundAmount(BigDecimal.valueOf(refundAmt).divide(new BigDecimal("100")).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
response.setRefundCount(orderCount.getTotalRefundNum().toString());
response.setTotalIncome(BigDecimal.valueOf(payAmount- refundAmt).divide(new BigDecimal("100")).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
return response;
}
private UnifiedOrderRQ buildRqByAuthCode(String authCode) {
String wayCode = JeepayKit.getPayWayCodeByBarCode(authCode);
if (CS.PAY_WAY_CODE.WX_BAR.equals(wayCode)) {
WxBarOrderRQ wxBarOrderRQ = new WxBarOrderRQ();
wxBarOrderRQ.setAuthCode(authCode);
return wxBarOrderRQ;
}else if (CS.PAY_WAY_CODE.ALI_BAR.equals(wayCode)) {
AliBarOrderRQ aliBarOrderRQ = new AliBarOrderRQ();
aliBarOrderRQ.setAuthCode(authCode);
return aliBarOrderRQ;
}else if (CS.PAY_WAY_CODE.YSF_BAR.equals(wayCode)) {
YsfBarOrderRQ ysfBarOrderRQ = new YsfBarOrderRQ();
ysfBarOrderRQ.setAuthCode(authCode);
return ysfBarOrderRQ;
}else {
throw new BizException("不支持的条码");
}
}
/**
* 获取查询时间范围
*/
private DateTime getQueryDate(String queryDateStr) {
// 时间搜索条件,默认今天
DateTime queryDate = DateUtil.date();
if (StringUtils.isNotBlank(queryDateStr)) {
queryDate = DateUtil.parseDate(queryDateStr);
}
return queryDate;
}
/**
* 转换查询时间范围
*/
private String covertQueryDateStr(DateTime queryDate) {
return "customDate_"+DateUtil.formatDate(DateUtil.beginOfDay(queryDate))+"_"+DateUtil.formatDate(DateUtil.endOfDay(queryDate));
}
}

View File

@@ -0,0 +1,121 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.service;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.refund.RefundOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.refund.RefundOrderRS;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchStoreDevice;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.RefundOrder;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiPayOrderQueryResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiRefundAuthCodeApplyResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiRefundOrderResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.SpiBaseResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.util.AlipaySpiRuyiUtil;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.ctrl.refund.RefundOrderController;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.RefundOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 支付宝如意spi接口退款、退款查单
*
* @author zx
*
* @date 2021-04-27 15:50
*/
@Slf4j
@Service
public class AlipaySpiRuyiRefundOrderService extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private RefundOrderService refundOrderService;
/** 通过交易凭证商户单号查询支付订单 **/
public AlipaySpiPayOrderQueryResponse billQuery(MchStoreDevice mchStoreDevice) {
String platformMchOrderNo = getValStringRequired("bill_merchant_no");
PayOrder payOrder = payOrderService.selectOneByPlatformMchOrderNo(platformMchOrderNo);
if (payOrder == null || !PayOrder.DEVICE_TYPE_ALIPAY_RUYI_LITE.equals(payOrder.getDeviceType()) || !payOrder.getDeviceNo().equals(mchStoreDevice.getDeviceNo())) {
throw new BizException("支付订单不存在");
}
return AlipaySpiRuyiUtil.covertToAlipaySpiPayOrderQueryResponse(payOrder);
}
/** 申请退款授权码 **/
public SpiBaseResponse refundAuthCodeApply(MchStoreDevice mchStoreDevice) {
AlipaySpiRefundAuthCodeApplyResponse response = new AlipaySpiRefundAuthCodeApplyResponse();
response.setGrantResult(true);
response.setGrantPath("OTHER");
return response;
}
/** 退款 **/
public AlipaySpiRefundOrderResponse refund(MchStoreDevice mchStoreDevice) throws AlipayApiException {
String logPrefix = "【支付宝如意Lite】退款";
Long refundAmount = getRequiredAmountL("refund_amount");
String payOrderId = getValStringRequired("isv_order_no"); // 服务商测订单号
String merchantOrderNo = getValStringRequired("merchant_order_no"); // 商家订单号
String merchantRefundNo = getValStringRequired("merchant_refund_no"); // 商家退款单号
String authCode = getValString("auth_code");
// 端上超时会相同参数重复调用
RefundOrder refundOrder = refundOrderService.queryMchOrder(mchStoreDevice.getMchNo(), merchantRefundNo, null);
if (refundOrder != null) {
return AlipaySpiRuyiUtil.covertToAlipaySpiRefundOrderResponse(refundOrder, merchantOrderNo);
}
// 封装下单参数
RefundOrderRQ rq = new RefundOrderRQ();
rq.setMchOrderNo(merchantOrderNo);
rq.setPayOrderId(payOrderId);
rq.setMchRefundNo(merchantRefundNo);
rq.setCurrency("cny");
rq.setRefundAmount(refundAmount);
rq.setRefundReason("如意Lite退款");
rq.setClientIp(getClientIp());
rq.setMchNo(mchStoreDevice.getMchNo());
rq.setAppId(mchStoreDevice.getAppId());
rq.setSignType(MchApp.SIGN_TYPE_MD5); // 设置默认签名方式为MD5
RefundOrderController refundOrderController = SpringBeansUtil.getBean(RefundOrderController.class);
ApiRes apiRes = refundOrderController.refund(rq);
logger.info("{}发起退款返回apiRes{}", logPrefix, JSON.toJSONString(apiRes));
if (ApiCodeEnum.SUCCESS.getCode() != apiRes.getCode()) {
throw new AlipayApiException("BIZ_ERROR", apiRes.getMsg());
}
RefundOrderRS refundOrderRS = (RefundOrderRS) apiRes.getData();
RefundOrder dbRecord = refundOrderService.getById(refundOrderRS.getRefundOrderId());
AlipaySpiRefundOrderResponse response = AlipaySpiRuyiUtil.covertToAlipaySpiRefundOrderResponse(dbRecord, merchantOrderNo);
return response;
}
/** 通过交易凭证商户单号查询支付订单 **/
public AlipaySpiRefundOrderResponse refundQuery(MchStoreDevice mchStoreDevice) {
String merchantOrderNo = getValStringRequired("merchant_order_no"); // 商家订单号
String payOrderId = getValStringRequired("isv_order_no"); // 服务商订单号
String merchantRefundNo = getValStringRequired("merchant_refund_no"); // 商家退款单号
String refundOrderId = getValString("isv_refund_no"); // 服务商退款订单号
RefundOrder refundOrder = refundOrderService.queryMchOrder(mchStoreDevice.getMchNo(), merchantRefundNo, refundOrderId);
if (refundOrder == null || !refundOrder.getPayOrderId().equals(payOrderId)) {
throw new BizException("退款订单不存在");
}
return AlipaySpiRuyiUtil.covertToAlipaySpiRefundOrderResponse(refundOrder, merchantOrderNo);
}
}

View File

@@ -0,0 +1,29 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.util;
import java.util.HashMap;
import java.util.Map;
/*
* 支付宝异步通知消息 工具类
*
* @author zx
*
* @date 2023/3/21 17:40
*/
public class AlipayNotifyUtil {
public static final String NOTIFY_UNKNOWN = "unknown"; // 未知消息
public static final String NOTIFY_SP_OPERATION_RESULT = "alipay.open.sp.operation.result.notify"; // 服务商代运营操作结果通知
public static final String NOTIFY_SHOP_SAVE_PASSED = "ant.merchant.expand.shop.save.passed"; // 蚂蚁店铺保存审核通过消息
public static final String NOTIFY_SHOP_SAVE_REJECTED = "ant.merchant.expand.shop.save.rejected"; // 店铺保存拒绝消息
public static final Map<String, String> notifyMap = new HashMap<>();
static {
notifyMap.put(NOTIFY_UNKNOWN, "未知接口");
notifyMap.put(NOTIFY_SP_OPERATION_RESULT, "服务商代运营操作结果通知");
notifyMap.put(NOTIFY_SHOP_SAVE_PASSED, "蚂蚁店铺保存审核通过消息");
notifyMap.put(NOTIFY_SHOP_SAVE_REJECTED, "店铺保存拒绝消息");
}
}

View File

@@ -0,0 +1,145 @@
package com.jeequan.jeepay.pay.ctrl.channel.alipay.util;
import cn.hutool.core.date.DateUtil;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.RefundOrder;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiPayOrderQueryResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiPayOrderResponse;
import com.jeequan.jeepay.pay.ctrl.channel.alipay.model.response.AlipaySpiRefundOrderResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/*
* 支付宝spi工具
*
* @author zx
*
* @date 2021/6/8 17:40
*/
@Slf4j
public class AlipaySpiRuyiUtil {
public static final String SPI_RUYI_UNKNOWN = "unknown";
public static final String SPI_RUYI_PAY = "spi.alipay.merchant.order.commonisv.pay";
public static final String SPI_RUYI_PAY_QUERY = "spi.alipay.merchant.order.commonisv.query";
public static final String SPI_RUYI_BILL_QUERY = "spi.alipay.merchant.order.commonisv.bill.query";
public static final String SPI_RUYI_REFUND_AUTHCODE_APPLY = "spi.alipay.commerce.refundauthcode.apply";
public static final String SPI_RUYI_REFUND = "spi.alipay.merchant.order.commonisv.refund";
public static final String SPI_RUYI_REFUND_QUERY = "spi.alipay.merchant.order.commonisv.refund.query";
public static final String SPI_RUYI_BILL_DETAIL_QUERY = "spi.alipay.commerce.billdetail.query";
public static final String SPI_RUYI_BILL_STATISTICS_QUERY = "spi.alipay.commerce.billstatistics.query";
public static final String SPI_RUYI_CASHIER_SIGN = "spi.alipay.commerce.fmcgsaascashier.sign";
public static final String SPI_RUYI_CASHIER_UNSIGN = "spi.alipay.commerce.fmcgsaascashier.unsign";
public static final String SPI_RUYI_CASHIER_BATCH_QUERY = "spi.alipay.commerce.fmcgsaascashier.batchquery";
public static final String SPI_RUYI_CASHIER_QUERY = "spi.alipay.commerce.fmcgsaascashier.query";
public static Map<String, String> spiMap = new HashMap<>();
static {
spiMap.put(SPI_RUYI_UNKNOWN, "未知接口");
spiMap.put(SPI_RUYI_PAY, "支付下单接口");
spiMap.put(SPI_RUYI_PAY_QUERY, "支付查单接口");
spiMap.put(SPI_RUYI_BILL_QUERY, "扫支付凭证商户单号查单接口");
spiMap.put(SPI_RUYI_REFUND_AUTHCODE_APPLY, "申请退款授权码接口");
spiMap.put(SPI_RUYI_REFUND, "发起退款接口");
spiMap.put(SPI_RUYI_REFUND_QUERY, "退款查单接口");
spiMap.put(SPI_RUYI_BILL_DETAIL_QUERY, "账单流水查询接口");
spiMap.put(SPI_RUYI_BILL_STATISTICS_QUERY, "账单统计查询接口");
spiMap.put(SPI_RUYI_CASHIER_SIGN, "收银员签到接口");
spiMap.put(SPI_RUYI_CASHIER_UNSIGN, "收银员签退接口");
spiMap.put(SPI_RUYI_CASHIER_BATCH_QUERY, "查询收银员列表接口");
spiMap.put(SPI_RUYI_CASHIER_QUERY, "查询收银员状态接口");
}
/** 支付订单下单接口 转 支付宝订单响应 **/
public static AlipaySpiPayOrderResponse covertToAlipaySpiPayOrderResponse(PayOrder payOrder) {
AlipaySpiPayOrderResponse response = new AlipaySpiPayOrderResponse();
response.setMerchantOrderNo(payOrder.getMchOrderNo());
response.setIsvOrderNo(payOrder.getPayOrderId());
response.setChannelOrderNo(StringUtils.defaultString(payOrder.getChannelOrderNo(), ""));
response.setChannelType(covertPaywayTypeToChannelType(payOrder.getWayCodeType()));
response.setOrderState(covertToAlipayOrderState(payOrder.getState(), payOrder.getRefundState()));
response.setPayTime(DateUtil.formatDateTime(payOrder.getSuccessTime()));
response.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount()));
response.setReceiptAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount()));
response.setBuyerPayAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount()));
response.setBuyerUserId(payOrder.getChannelUser());
response.setBuyerAccountName("");
response.setAttachParams(payOrder.getExtParam());
return response;
}
/** 支付订单查单接口 转 支付宝订单响应 **/
public static AlipaySpiPayOrderQueryResponse covertToAlipaySpiPayOrderQueryResponse(PayOrder payOrder) {
AlipaySpiPayOrderQueryResponse response = new AlipaySpiPayOrderQueryResponse();
response.setMerchantOrderNo(payOrder.getMchOrderNo());
response.setIsvOrderNo(payOrder.getPayOrderId());
response.setChannelOrderNo(payOrder.getChannelOrderNo());
response.setChannelType(covertPaywayTypeToChannelType(payOrder.getWayCodeType()));
response.setTerminalId(payOrder.getDeviceNo());
response.setOrderState(covertToAlipayOrderState(payOrder.getState(), payOrder.getRefundState()));
response.setPayTime(DateUtil.formatDateTime(payOrder.getSuccessTime()));
response.setTotalAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount()));
response.setReceiptAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount()));
response.setBuyerPayAmount(AmountUtil.convertCent2Dollar(payOrder.getAmount()));
response.setBuyerUserId(payOrder.getChannelUser());
response.setBuyerAccountName("");
response.setAttachParams(payOrder.getExtParam());
return response;
}
/** 退款订单 转 支付宝退款订单响应 **/
public static AlipaySpiRefundOrderResponse covertToAlipaySpiRefundOrderResponse(RefundOrder refundOrder, String mchOrderNo) {
AlipaySpiRefundOrderResponse response = new AlipaySpiRefundOrderResponse();
response.setMerchantOrderNo(mchOrderNo);
response.setIsvOrderNo(refundOrder.getPayOrderId());
response.setMerchantRefundNo(refundOrder.getMchRefundNo());
response.setIsvRefundNo(refundOrder.getRefundOrderId());
response.setChannelRefundNo(refundOrder.getChannelOrderNo());
response.setRefundAmount(AmountUtil.convertCent2Dollar(refundOrder.getRefundAmount()));
if (refundOrder.getState() == RefundOrder.STATE_SUCCESS) {
response.setFinishTime(DateUtil.formatDateTime(refundOrder.getSuccessTime()));
response.setOrderState("SUCCESS");
}else if (refundOrder.getState() == RefundOrder.STATE_ING) {
response.setOrderState("ACCEPT");
}else if (refundOrder.getState() == RefundOrder.STATE_INIT) {
response.setOrderState("PROCESSING");
}
return response;
}
/** 转换支付订单状态 **/
private static String covertToAlipayOrderState(Byte state, Byte refundState) {
switch (state) {
case PayOrder.STATE_INIT:
case PayOrder.STATE_ING: return "WAIT_PAY";
case PayOrder.STATE_SUCCESS: {
if (refundState == PayOrder.REFUND_STATE_NONE) {
return "ORDER_SUCCESS";
}else if (refundState == PayOrder.REFUND_STATE_SUB) {
return "REFUND_PART";
}
}
case PayOrder.STATE_REFUND: return "REFUND_SUCCESS";
default: return "ORDER_CLOSED";
}
}
/** 转换支付方式类型 **/
public static String covertPaywayTypeToChannelType(String wayCodeType) {
switch (wayCodeType) {
case CS.PAY_WAY_CODE_TYPE.ALIPAY: return "001";
case CS.PAY_WAY_CODE_TYPE.WECHAT: return "002";
case CS.PAY_WAY_CODE_TYPE.YSFPAY:
case CS.PAY_WAY_CODE_TYPE.UNIONPAY:
return "007";
default: return "100";
}
}
}

View File

@@ -0,0 +1,36 @@
package com.jeequan.jeepay.pay.ctrl.device.speaker;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.device.ISpeakerService;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO
* 音箱相关接口类型
* @author crystal
* @date 2023/12/28 11:23
*/
@RestController
@RequestMapping("/api/speaker")
public class SpeakerController extends ApiController {
@RequestMapping(value = "/bind/status/{provider}")
public JSONObject bindStatus(@PathVariable("provider") String provider){
JSONObject params = getReqParamJSON();
//获取音箱接口配置
ISpeakerService speakerService = SpringBeansUtil.getBean(provider + "SpeakerService", ISpeakerService.class);
return speakerService.bindQuery(params);
}
@RequestMapping(value = "/get/url/{provider}")
public JSONObject getUrl(@PathVariable("provider") String provider){
JSONObject reqData = getReqParamJSON();
//获取音箱接口配置
ISpeakerService speakerService = SpringBeansUtil.getBean(provider + "SpeakerService", ISpeakerService.class);
return speakerService.getUrl(reqData);
}
}

View File

@@ -0,0 +1,152 @@
package com.jeequan.jeepay.pay.ctrl.division;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.msg.DivisionChannelNotifyModel;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.PayOrderDivisionRecord;
import com.jeequan.jeepay.pay.service.PayOrderMarketReissueService;
import com.jeequan.jeepay.service.impl.PayOrderDivisionRecordService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.thirdparty.channel.AbstractDivisionRecordChannelNotifyService;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/*
* 分账渠道侧的通知入口Controller
*
* @author terrfly
*
* @date 2023/3/29 15:35
*/
@Slf4j
@Controller
public class DivisionRecordChannelNotifyController extends AbstractCtrl {
@Autowired private PayOrderDivisionRecordService payOrderDivisionRecordService;
@Autowired private ConfigContextQueryService configContextQueryService;
@Autowired private PayOrderService payOrderService;
@Autowired private PayOrderMarketReissueService payOrderMarketReissueService;
/** 异步回调入口 **/
@ResponseBody
@RequestMapping(value= {"/api/divisionRecordChannelNotify/{ifCode}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode){
String divisionBatchId = null;
String logPrefix = "进入[" +ifCode+ "]分账回调";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询支付接口是否存在
AbstractDivisionRecordChannelNotifyService divisionNotifyService = SpringBeansUtil.getBean(ifCode + "DivisionRecordChannelNotifyService", AbstractDivisionRecordChannelNotifyService.class);
// 支付通道接口实现不存在
if(divisionNotifyService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 解析批次号 和 请求参数
MutablePair<String, Object> mutablePair = divisionNotifyService.parseParams(request);
if(mutablePair == null){ // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//解析到订单号
divisionBatchId = mutablePair.left;
log.info("{}, 解析数据为divisionBatchId:{}, params:{}", logPrefix, divisionBatchId, mutablePair.getRight());
// 通过 batchId 查询出列表( 注意: 需要按照ID 排序!!!!
List<PayOrderDivisionRecord> recordList = null;
if(CS.IF_CODE.YSPAY.equals(ifCode)){
recordList = payOrderDivisionRecordService.list(PayOrderDivisionRecord.gw()
.eq(PayOrderDivisionRecord::getState, PayOrderDivisionRecord.STATE_ACCEPT)
.eq(PayOrderDivisionRecord::getPayOrderId, divisionBatchId)
.orderByAsc(PayOrderDivisionRecord::getRecordId));
}else{
recordList = payOrderDivisionRecordService.list(PayOrderDivisionRecord.gw()
.eq(PayOrderDivisionRecord::getState, PayOrderDivisionRecord.STATE_ACCEPT)
.eq(PayOrderDivisionRecord::getBatchOrderId, divisionBatchId)
.orderByAsc(PayOrderDivisionRecord::getRecordId)
);
}
// 订单不存在
if(recordList == null || recordList.isEmpty()){
log.error("{}, 待处理订单不存在. divisionBatchId={} ", logPrefix, divisionBatchId);
return divisionNotifyService.doNotifyOrderNotExists(request);
}
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(recordList.get(0).getMchNo(), recordList.get(0).getAppId(),recordList.get(0).getMchExtNo());
//调起接口的回调判断
DivisionChannelNotifyModel notifyResult = divisionNotifyService.doNotify(request, mutablePair.getRight(), recordList, mchAppConfigContext);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getApiRes() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
if(notifyResult.getRecordResultMap() != null && !notifyResult.getRecordResultMap().isEmpty()){
for (Long divisionId : notifyResult.getRecordResultMap().keySet()) {
// 单条结果
ChannelRetMsg retMsgItem = notifyResult.getRecordResultMap().get(divisionId);
// 明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == retMsgItem.getChannelState()){
payOrderDivisionRecordService.updateRecordSuccessOrFailBySingleItem(divisionId, PayOrderDivisionRecord.STATE_SUCCESS, retMsgItem.getChannelOriginResponse());
} else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == retMsgItem.getChannelState()){ // 明确失败
payOrderDivisionRecordService.updateRecordSuccessOrFailBySingleItem(divisionId, PayOrderDivisionRecord.STATE_FAIL, StringUtils.defaultIfEmpty(retMsgItem.getChannelErrMsg(), retMsgItem.getChannelOriginResponse()));
}
}
}
payOrderService.updateDivState(recordList.get(0).getPayOrderId(), PayOrder.DIVISION_STATE_FINISH);
PayOrder payOrder = payOrderService.getById(recordList.get(0).getPayOrderId());
payOrderMarketReissueService.marketCashFeeReissue(payOrder,mchAppConfigContext);
log.info("===== {}, 通知完成。 divisionBatchId={}, parseState = {} =====", logPrefix, divisionBatchId, notifyResult);
return notifyResult.getApiRes();
} catch (BizException e) {
log.error("{}, divisionBatchId={}, BizException", logPrefix, divisionBatchId, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, divisionBatchId={}, ResponseException", logPrefix, divisionBatchId, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, divisionBatchId={}, 系统异常", logPrefix, divisionBatchId, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}

View File

@@ -0,0 +1,201 @@
package com.jeequan.jeepay.pay.ctrl.division;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IDivisionService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.division.DivisionReceiverBindRQ;
import com.jeequan.jeepay.core.model.rqrs.division.DivisionReceiverBindRS;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchDivisionReceiver;
import com.jeequan.jeepay.db.entity.MchDivisionReceiverGroup;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverService;
import com.jeequan.jeepay.service.impl.PayInterfaceConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.Date;
/**
* 分账账号绑定
*
* @author terrfly
*
* @date 2021/8/25 9:07
*/
@Slf4j
@RestController
public class MchDivisionReceiverBindController extends ApiController {
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
@Autowired private MchDivisionReceiverService mchDivisionReceiverService;
@Autowired private MchDivisionReceiverGroupService mchDivisionReceiverGroupService;
/** 分账账号绑定 **/
@MethodLog(remark = "绑定分账用户API")
@PostMapping("/api/division/receiver/bind")
public ApiRes bind(){
//获取参数 & 验签
DivisionReceiverBindRQ bizRQ = getRQByWithMchSign(DivisionReceiverBindRQ.class);
try {
//检查商户应用是否存在该接口
String ifCode = bizRQ.getIfCode();
// 商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(bizRQ.getMchNo(), bizRQ.getAppId(),bizRQ.getMchExtNo());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
MchInfo mchInfo = mchAppConfigContext.getMchInfo();
if(!payInterfaceConfigService.mchAppHasAvailableIfCode(bizRQ.getAppId(), ifCode)){
throw new BizException("商户应用的支付配置不存在或已关闭");
}
MchDivisionReceiverGroup group = mchDivisionReceiverGroupService.findByIdAndMchNo(bizRQ.getReceiverGroupId(), bizRQ.getMchNo());
if(group == null){
throw new BizException("商户分账账号组不存在,请检查或进入商户平台进行创建操作");
}
BigDecimal divisionProfit = new BigDecimal(bizRQ.getDivisionProfit());
if(divisionProfit.compareTo(BigDecimal.ZERO) <= 0 || divisionProfit.compareTo(BigDecimal.ONE) > 1){
throw new BizException("账号分账比例有误, 配置值为[0.0001~1.0000]");
}
//生成数据库对象信息 (数据不完成, 暂时不可入库操作)
MchDivisionReceiver receiver = genRecord(bizRQ, group, mchInfo, divisionProfit);
//调起上游接口
IDivisionService divisionService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "DivisionService", IDivisionService.class);
if(divisionService == null){
throw new BizException("系统不支持该分账接口");
}
// 同一个分账接收方账号, 不可以在同一个商户的同一个接口绑定多次! 避免出现类似微信: 分账对象重复
if(mchDivisionReceiverService.count(MchDivisionReceiver.gw()
.eq(MchDivisionReceiver::getMchNo, bizRQ.getMchNo()).eq(MchDivisionReceiver::getAppId, bizRQ.getAppId())
.eq(MchDivisionReceiver::getIfCode, bizRQ.getIfCode()).eq(MchDivisionReceiver::getAccNo, bizRQ.getAccNo())
.eq(MchDivisionReceiver::getAccType, receiver.getAccType())
) > 0 ){
throw new BizException("分账接收账号["+ receiver.getAccNo() +"]已绑定,无需重复绑定。");
}
ChannelRetMsg retMsg = divisionService.bind(receiver, mchAppConfigContext);
if(retMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
receiver.setState(CS.YES);
receiver.setBindSuccessTime(new Date());
receiver.setChannelAccNo(retMsg.getChannelAttach()); // 在channelAttach数据中写入 渠道用户编号
mchDivisionReceiverService.save(receiver);
}else{
receiver.setState(CS.NO);
receiver.setChannelBindResult(retMsg.getChannelErrMsg());
}
DivisionReceiverBindRS bizRes = DivisionReceiverBindRS.buildByRecord(receiver);
if(retMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
bizRes.setBindState(CS.YES);
}else{
bizRes.setBindState(CS.NO);
bizRes.setErrCode(retMsg.getChannelErrCode());
bizRes.setErrMsg(retMsg.getChannelErrMsg());
}
return ApiResKit.okWithSign(bizRes, mchAppConfigContext.getMchApp(), bizRQ.getSignType());
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
private MchDivisionReceiver genRecord(DivisionReceiverBindRQ bizRQ, MchDivisionReceiverGroup group, MchInfo mchInfo, BigDecimal divisionProfit){
MchDivisionReceiver receiver = new MchDivisionReceiver();
receiver.setReceiverAlias(StringUtils.defaultIfEmpty(bizRQ.getReceiverAlias(), bizRQ.getAccNo())); //别名
receiver.setReceiverGroupId(bizRQ.getReceiverGroupId()); //分组ID
receiver.setReceiverGroupName(group.getReceiverGroupName()); //组名称
receiver.setMchNo(bizRQ.getMchNo()); //商户号
// receiver.setIsvNo(mchInfo.getIsvNo()); //isvNo
receiver.setAppId(bizRQ.getAppId()); //appId
receiver.setIfCode(bizRQ.getIfCode()); //接口代码
receiver.setAccType(bizRQ.getAccType()); //账号类型
receiver.setAccNo(bizRQ.getAccNo()); //账号
receiver.setAccName(bizRQ.getAccName()); //账号名称
receiver.setRelationType(bizRQ.getRelationType()); //关系
receiver.setRelationTypeName(getRelationTypeName(bizRQ.getRelationType())); //关系名称
if(receiver.getRelationTypeName() == null){
receiver.setRelationTypeName(bizRQ.getRelationTypeName());
}
receiver.setDivisionProfit(divisionProfit); //分账比例
receiver.setChannelExtInfo(bizRQ.getChannelExtInfo()); //渠道信息
return receiver;
}
public String getRelationTypeName(String relationType){
if("PARTNER".equals(relationType)){
return "合作伙伴";
}else if("SERVICE_PROVIDER".equals(relationType)){
return "服务商";
}else if("STORE".equals(relationType)){
return "门店";
}else if("STAFF".equals(relationType)){
return "员工";
}else if("STORE_OWNER".equals(relationType)){
return "店主";
}else if("HEADQUARTER".equals(relationType)){
return "总部";
}else if("BRAND".equals(relationType)){
return "品牌方";
}else if("DISTRIBUTOR".equals(relationType)){
return "分销商";
}else if("USER".equals(relationType)){
return "用户";
}else if("SUPPLIER".equals(relationType)){
return "供应商";
}else if("PAYMENT".equals(relationType)){
return "收款方";
}
return null;
}
}

View File

@@ -0,0 +1,100 @@
package com.jeequan.jeepay.pay.ctrl.division;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IDivisionService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.division.DivisionReceiverChannelBalanceCashoutRQ;
import com.jeequan.jeepay.core.model.rqrs.division.DivisionReceiverChannelBalanceCashoutRS;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchDivisionReceiver;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverService;
import com.jeequan.jeepay.service.impl.PayInterfaceConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 分账账号 渠道余额提现
*
* @author terrfly
*
* @date 2022/05/11 10:46
*/
@Slf4j
@RestController
public class MchDivisionReceiverChannelBalanceCashoutController extends ApiController {
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
@Autowired private MchDivisionReceiverService mchDivisionReceiverService;
/** 分账账号绑定 **/
@MethodLog(remark = "对分账用户的渠道余额发起提现API")
@PostMapping("/api/division/receiver/channelBalanceCashout")
public ApiRes channelBalanceCashout(){
//获取参数 & 验签
DivisionReceiverChannelBalanceCashoutRQ bizRQ = getRQByWithMchSign(DivisionReceiverChannelBalanceCashoutRQ.class);
try {
MchDivisionReceiver mchDivisionReceiver = mchDivisionReceiverService.getOne(MchDivisionReceiver.gw()
.eq(MchDivisionReceiver::getReceiverId, bizRQ.getReceiverId())
.eq(MchDivisionReceiver::getMchNo, bizRQ.getMchNo())
);
if(mchDivisionReceiver == null){
throw new BizException("分账用户不存在");
}
//检查商户应用是否存在该接口
String ifCode = mchDivisionReceiver.getIfCode();
// 商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(bizRQ.getMchNo(), bizRQ.getAppId(),bizRQ.getMchExtNo());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
if(!payInterfaceConfigService.mchAppHasAvailableIfCode(bizRQ.getAppId(), ifCode)){
throw new BizException("商户应用的支付配置不存在或已关闭");
}
//调起上游接口
IDivisionService divisionService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "DivisionService", IDivisionService.class);
if(divisionService == null){
throw new BizException("系统不支持该分账接口");
}
DivisionReceiverChannelBalanceCashoutRS bizRes = new DivisionReceiverChannelBalanceCashoutRS();
ChannelRetMsg retMsg = divisionService.cashout(mchDivisionReceiver, bizRQ.getCashoutAmount(), mchAppConfigContext);
if(retMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
bizRes.setState(CS.YES);
}else{
bizRes.setState(CS.NO);
bizRes.setErrCode(retMsg.getChannelErrCode());
bizRes.setErrMsg(retMsg.getChannelErrMsg());
}
return ApiResKit.okWithSign(bizRes, mchAppConfigContext.getMchApp(), bizRQ.getSignType());
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
}

View File

@@ -0,0 +1,93 @@
package com.jeequan.jeepay.pay.ctrl.division;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IDivisionService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.division.DivisionReceiverChannelBalanceQueryRQ;
import com.jeequan.jeepay.core.model.rqrs.division.DivisionReceiverChannelBalanceQueryRS;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchDivisionReceiver;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverService;
import com.jeequan.jeepay.service.impl.PayInterfaceConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 分账账号 渠道余额查询
*
* @author terrfly
*
* @date 2022/05/11 10:46
*/
@Slf4j
@RestController
public class MchDivisionReceiverChannelBalanceQueryController extends ApiController {
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
@Autowired private MchDivisionReceiverService mchDivisionReceiverService;
/** ctrl **/
@MethodLog(remark = "查询分账用户可用余额API")
@PostMapping("/api/division/receiver/channelBalanceQuery")
public ApiRes query(){
//获取参数 & 验签
DivisionReceiverChannelBalanceQueryRQ bizRQ = getRQByWithMchSign(DivisionReceiverChannelBalanceQueryRQ.class);
try {
MchDivisionReceiver mchDivisionReceiver = mchDivisionReceiverService.getOne(MchDivisionReceiver.gw()
.eq(MchDivisionReceiver::getReceiverId, bizRQ.getReceiverId())
.eq(MchDivisionReceiver::getMchNo, bizRQ.getMchNo())
);
if(mchDivisionReceiver == null){
throw new BizException("分账用户不存在");
}
//检查商户应用是否存在该接口
String ifCode = mchDivisionReceiver.getIfCode();
// 商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(bizRQ.getMchNo(), bizRQ.getAppId(),bizRQ.getMchExtNo());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
if(!payInterfaceConfigService.mchAppHasAvailableIfCode(bizRQ.getAppId(), ifCode)){
throw new BizException("商户应用的支付配置不存在或已关闭");
}
//调起上游接口
IDivisionService divisionService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "DivisionService", IDivisionService.class);
if(divisionService == null){
throw new BizException("系统不支持该分账接口");
}
Long balance = divisionService.queryBalanceAmount(mchDivisionReceiver, mchAppConfigContext);
DivisionReceiverChannelBalanceQueryRS bizRes = new DivisionReceiverChannelBalanceQueryRS();
bizRes.setReceiverId(bizRQ.getReceiverId());
bizRes.setBalanceAmount(balance);
return ApiResKit.okWithSign(bizRes, mchAppConfigContext.getMchApp(), bizRQ.getSignType());
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
}

View File

@@ -0,0 +1,198 @@
package com.jeequan.jeepay.pay.ctrl.division;
import com.alibaba.fastjson.JSON;
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.division.PayOrderDivisionExecRQ;
import com.jeequan.jeepay.core.model.rqrs.division.PayOrderDivisionExecRS;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.db.entity.MchDivisionReceiver;
import com.jeequan.jeepay.db.entity.MchDivisionReceiverGroup;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.PayOrderDivisionRecord;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.service.PayOrderDivisionProcessService;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 发起分账请求
*
* @author terrfly
*
* @date 2021/8/27 8:01
*/
@Slf4j
@RestController
public class PayOrderDivisionExecController extends ApiController {
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayOrderService payOrderService;
@Autowired private MchDivisionReceiverService mchDivisionReceiverService;
@Autowired private MchDivisionReceiverGroupService mchDivisionReceiverGroupService;
@Autowired private PayOrderDivisionProcessService payOrderDivisionProcessService;
/** 分账执行 **/
@MethodLog(remark = "发起订单分账API")
@PostMapping("/api/division/exec")
public ApiRes exec(){
//获取参数 & 验签
PayOrderDivisionExecRQ bizRQ = getRQByWithMchSign(PayOrderDivisionExecRQ.class);
try {
if(StringUtils.isAllEmpty(bizRQ.getMchOrderNo(), bizRQ.getPayOrderId())){
throw new BizException("mchOrderNo 和 payOrderId不能同时为空");
}
PayOrder payOrder = payOrderService.queryMchOrder(bizRQ.getMchNo(), bizRQ.getPayOrderId(), bizRQ.getMchOrderNo());
if(payOrder == null){
throw new BizException("订单不存在");
}
if(payOrder.getState() != PayOrder.STATE_SUCCESS || payOrder.getDivisionState() != PayOrder.DIVISION_STATE_UNHAPPEN || payOrder.getDivisionMode() != PayOrder.DIVISION_MODE_MANUAL){
throw new BizException("当前订单状态不支持分账");
}
List<PayOrderDivisionMQ.CustomerDivisionReceiver> receiverList = null;
//不使用默认分组, 需要转换每个账号信息
if(bizRQ.getUseSysAutoDivisionReceivers() != CS.YES && !StringUtils.isEmpty(bizRQ.getReceivers())){
receiverList = JSON.parseArray(bizRQ.getReceivers(), PayOrderDivisionMQ.CustomerDivisionReceiver.class);
}
// 验证账号是否合法
this.checkReceiverList(receiverList, payOrder.getIfCode(), bizRQ.getMchNo(), bizRQ.getAppId());
// 商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(bizRQ.getMchNo(), bizRQ.getAppId(),payOrder.getMchExtNo());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
//处理分账请求
ChannelRetMsg channelRetMsg = payOrderDivisionProcessService.processPayOrderDivision(payOrder.getPayOrderId(), bizRQ.getUseSysAutoDivisionReceivers(), receiverList, false, false);
PayOrderDivisionExecRS bizRS = new PayOrderDivisionExecRS();
// 确认分账成功
if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
bizRS.setState(PayOrderDivisionRecord.STATE_SUCCESS);
}else if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL){ //分账失败
bizRS.setState(PayOrderDivisionRecord.STATE_FAIL);
}else if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.WAITING){ // 分账处理中
bizRS.setState(PayOrderDivisionRecord.STATE_ING);
}
bizRS.setChannelBatchOrderId(channelRetMsg.getChannelOrderId());
bizRS.setErrCode(channelRetMsg.getChannelErrCode());
bizRS.setErrMsg(channelRetMsg.getChannelErrMsg());
return ApiResKit.okWithSign(bizRS, mchAppConfigContext.getMchApp(), bizRQ.getSignType());
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
/** 检验账号是否合法 **/
private void checkReceiverList(List<PayOrderDivisionMQ.CustomerDivisionReceiver> receiverList, String ifCode, String mchNo, String appId){
if(receiverList == null || receiverList.isEmpty()){
return ;
}
Set<Long> receiverIdSet = new HashSet<>();
Set<Long> receiverGroupIdSet = new HashSet<>();
for (PayOrderDivisionMQ.CustomerDivisionReceiver receiver : receiverList) {
if(receiver.getReceiverId() != null){
receiverIdSet.add(receiver.getReceiverId());
}
if(receiver.getReceiverGroupId() != null){
receiverGroupIdSet.add(receiver.getReceiverGroupId());
}
if(receiver.getReceiverId() == null && receiver.getReceiverGroupId() == null){
throw new BizException("分账用户组: receiverId 和 与receiverGroupId 必填一项");
}
if(receiver.getDivisionProfit() != null){
if(receiver.getDivisionProfit().compareTo(BigDecimal.ZERO) < 0){
throw new BizException("分账用户receiverId=["+ ( receiver.getReceiverId() == null ? "": receiver.getReceiverId() ) +"]," +
"receiverGroupId=["+ (receiver.getReceiverGroupId() == null ? "": receiver.getReceiverGroupId() ) +"] 分账比例不得小于0%");
}
if(receiver.getDivisionProfit().compareTo(BigDecimal.ONE) > 0){
throw new BizException("分账用户receiverId=["+ ( receiver.getReceiverId() == null ? "": receiver.getReceiverId() ) +"]," +
"receiverGroupId=["+ (receiver.getReceiverGroupId() == null ? "": receiver.getReceiverGroupId() ) +"] 分账比例不得高于100%");
}
}
if(receiver.getDivisionAmount() != null && receiver.getDivisionAmount() <= 0){
throw new BizException("分账用户receiverId=["+ ( receiver.getReceiverId() == null ? "": receiver.getReceiverId() ) + "]," +
"receiverGroupId=["+ (receiver.getReceiverGroupId() == null ? "": receiver.getReceiverGroupId() ) +"] 分账金额请大于0");
}
}
if(!receiverIdSet.isEmpty()){
long receiverCount = mchDivisionReceiverService.count(MchDivisionReceiver.gw()
.in(MchDivisionReceiver::getReceiverId, receiverIdSet)
.eq(MchDivisionReceiver::getMchNo, mchNo)
.eq(MchDivisionReceiver::getAppId, appId)
.eq(MchDivisionReceiver::getIfCode, ifCode)
.eq(MchDivisionReceiver::getState, CS.YES)
);
if(receiverCount != receiverIdSet.size()){
throw new BizException("分账[用户]中包含不存在或渠道不可用账号,请更改");
}
}
if(!receiverGroupIdSet.isEmpty()){
long receiverGroupCount = mchDivisionReceiverGroupService.count(MchDivisionReceiverGroup.gw()
.in(MchDivisionReceiverGroup::getReceiverGroupId, receiverGroupIdSet)
.eq(MchDivisionReceiverGroup::getMchNo, mchNo)
);
if(receiverGroupCount != receiverGroupIdSet.size()){
throw new BizException("分账[账号组]中包含不存在或不可用组,请更改");
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.jeequan.jeepay.pay.ctrl.openapi;
import com.jeequan.jeepay.pay.anno.OpenLabel;
import com.jeequan.jeepay.pay.ctrl.OpenApiController;
import com.jeequan.jeepay.pay.service.openapi.impl.MchApplymentApiService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO
*
* @author deng
* @since 2024/4/19
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/open/applyment")
public class MchApplymentController extends OpenApiController {
private final MchApplymentApiService mchApplymentApiService;
/**
* 创建草稿商户返回编辑的H5链接
* @return
*/
@OpenLabel
@PostMapping(value = "/h5Data")
public Object createApplymentUrl() {
return mchApplymentApiService.createH5MchApplyment(getRQ());
}
/**
* 查询商户信息
* @return
*/
@OpenLabel
@PostMapping(value = "/queryApplymentInfo")
public Object queryApplymentInfo() {
return mchApplymentApiService.queryApplymentInfo(getRQ());
}
}

View File

@@ -0,0 +1,62 @@
package com.jeequan.jeepay.pay.ctrl.openapi;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.openapi.AccessReq;
import com.jeequan.jeepay.core.model.openapi.AccessResp;
import com.jeequan.jeepay.core.service.IOpenApiService;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.pay.anno.OpenLabel;
import com.jeequan.jeepay.pay.ctrl.OpenApiController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO
* 交易Api控制器
* @author crystal
* @date 2024/4/7 15:03
*/
@Slf4j
@RestController
@RequestMapping("/api/open")
public class QueryOpenApiController extends OpenApiController {
@Autowired
@Qualifier("queryTradeOpenApiService")
private IOpenApiService queryTradeService;
@Autowired
@Qualifier("queryRefundOpenApiService")
private IOpenApiService queryRefundService;
/**
* 交易订单查询
* @return
*/
@OpenLabel
@RequestMapping(value = "/query/trade")
public AccessResp tradeQuery(){
return queryTradeService.execute(getRQ());
}
/**
* 退款订单查询
* @return
*/
@OpenLabel
@RequestMapping(value = "/query/refund")
public AccessResp refundQuery(){
return queryRefundService.execute(getRQ());
}
}

View File

@@ -0,0 +1,47 @@
package com.jeequan.jeepay.pay.ctrl.openapi;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.openapi.AccessReq;
import com.jeequan.jeepay.core.model.openapi.AccessResp;
import com.jeequan.jeepay.core.service.IOpenApiService;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.pay.anno.OpenLabel;
import com.jeequan.jeepay.pay.ctrl.OpenApiController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO
* 交易Api控制器
* @author crystal
* @date 2024/4/7 15:03
*/
@Slf4j
@RestController
@RequestMapping("/api/open")
public class RefundOpenApiController extends OpenApiController {
@Autowired
@Qualifier("refundOpenApiService")
private IOpenApiService refundService;
/**
* 退款
* @return
*/
@OpenLabel("")
@RequestMapping(value = "/order/refund")
public AccessResp refund(){
return refundService.execute(getRQ());
}
}

View File

@@ -0,0 +1,234 @@
package com.jeequan.jeepay.pay.ctrl.openapi;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.openapi.AccessReq;
import com.jeequan.jeepay.core.model.openapi.AccessResp;
import com.jeequan.jeepay.core.service.IOpenApiService;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.pay.anno.OpenLabel;
import com.jeequan.jeepay.pay.ctrl.OpenApiController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO
* 交易Api控制器
* @author crystal
* @date 2024/4/7 15:03
*/
@Slf4j
@RestController
@RequestMapping("/api/open")
public class TradeOpenApiController extends OpenApiController {
@Autowired
@Qualifier("jsapiOpenApiService")
private IOpenApiService jsapiService;
@Autowired
@Qualifier("microOpenApiService")
private IOpenApiService microService;
@Autowired
@Qualifier("h5OpenApiService")
private IOpenApiService h5Service;
@Autowired
@Qualifier("scanOpenApiService")
private IOpenApiService scanService;
@Autowired
@Qualifier("appOpenApiService")
private IOpenApiService appService;
@Autowired
@Qualifier("closeOpenApiService")
private IOpenApiService closeService;
@Autowired
@Qualifier("cashierOpenApiService")
private IOpenApiService cashierService;
/**
* 聚合收银台
* @return
*/
@OpenLabel(CS.MCH_APP_API_ENUM_V2.CASHIER_PAY)
@RequestMapping(value = "/payment/chrpay")
public AccessResp chrPay(){
return cashierService.execute(getRQ());
}
/**
* 公众号/生活号支付
* @return
*/
@OpenLabel(CS.MCH_APP_API_ENUM_V2.JSAPI_PAY)
@RequestMapping(value = "/payment/jspay")
public AccessResp jsPay(){
return jsapiService.execute(getRQ());
}
/**
* 聚合扫码支付
* @return
*/
@OpenLabel(CS.MCH_APP_API_ENUM_V2.SCAN_PAY)
@RequestMapping(value = "/payment/scanpay")
public AccessResp scanPay(){
return scanService.execute(getRQ());
}
/**
* 小程序支付入口
* @return
*/
@OpenLabel(CS.MCH_APP_API_ENUM_V2.APPLET_PAY)
@RequestMapping(value = "/payment/ltpay")
public AccessResp ltPay(){
AccessReq rq = getRQ().setIsApplet(true);
return jsapiService.execute(rq);
}
/**
* 反扫支付
* @return
*/
@OpenLabel(CS.MCH_APP_API_ENUM_V2.MICRO_PAY)
@RequestMapping(value = "/payment/micropay")
public AccessResp microPay(){
return microService.execute(getRQ());
}
/**
* H5支付入口
* @return
*/
@OpenLabel(CS.MCH_APP_API_ENUM_V2.H5_PAY)
@RequestMapping(value = {"/payment/h5pay"})
public AccessResp h5Pay(){
return h5Service.execute(getRQ());
}
/**
* APP支付入口
* @return
*/
@OpenLabel(CS.MCH_APP_API_ENUM_V2.APP_PAY)
@RequestMapping(value = "/payment/apppay")
public AccessResp appPay(){
return appService.execute(getRQ());
}
/**
* 订单关闭
* @return
*/
@OpenLabel()
@RequestMapping(value = "/order/close")
public AccessResp close(){
return closeService.execute(getRQ());
}
// result.put("payType", "wx");
// result.put("openid", "oY6Es6Y52jNTArOpt-CTDP4ScKqs");
// result.put("payType", "alipay");
// result.put("userid", "2088502904759300");
public static void main(String[] args) {
String md5Key = "3oloC61eOXyFYLJiYom6HpvAkxYEj2ZZhaNUljSsRGLyRTuw2m77EQM2ouaK7okh3gwiYakPYFOKP2HzBKB3xp86d9RrCgOnl6D4mRVNJKWXOy8OtQpvRaEXrRMYDLY7";
String storeId = "S2404236859";
String mercOrderNo = SeqKit.genMhoOrderId();
JSONObject bizData = new JSONObject();
bizData.put("subject","测试支付1");
bizData.put("amount",3);
// bizData.put("payType","ALIPAY");
// bizData.put("isScreen",true);
// bizData.put("subAppId","wx9646c9a52da17c40");
// bizData.put("userId","oY6Es6Y52jNTArOpt-CTDP4ScKqs");
// bizData.put("userId","2088502904759300");
bizData.put("clientIp","127.0.0.1");
bizData.put("mchOrderNo",mercOrderNo);
bizData.put("body","测试支付2");
bizData.put("storeId",storeId);
bizData.put("currency","cny");
JSONObject params = new JSONObject();
params.put("reqId",System.currentTimeMillis() + "");
params.put("reqTime", DateUtil.format(new DateTime(),DatePattern.PURE_DATETIME_FORMAT));
params.put("version", "1.0");
params.put("signType", "MD5");
params.put("appId", "2081240416099412");
params.put("bizData",bizData);
String strSort = JeepayKit.getStrSort(params) + "&appSecret="+md5Key;
String sign = JeepayKit.md5(strSort, "utf-8");
params.put("sign", sign);
System.out.println(params.toJSONString());
}
/**
* 测试1
* @param args
*/
// public static void main(String[] args) {
// String md5Key = "3oloC61eOXyFYLJiYom6HpvAkxYEj2ZZhaNUljSsRGLyRTuw2m77EQM2ouaK7okh3gwiYakPYFOKP2HzBKB3xp86d9RrCgOnl6D4mRVNJKWXOy8OtQpvRaEXrRMYDLY7";
// String storeId = "S2404183032";
// String mercOrderNo = SeqKit.genMhoOrderId();
// JSONObject bizData = new JSONObject();
// bizData.put("subject","测试支付1");
// bizData.put("amount",3);
// bizData.put("payType","ALIPAY");
//// bizData.put("isScreen",true);
//// bizData.put("subAppId","wx9646c9a52da17c40");
//// bizData.put("userId","oY6Es6Y52jNTArOpt-CTDP4ScKqs");
//// bizData.put("userId","2088502904759300");
// bizData.put("clientIp","127.0.0.1");
// bizData.put("mchOrderNo",mercOrderNo);
// bizData.put("body","测试支付2");
// bizData.put("storeId",storeId);
// bizData.put("currency","cny");
// JSONObject params = new JSONObject();
// params.put("reqId",System.currentTimeMillis() + "");
// params.put("reqTime", DateUtil.format(new DateTime(),DatePattern.PURE_DATETIME_FORMAT));
// params.put("version", "1.0");
// params.put("signType", "MD5");
// params.put("appId", "2081240416099412");
// params.put("bizData",bizData);
// String strSort = JeepayKit.getStrSort(params) + "&appSecret="+md5Key;
// String sign = JeepayKit.md5(strSort, "utf-8");
// params.put("sign", sign);
// System.out.println(params.toJSONString());
// }
// public static void main(String[] args) {
// String md5Key = "O3kAhyaxYSbYhFg0CDiNflJ8eoNGpQ8Gn8m1vQKrcybE0tH8aB1WEggDT3NbRza6FItOoSDbj64WdxR0JG749c1jTTI4LQkKzZMAggPbhRB32Mf2lgnYDKJNs5sqlJLS";
// String mercOrderNo = SeqKit.genMhoOrderId();
// JSONObject bizData = new JSONObject();
// bizData.put("mchOrderNo","1231231");
// JSONObject params = new JSONObject();
// params.put("reqId",System.currentTimeMillis() + "");
// params.put("reqTime", DateUtil.format(new DateTime(),DatePattern.PURE_DATETIME_FORMAT));
// params.put("version", "1.0");
// params.put("signType", "MD5");
// params.put("appId", "65d5abb9e4b0607c72ea5eaf");
// params.put("bizData",bizData);
// String strSort = JeepayKit.getStrSort(params) + "&appSecret="+md5Key;
// String sign = JeepayKit.md5(strSort, "utf-8");
// params.put("sign", sign);
// System.out.println(params.toJSONString());
// }
}

View File

@@ -0,0 +1,993 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jeequan.jeepay.components.mq.model.PayOrderReissueMQ;
import com.jeequan.jeepay.components.mq.model.PushWxMpMsgMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.core.annotate.kits.ChannelMchIdKit;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.constants.ChannelMchIdTypeEnum;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ChannelException;
import com.jeequan.jeepay.core.interfaces.paychannel.IPaymentService;
import com.jeequan.jeepay.core.interfaces.wxmp.IWxLiteUrlLinkService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.DBApplicationConfig;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.applyment.PaywayFee;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.mchconfig.MchDivisionConfig;
import com.jeequan.jeepay.core.model.oauth2.AlipayOauth2Params;
import com.jeequan.jeepay.core.model.oauth2.Oauth2Params;
import com.jeequan.jeepay.core.model.oauth2.WxpayOauth2Params;
import com.jeequan.jeepay.core.model.params.IsvParams;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.payorder.QueryPayOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.QrCashierOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.QrCashierOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.WebCashierOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.WebCashierOrderRS;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.core.utils.StringKit;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.service.PayOrderProcessService;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.*;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/*
* 创建支付订单抽象类
*
* @author terrfly
*
* @date 2021/6/8 17:26
*/
@Slf4j
public abstract class AbstractPayOrderController extends ApiController {
@Autowired private IpLocationService ipLocationService;
@Autowired private PayOrderService payOrderService;
@Autowired private ConfigContextQueryService configContextQueryService;
@Autowired private PayOrderProcessService payOrderProcessService;
@Autowired private SysConfigService sysConfigService;
@Autowired private MchStoreService mchStoreService;
@Autowired private PayWayService payWayService;
@Autowired private IMQSender mqSender;
@Autowired private MchQrcodeCardService mchQrcodeCardService;
@Autowired private RateConfigService rateConfigService;
@Autowired private MchConfigService mchConfigService;
@Autowired private DeviceProvideConfigService deviceProvideConfigService;
@Autowired private MchStoreDeviceService mchStoreDeviceService;
@Autowired private IWxLiteUrlLinkService wxLiteUrlLinkService;
@Autowired private MchDivisionReceiverService mchDivisionReceiverService;
@Autowired private RedPacketRuleService redPacketRuleService;
@Autowired private RouteService routeService;
@Autowired private RouteManageService routeManageService;
/** 统一下单 (新建订单模式) **/
protected ApiRes unifiedOrder(String wayCode, UnifiedOrderRQ bizRQ){
return unifiedOrder(wayCode, bizRQ, null,null);
}
/** 统一下单 **/
protected ApiRes unifiedOrder(String wayCode, UnifiedOrderRQ bizRQ, PayOrder payOrder,RouteManage routeManage){
// 响应数据
UnifiedOrderRS bizRS = null;
//是否新订单模式 [ 一般接口都为新订单模式, 由于QR_CASHIER支付方式需要先 在DB插入一个新订单 导致此处需要特殊判断下。 如果已存在则直接更新,否则为插入。 ]
boolean isNewOrder = payOrder == null;
String oriPayCode = null;
try {
if(payOrder != null){ //当订单存在时,封装公共参数。
if(payOrder.getState() != PayOrder.STATE_INIT && payOrder.getState() != PayOrder.STATE_ING){
throw new BizException("订单状态异常");
}
oriPayCode = payOrder.getWayCode();
payOrder.setWayCode(wayCode); // 需要将订单更新 支付方式
PayWay payWay = payWayService.getById(wayCode);
payOrder.setWayCodeType(payWay == null ? CS.PAY_WAY_CODE_TYPE.OTHER :payWay.getWayType());
payOrder.setChannelUser(bizRQ.getChannelUserId()); //更新渠道用户信息
bizRQ.setMchNo(payOrder.getMchNo());
bizRQ.setAppId(payOrder.getAppId());
bizRQ.setMchOrderNo(payOrder.getMchOrderNo());
bizRQ.setWayCode(wayCode);
bizRQ.setAmount(payOrder.getAmount());
bizRQ.setCurrency(payOrder.getCurrency());
bizRQ.setClientIp(payOrder.getClientIp());
bizRQ.setSubject(payOrder.getSubject());
bizRQ.setNotifyUrl(payOrder.getNotifyUrl());
bizRQ.setReturnUrl(payOrder.getReturnUrl());
bizRQ.setChannelExtra(payOrder.getChannelExtra());
bizRQ.setExtParam(payOrder.getExtParam());
bizRQ.setDivisionMode(payOrder.getDivisionMode());
bizRQ.setStoreId(payOrder.getStoreId());
}
String mchNo = bizRQ.getMchNo();
String appId = bizRQ.getAppId();
// 只有新订单模式,进行校验
if(isNewOrder && payOrderService.count(PayOrder.gw().eq(PayOrder::getMchNo, mchNo).eq(PayOrder::getMchOrderNo, bizRQ.getMchOrderNo())) > 0){
throw new BizException("商户订单["+bizRQ.getMchOrderNo()+"]已存在");
}
if(StringUtils.isNotEmpty(bizRQ.getNotifyUrl()) && !StringKit.isAvailableUrl(bizRQ.getNotifyUrl())){
throw new BizException("异步通知地址协议仅支持http:// 或 https:// !");
}
if(StringUtils.isNotEmpty(bizRQ.getReturnUrl()) && !StringKit.isAvailableUrl(bizRQ.getReturnUrl())){
throw new BizException("同步通知地址协议仅支持http:// 或 https:// !");
}
// TODO 新版直接取商户号 获取门店数据
MchStore mchStore = null;
if(bizRQ.getStoreId() != null){
mchStore = mchStoreService.getById(bizRQ.getStoreId());
}else{
mchStore = mchStoreService.queryDefaultStore(bizRQ.getMchNo());
}
if(mchStore == null || StringUtils.isEmpty(mchStore.getMchApplyId())){
throw new BizException("门店信息异常或门店关联商户信息异常");
}
if(StringUtils.isBlank(bizRQ.getSubject())){
bizRQ.setSubject(mchStore.getStoreName());
}
if(StringUtils.isBlank(bizRQ.getBody())){
bizRQ.setBody(mchStore.getStoreName());
}
//获取支付参数 (缓存数据) 和 商户信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(mchNo, appId,mchStore.getMchApplyId());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
PaywayFee d0PaywayFee = null;
if(CS.SETTLEMENT_TYPE.D0.equals(mchAppConfigContext.getMchApplyment().getSettlementType())){
d0PaywayFee = rateConfigService.queryCreateOrderPaywayFee(mchAppConfigContext.getMchApplyment().getApplyId(), mchAppConfigContext.getMchApplyment().getIsvNo(), mchAppConfigContext.getMchApplyment().getAutoConfigMchAppId(), mchAppConfigContext.getMchApplyment().getIfCode(), CS.PAY_WAY_CODE.D0);
if(d0PaywayFee == null){
throw new BizException("当前商户为D0商户D0费率未配置生效");
}
}
if(bizRQ.getQrcId() != null){
MchQrcodeCard mchQrcodeCard = mchQrcodeCardService.getById(bizRQ.getQrcId());
if(mchQrcodeCard == null){
throw new BizException("商户码牌不存在!");
}
if(mchQrcodeCard.getBindState() == CS.NO || !bizRQ.getMchNo().equals(mchQrcodeCard.getMchNo())){
throw new BizException("码牌所属商户不匹配!");
}
}
//收银台支付并且只有新订单需要走这里, 收银台二次下单的wayCode应该为实际支付方式。
if(isNewOrder && CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)){
return qrCashierPayWay(bizRQ, mchAppConfigContext, null,routeManage);
}
//web统一收银台支付并且只有新订单需要走这里
if(isNewOrder && CS.PAY_WAY_CODE.WEB_CASHIER.equals(wayCode)){
return webCashierPayWay(bizRQ, mchAppConfigContext,routeManage);
}
// 根据支付方式, 查询出 该商户 可用的支付接口
// MchPayPassage mchPayPassage = mchPayPassageService.findMchPayPassage(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), wayCode);
// if(mchPayPassage == null){
// throw new BizException("商户应用不支持该支付方式");
// }
//获取支付接口
IPaymentService paymentService = checkMchWayCodeAndGetService(mchAppConfigContext, mchAppConfigContext.getMchApplyment().getIfCode());
// 支付接口克隆改造2323-05-15支持服务商对同一通道配置多个支付接口
String cloneIfCode = mchAppConfigContext.getMchApplyment().getIfCode();
// 校验终端设备
checkDeviceParams(bizRQ, cloneIfCode);
// 查询渠道服务商号和渠道子商户号
String[] channelMchNoArr = getChannelMchNo(mchAppConfigContext);
// 查询费率
PaywayFee paywayFee = rateConfigService.queryCreateOrderPaywayFee(mchAppConfigContext.getMchApplyment().getApplyId(), mchAppConfigContext.getMchApplyment().getIsvNo(), mchAppConfigContext.getMchApplyment().getAutoConfigMchAppId(), mchAppConfigContext.getMchApplyment().getIfCode(), wayCode);
if(paywayFee == null){
throw new BizException("商户费率未配置");
}
//生成订单
if(isNewOrder){
payOrder = genPayOrder(bizRQ, mchAppConfigContext, paywayFee, channelMchNoArr,d0PaywayFee,routeManage);
}else{
payOrder.setIfCode(cloneIfCode);
// 查询支付方式的费率,并 在更新ing时更新费率信息
MutablePair<String, Long> feeAndSnapshot = paywayFee.calFeeAndSnapshot(payOrder.getFindAmt());
payOrder.setMchFeeRate(feeAndSnapshot.left);
//TODO 银盛特殊性 保底收 0.01
if(CS.IF_CODE.YSPAY.equals(payOrder.getIfCode())){
feeAndSnapshot.right = feeAndSnapshot.right == BigDecimal.ZERO.longValue() ? BigDecimal.ONE.longValue() : feeAndSnapshot.right;
}
payOrder.setMchFeeAmount(feeAndSnapshot.right); //商户手续费,单位分
payOrder.setMchOrderFeeAmount(feeAndSnapshot.right); //商户手续费,单位分
// 服务商机构号或子商户号
payOrder.setChannelIsvNo(channelMchNoArr[0]);
payOrder.setChannelMchNo(channelMchNoArr[1]);
if(d0PaywayFee != null && CS.SETTLEMENT_TYPE.D0.equals(mchAppConfigContext.getMchApplyment().getSettlementType())){
MutablePair<String, Long> feeD0 = d0PaywayFee.calFeeAndSnapshot(payOrder.getFindAmt());
payOrder.setCashRate(d0PaywayFee.getFeeRate().multiply(BigDecimal.valueOf(100)));
Long cashFee = feeD0.right == 0 ? BigDecimal.ONE.longValue() : feeD0.right;
payOrder.setCashFee(cashFee); //商户手续费,单位分
//拉卡拉D0 除了支付宝或者微信以外银联交易都是D1 或者小于10元都是D1
if(CS.IF_CODE.LKLSPAY.equals(payOrder.getIfCode()) && (!CS.PAY_WAY_CODE_TYPE.WECHAT.equals(payOrder.getWayCodeType()) && !CS.PAY_WAY_CODE_TYPE.ALIPAY.equals(payOrder.getWayCodeType())) || payOrder.getFindAmt().longValue() < 10){
payOrder.setCashFee(0L);
payOrder.setCashRate(BigDecimal.ZERO);
}
}
// 自动分账模式判断 A : 与 B对应
// 自动分账模式: 判断是否有 分账用户。 若没有分账用户: 则分账模式为关闭。 ( 解决 斗拱接口,没有分账用户无法调用完结导致资金冻结的问题 )
// 注意: 此处为: 聚合码下单后, 第二次调用下单接口的场景。
if(payOrder.getDivisionMode() != null && payOrder.getDivisionMode() == PayOrder.DIVISION_MODE_AUTO && cloneIfCode != null){
// 无 自动分账组用户
if(mchDivisionReceiverService.getBaseMapper().countByAutoDivisionFlag(payOrder.getMchNo(), payOrder.getAppId(), payOrder.getIfCode()) <= 0){
payOrder.setDivisionMode(PayOrder.DIVISION_MODE_FORBID);
bizRQ.setDivisionMode(PayOrder.DIVISION_MODE_FORBID); // 修改 req对象。
}
}
}
if(!bizRQ.getIsRoute()){
//预先校验
String errMsg = paymentService.preCheck(bizRQ, payOrder);
if(StringUtils.isNotEmpty(errMsg)){
throw new BizException(errMsg);
}
ipLocationService.preCheck(payOrder.getClientIp(),mchAppConfigContext.getMchApplyment());
}
// ipLocationService.preCheck(payOrder.getClientIp(),mchAppConfigContext.getMchApplyment());
// String newPayOrderId = paymentService.customPayOrderId(bizRQ, payOrder, mchAppConfigContext);
if(isNewOrder){
//订单入库 订单状态: 生成状态 此时没有和任何上游渠道产生交互。
payOrderService.save(payOrder);
}
// else{
//
// // TODO 系统生成的二维码聚合支付方式QR_CASHIER模式商户不应该通过payOrderId来查单 此时订单号有可能更改!
// if(StringUtils.isNotBlank(newPayOrderId)){ // 自定义订单号, 更新原订单号
// payOrderService.update(new LambdaUpdateWrapper<PayOrder>().set(PayOrder::getPayOrderId, newPayOrderId).eq(PayOrder::getPayOrderId, payOrder.getPayOrderId()));
// payOrder.setPayOrderId(newPayOrderId);
// }
// }
//如果是路由
if(bizRQ.getIsRoute() && isNewOrder){
bizRS = new UnifiedOrderRS();
/*if(routeManage.getIsSend()){
mqSender.send(PushWxMpMsgMQ.build(payOrder.getPayOrderId(),PushWxMpMsgMQ.ROUTE_NOTICE));
}*/
routeManageService.upRouteData(routeManage,payOrder);
return packageApiResByPayOrder(bizRQ, bizRS, payOrder);
}
if(!isNewOrder && StringUtils.isNotEmpty(payOrder.getPayData())){
if(payOrder.getWayCode().equals(oriPayCode)){
bizRS = new UnifiedOrderRS();
bizRS.setPayOrderId(payOrder.getPayOrderId());
bizRS.setPayData(payOrder.getPayData());
bizRS.setChannelRetMsg(ChannelRetMsg.waiting());
bizRS.setMchOrderNo(payOrder.getMchOrderNo());
bizRS.setOrderState(payOrder.getState());
}else{
throw new BizException("当前订单号已被使用,请重新下单");
}
}else{
//调起上游支付接口
bizRS = (UnifiedOrderRS) paymentService.pay(bizRQ, payOrder, mchAppConfigContext);
}
//处理上游返回数据
this.processChannelMsg(bizRS,bizRS.getChannelRetMsg(), payOrder);
return packageApiResByPayOrder(bizRQ, bizRS, payOrder);
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (ChannelException e) {
//处理上游返回数据
this.processChannelMsg(null,e.getChannelRetMsg(), payOrder);
if(e.getChannelRetMsg().getChannelState() == ChannelRetMsg.ChannelState.SYS_ERROR ){
return ApiRes.customFail(e.getMessage());
}
return this.packageApiResByPayOrder(bizRQ, bizRS, payOrder);
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
/**
* 构建订单
* @param rq
* @param mchAppConfigContext
* @param paywayFee
* @param channelMchNoArr
* @param d0PaywayFee
* @return
*/
public PayOrder genPayOrder(UnifiedOrderRQ rq, MchAppConfigContext mchAppConfigContext, PaywayFee paywayFee, String[] channelMchNoArr,PaywayFee d0PaywayFee,RouteManage routeManage){
MchInfo mchInfo = mchAppConfigContext.getMchInfo();
MchApp mchApp = mchAppConfigContext.getMchApp();
PayOrder payOrder = new PayOrder();
payOrder.setPayOrderId(SeqKit.genPayOrderId()); //生成订单ID
payOrder.setMchNo(mchInfo.getMchNo()); //商户号
payOrder.setEpUserId(mchInfo.getCreatedUid()); // 商户拓展员
payOrder.setIsvNo(mchInfo.getIsvNo()); //服务商号
payOrder.setMchName(mchAppConfigContext.getMchApplyment().getMchShortName()); //商户名称(简称)
payOrder.setMchType(mchInfo.getType()); //商户类型
payOrder.setMchOrderNo(rq.getMchOrderNo()); //商户订单号
payOrder.setAppId(mchApp.getAppId()); //商户应用appId
payOrder.setIfCode(mchAppConfigContext.getMchApplyment().getIfCode()); //接口代码
payOrder.setWayCode(rq.getWayCode()); //支付方式
payOrder.setAgentNo(mchInfo.getAgentNo()); // 服务商ID
payOrder.setTopAgentNo(mchInfo.getTopAgentNo()); // 上级服务商ID
payOrder.setSettleType(mchAppConfigContext.getMchApplyment().getSettlementType()); //结算类型
PayWay payWay = payWayService.getById(rq.getWayCode());
payOrder.setWayCodeType(payWay == null ? CS.PAY_WAY_CODE_TYPE.OTHER :payWay.getWayType());
payOrder.setAmount(rq.getAmount()); //订单金额
payOrder.setFindAmt(rq.getAmount());
MchStore mchStore = null;
if(rq.getStoreId() != null){
mchStore = mchStoreService.getById(rq.getStoreId()); // 查询参数门店
}else{
mchStore = mchStoreService.queryDefaultStore(rq.getMchNo()); //查询默认门店
}
// if(mchStore == null || !mchStore.getMchNo().equals(rq.getMchNo())){
// throw new BizException("门店不存在");
// }
payOrder.setStoreId(mchStore.getStoreId()); // 门店ID
payOrder.setStoreName(mchStore.getStoreName()); // 门店名称
payOrder.setLng(mchStore.getLng()); // 经度
payOrder.setLat(mchStore.getLat()); // 纬度
payOrder.setAddress(mchStore.getAddress()); // 详细地址
payOrder.setStoreUserId(rq.getStoreUserId()); // 门店收银员ID
payOrder.setMchExtNo(mchStore.getMchApplyId());
if (StringUtils.isNotBlank(rq.getDeviceInfo())) {
UnifiedOrderRQ.DeviceInfo deviceInfo = JSON.parseObject(rq.getDeviceInfo(), UnifiedOrderRQ.DeviceInfo.class);
payOrder.setDeviceType(deviceInfo.getDeviceType()); // 设备类型
payOrder.setDeviceNo(deviceInfo.getDeviceNo()); // 设备号
payOrder.setDeviceProvider(deviceInfo.getProvider()); // 设备厂商
}
if (rq.getQrcId() != null) {
payOrder.setQrcId(rq.getQrcId()); // 码牌ID
payOrder.setDeviceType(PayOrder.DEVICE_TYPE_QR_CODE); // 码牌类型
payOrder.setDeviceNo(String.valueOf(rq.getQrcId()));
if(StringUtils.isEmpty(mchStore.getMchApplyId())){
throw new BizException("门店关联的商户信息异常");
}
payOrder.setMchExtNo(mchStore.getMchApplyId());
}
//判断门店是否开通了营销规则
boolean isRule = redPacketRuleService.isCheckStoreRedRule(payOrder.getMchNo(), payOrder.getStoreId());
if(paywayFee != null && !isRule){
MutablePair<String, Long> feeAndSnapshot = paywayFee.calFeeAndSnapshot(payOrder.getFindAmt());
payOrder.setMchFeeRate(feeAndSnapshot.left);
//TODO 银盛特殊性 保底收 0.01
if(CS.IF_CODE.YSPAY.equals(payOrder.getIfCode())){
feeAndSnapshot.right = feeAndSnapshot.right == BigDecimal.ZERO.longValue() ? BigDecimal.ONE.longValue() : BigDecimal.ZERO.longValue();
}
payOrder.setMchFeeAmount(feeAndSnapshot.right); //商户手续费,单位分
payOrder.setMchOrderFeeAmount(feeAndSnapshot.right); //商户手续费,单位分
}else{
payOrder.setMchFeeRate(""); //预下单模式, 按照0计算入库 后续进行更新
payOrder.setMchFeeAmount(0L); //商户手续费,单位分
payOrder.setMchOrderFeeAmount(0L); //商户手续费,单位分
}
if(d0PaywayFee != null && CS.SETTLEMENT_TYPE.D0.equals(mchAppConfigContext.getMchApplyment().getSettlementType())){
MutablePair<String, Long> feeAndSnapshot = d0PaywayFee.calFeeAndSnapshot(payOrder.getFindAmt());
payOrder.setCashRate(d0PaywayFee.getFeeRate().multiply(BigDecimal.valueOf(100)));
Long cashFee = feeAndSnapshot.right;
if(cashFee == 0){
cashFee = BigDecimal.ONE.longValue();
}
payOrder.setCashFee(cashFee); //商户手续费,单位分
//拉卡拉D0 除了支付宝或者微信以外银联交易都是D1 或者小于10元都是D1
if(CS.IF_CODE.LKLSPAY.equals(payOrder.getIfCode()) && (!CS.PAY_WAY_CODE_TYPE.WECHAT.equals(payOrder.getWayCodeType()) && !CS.PAY_WAY_CODE_TYPE.ALIPAY.equals(payOrder.getWayCodeType())) || payOrder.getFindAmt().longValue() < 10){
payOrder.setCashFee(0L);
payOrder.setCashRate(BigDecimal.ZERO);
}
}
payOrder.setCurrency(rq.getCurrency()); //币种
payOrder.setState(PayOrder.STATE_INIT); //订单状态, 默认订单生成状态
payOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(), getClientIp())); //客户端IP
payOrder.setSubject(rq.getSubject()); //商品标题
payOrder.setBody(rq.getBody()); //商品描述信息
payOrder.setChannelBizData(rq.getChannelBizData()); // 渠道业务参数
// payOrder.setChannelExtra(rq.getChannelExtra()); //特殊渠道发起的附件额外参数, 是否应该删除该字段了?? 比如authCode不应该记录 只是在传输阶段存在的吧? 之前的为了在payOrder对象需要传参。
payOrder.setChannelUser(rq.getChannelUserId()); //渠道用户标志
payOrder.setExtParam(rq.getExtParam()); //商户扩展参数
payOrder.setNotifyUrl(rq.getNotifyUrl()); //异步通知地址
payOrder.setReturnUrl(rq.getReturnUrl()); //页面跳转地址
payOrder.setSellerRemark(rq.getSellerRemark()); // 卖家地址
payOrder.setBuyerRemark(rq.getBuyerRemark()); // 买家地址
// 服务商机构号或子商户号
if (channelMchNoArr != null) {
payOrder.setChannelIsvNo(channelMchNoArr[0]);
payOrder.setChannelMchNo(channelMchNoArr[1]);
}
if(rq.getIsMarketRed() != null && rq.getIsMarketRed()){
payOrder.setDivisionMode(PayOrder.MAKET_RED_PACKET_MANUAL);
}else{
// 分账模式
payOrder.setDivisionMode(ObjectUtils.defaultIfNull(rq.getDivisionMode(), PayOrder.DIVISION_MODE_FORBID));
// 订单不是自动分账模式(如果自动分账, 那么就无需查询DB了)
if(payOrder.getDivisionMode() != PayOrder.DIVISION_MODE_AUTO){
// && 当前商户配置模式为: 全局自动分账 && 金额满足
MchDivisionConfig mchDivisionConfig = mchConfigService.queryMchDivisionConfigByDefault(payOrder.getMchNo());
if(mchDivisionConfig.getOverrideAutoFlag() == CS.YES && payOrder.getAmount() >= mchDivisionConfig.getAutoDivisionRules().getAmountLimit()){
payOrder.setDivisionMode(PayOrder.DIVISION_MODE_AUTO);
}
}
// 自动分账模式判断 B : 与 A对应
// 自动分账模式: 判断是否有 分账用户。 若没有分账用户: 则分账模式为关闭。 ( 解决 斗拱接口,没有分账用户无法调用完结导致资金冻结的问题 )
// 注意: 聚合码此时没有ifCode, 判断不出来是否包含分账接收方。
// 此处判断的为: 直接下单的场景, 比如 微信二维码、 支付宝二维码、 被扫等场景。
if(payOrder.getDivisionMode() != null && payOrder.getDivisionMode() == PayOrder.DIVISION_MODE_AUTO && mchAppConfigContext.getMchApplyment().getIfCode() != null){
// 无 自动分账组用户
if(mchDivisionReceiverService.getBaseMapper().countByAutoDivisionFlag(payOrder.getMchNo(), payOrder.getAppId(), payOrder.getIfCode()) <= 0){
payOrder.setDivisionMode(PayOrder.DIVISION_MODE_FORBID);
}
}
}
Date nowDate = new Date();
//订单过期时间 单位: 秒
if(rq.getExpiredTime() != null){
payOrder.setExpiredTime(DateUtil.offsetSecond(nowDate, rq.getExpiredTime()));
}else{
payOrder.setExpiredTime(DateUtil.offsetHour(nowDate, 2)); //订单过期时间 默认两个小时
}
// 会员信息
if (StringUtils.isNotBlank(rq.getMbrId())) {
payOrder.setMbrId(rq.getMbrId());
}
if (StringUtils.isNotBlank(rq.getMbrTel())) {
payOrder.setMbrTel(rq.getMbrTel());
}
payOrder.setCreatedAt(nowDate); //订单创建时间
// 设置优惠
JSONObject discount = rq.getDiscount();
if(discount != null){
Long discountAmt = discount.getLong("discountAmt") == null ? BigDecimal.ZERO.longValue() : discount.getLong("discountAmt");
payOrder.setFindAmt(payOrder.getAmount() - discountAmt);
payOrder.setDiscountAmt(discountAmt);
payOrder.setDiscountScale(discount.getBigDecimal("discountScale"));
}
if(routeManage != null){
payOrder.setRouteId(routeManage.getRouteId());
}
return payOrder;
}
/**
* 校验: 商户的支付方式是否可用
* 返回: 支付接口
* **/
private IPaymentService checkMchWayCodeAndGetService(MchAppConfigContext mchAppConfigContext, String ifCode){
IPaymentService paymentService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "PaymentService", IPaymentService.class);
if(paymentService == null){
throw new BizException("无此支付通道接口");
}
if(mchAppConfigContext.getMchType() == MchInfo.TYPE_NORMAL){ //普通商户
if(configContextQueryService.queryNormalMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode) == null){
throw new BizException("商户应用参数未配置");
}
}else if(mchAppConfigContext.getMchType() == MchInfo.TYPE_ISVSUB){ //特约商户
// if(configContextQueryService.queryIsvsubMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode) == null){
// throw new BizException("特约商户参数未配置");
// }
if (configContextQueryService.queryIsvParams(mchAppConfigContext.getMchApplyment().getIsvNo(), ifCode) == null) {
throw new BizException("服务商参数未配置");
}
}
return paymentService;
}
/** 处理返回的渠道信息,并更新订单状态
* payOrder将对部分信息进行 赋值操作。
* **/
protected void processChannelMsg(UnifiedOrderRS urs,ChannelRetMsg channelRetMsg, PayOrder payOrder){
//对象为空 || 上游返回状态为空, 则无需操作
if(channelRetMsg == null || channelRetMsg.getChannelState() == null){
return ;
}
String payOrderId = payOrder.getPayOrderId();
//明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == channelRetMsg.getChannelState()) {
this.updateInitOrderStateThrowException(PayOrder.STATE_SUCCESS, payOrder, urs);
//订单支付成功,其他业务逻辑
payOrderProcessService.confirmSuccess(payOrder, null);
//明确失败
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == channelRetMsg.getChannelState()) {
this.updateInitOrderStateThrowException(PayOrder.STATE_FAIL, payOrder, urs);
// 上游处理中 || 未知 || 上游接口返回异常 订单为支付中状态
}else if( ChannelRetMsg.ChannelState.WAITING == channelRetMsg.getChannelState() ||
ChannelRetMsg.ChannelState.UNKNOWN == channelRetMsg.getChannelState() ||
ChannelRetMsg.ChannelState.API_RET_ERROR == channelRetMsg.getChannelState()
){
this.updateInitOrderStateThrowException(PayOrder.STATE_ING, payOrder, urs);
// 系统异常: 订单不再处理。 为: 生成状态
}else if( ChannelRetMsg.ChannelState.SYS_ERROR == channelRetMsg.getChannelState()){
}else{
throw new BizException("ChannelState 返回异常!");
}
//判断是否需要轮询查单
if(channelRetMsg.isNeedQuery()){
mqSender.send(PayOrderReissueMQ.build(payOrderId, 1), 5);
}
}
/** 更新订单状态 --》 订单生成--》 其他状态 (向外抛出异常) **/
private void updateInitOrderStateThrowException(byte orderState, PayOrder payOrder, UnifiedOrderRS urs){
if(payOrder.getState() == PayOrder.STATE_ING){
return ;
}
payOrder.setState(orderState);
boolean isSuccess = payOrderService.updateInit2Ing(payOrder.getPayOrderId(), payOrder);
if(!isSuccess){
throw new BizException("更新订单异常!");
}
isSuccess = payOrderService.updateIng2SuccessOrFail(payOrder.getPayOrderId(), payOrder.getState(),urs.getChannelRetMsg());
if(!isSuccess){
throw new BizException("更新订单异常!");
}
}
/** 统一封装订单数据 **/
private ApiRes packageApiResByPayOrder(UnifiedOrderRQ bizRQ, UnifiedOrderRS bizRS, PayOrder payOrder){
// 返回接口数据
bizRS.setPayOrderId(payOrder.getPayOrderId());
bizRS.setOrderState(payOrder.getState());
bizRS.setMchOrderNo(payOrder.getMchOrderNo());
bizRS.setStoreName(payOrder.getStoreName());
if(bizRQ.getIsRoute()){
String tk = sysConfigService.genQrToken(QRCodeParams.TYPE_PAY_ORDER, payOrder.getPayOrderId(), null);
bizRS.setPayData(tk);
Route route = routeService.preCheck(payOrder.getRouteId());
JSONObject extData = new JSONObject();
extData.put("payMethod",route.getPayMethod());
if(payOrder.getWayCode().contains("WX")){
WxpayOauth2Params oauth2Params = (WxpayOauth2Params)configContextQueryService.queryIsvOauth2Params(payOrder.getIsvNo(), CS.IF_CODE.WXPAY);
extData.put("appid",oauth2Params.getAppId());
if(Route.LITE == route.getPayMethod()){
extData.put("liteAppid",oauth2Params.getLiteAppId());
}
}else{
AlipayOauth2Params oauth2Params = (AlipayOauth2Params)configContextQueryService.queryIsvOauth2Params(payOrder.getIsvNo(), CS.IF_CODE.ALIPAY);
extData.put("appid",oauth2Params.getAppId());
if(Route.LITE == route.getPayMethod()){
AlipayOauth2Params liteParams = oauth2Params.getLiteParams();
extData.put("liteAppid",liteParams.getAppId());
}
}
bizRS.setExtData(extData);
}
if(payOrder.getState() == PayOrder.STATE_FAIL){
bizRS.setErrCode(bizRS.getChannelRetMsg() != null ? bizRS.getChannelRetMsg().getChannelErrCode() : null);
bizRS.setErrMsg(bizRS.getChannelRetMsg() != null ? bizRS.getChannelRetMsg().getChannelErrMsg() : null);
}
// 支付成功, 返回订单数据给接口调用者
if(payOrder.getState() == PayOrder.STATE_SUCCESS){
QueryPayOrderRS queryPayOrderRS = QueryPayOrderRS.buildByPayOrder(payOrderService.getById(payOrder.getPayOrderId()));
JSONObject jsonObject = (JSONObject) JSON.toJSON(queryPayOrderRS);
// 查询扩展参数
MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(payOrder.getMchNo(), "payOrderNotifyExtParams");
List<String> extParams = new ArrayList<>();
if(mchConfig != null && StringUtils.isNotBlank(mchConfig.getConfigVal())){
extParams = JSON.parseArray(mchConfig.getConfigVal(), String.class);
}
for (String key : QueryPayOrderRS.EXT_PRAMS) {
if(!extParams.contains(key)){
jsonObject.remove(key);
}
}
bizRS.setPayOrderInfo(jsonObject.toJSONString());
}
return ApiResKit.okWithSign(bizRS, configContextQueryService.queryMchApp(bizRQ.getMchNo(), bizRQ.getAppId()), bizRQ.getSignType());
}
/** 聚合码支付方式
*
* dbPayOrderId : null, 表示需要新增save订单 否则无需save直接查库 web收银台 H5跳小程序方式
*
* */
public ApiRes qrCashierPayWay(UnifiedOrderRQ bizRQ,MchAppConfigContext mchAppConfigContext, String dbPayOrderId,RouteManage routeManage) {
QrCashierOrderRQ qrCashierOrderRQ = (QrCashierOrderRQ)bizRQ;
if(StringUtils.isNotEmpty(qrCashierOrderRQ.getEntryPageType())
&& !QRCodeParams.ENTRY_PAGE_H5.equals(qrCashierOrderRQ.getEntryPageType())
&& !QRCodeParams.ENTRY_PAGE_LITE.equals(qrCashierOrderRQ.getEntryPageType())){
throw new BizException("聚合码入口参数有误! ");
}
// 判断商户是否拥有 可用通道
// int passageCount = mchPayPassageService.count(MchPayPassage.gw().eq(MchPayPassage::getAppId, mchApp.getAppId()).eq(MchPayPassage::getState, CS.YES));
// if(passageCount <= 0){
// throw new BizException("当前商户未开通任何支付通道!");
// }
String payOrderId = dbPayOrderId;
PayOrder payOrder = null;
if(StringUtils.isEmpty(dbPayOrderId)){
//生成订单
String[] channelMchNoArr = getChannelMchNo(mchAppConfigContext);
payOrder = genPayOrder(bizRQ, mchAppConfigContext, null, channelMchNoArr,null,routeManage);
payOrderId = payOrder.getPayOrderId();
//订单入库 订单状态: 生成状态 此时没有和任何上游渠道产生交互。
payOrderService.save(payOrder);
}else{
payOrder = payOrderService.getById(payOrderId);
}
QrCashierOrderRS qrCashierOrderRS = new QrCashierOrderRS();
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
String payUrl = dbApplicationConfig.genUniJsapiPayUrl(QRCodeParams.TYPE_PAY_ORDER, StringUtils.defaultIfEmpty(qrCashierOrderRQ.getEntryPageType(), QRCodeParams.ENTRY_PAGE_H5), payOrderId, payOrder.getIsvNo());
if(CS.PAY_DATA_TYPE.CODE_IMG_URL.equals(qrCashierOrderRQ.getPayDataType())){ //二维码图片地址
qrCashierOrderRS.setCodeImgUrl(dbApplicationConfig.genScanImgUrl(payUrl));
}else{ //默认都为跳转地址方式
// TODO Dingzhiwei 临时解决(待手机端支持后删除) 为了兼容之前通过payDataType支持app打开小程序支付功能
if(StringUtils.isEmpty(qrCashierOrderRQ.getEntryLiteType()) &&
(CS.PAY_DATA_TYPE.ALI_APP.equals(qrCashierOrderRQ.getPayDataType()) || CS.PAY_DATA_TYPE.WX_APP.equals(qrCashierOrderRQ.getPayDataType()))){
qrCashierOrderRQ.setEntryLiteType(qrCashierOrderRQ.getPayDataType());
}
if(StringUtils.isNotEmpty(qrCashierOrderRQ.getEntryLiteType())){
if(QRCodeParams.ENTRY_LITE_WXAPP.equals(qrCashierOrderRQ.getEntryLiteType())){
JSONObject liteObject = new JSONObject();
WxpayOauth2Params wxpayOauth2Params = null;
if(mchAppConfigContext.isIsvsubMch()){ // 特约商户
// 先获取 特约商户的 小程序 配置信息
wxpayOauth2Params = (WxpayOauth2Params) configContextQueryService.queryNormalOauth2Params(mchAppConfigContext.getMchApplyment().getIsvNo(), payOrder.getAppId(), CS.IF_CODE.WXPAY);
// 商户未配置 || 使用服务商的小程序时 查询服务商的配置信息
if(wxpayOauth2Params == null || wxpayOauth2Params.getIsUseSubmchAccount() == null || wxpayOauth2Params.getIsUseSubmchAccount() == CS.NO){
// 查询服务商的配置, 判断取哪个 配置条目。
// 查询商户 配置 的oauth2配置 ( 支付参数配置 ) selectOauth2InfoId: 配置的oauth2参数
String ifCodeByMchPassage = configContextQueryService.queryIfCodeByPageType(mchAppConfigContext, QRCodeParams.PAGE_TYPE_WECHAT_LITE);
String selectOauth2InfoId = configContextQueryService.queryAgentOrIsvSelectOauth2InfoId(mchAppConfigContext.getMchApplyment(), ifCodeByMchPassage);
wxpayOauth2Params = (WxpayOauth2Params) configContextQueryService.
queryIsvOauth2ParamsByAutoSelectOauth2InfoId(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.WXPAY, selectOauth2InfoId);
}
}else{
wxpayOauth2Params = (WxpayOauth2Params) configContextQueryService.queryNormalOauth2Params(payOrder.getIsvNo(), payOrder.getAppId(), CS.IF_CODE.WXPAY);
}
if(wxpayOauth2Params != null){
liteObject.put("appid", wxpayOauth2Params.getLiteAppId()); // 小程序的appId
liteObject.put("ghid", wxpayOauth2Params.getLiteGhid()); // 小程序原始ID
liteObject.put("path", wxpayOauth2Params.getLitePagePath()); // 小程序打开页面
liteObject.put("env", wxpayOauth2Params.getLiteEnv()); // 小程序版本
liteObject.put("qrUrl", payUrl); // 聚合码支付URL
}
payUrl = liteObject.toJSONString();
}
//静态托管H5
if(QRCodeParams.ENTRY_LITE_WX_TG_H5.equals(qrCashierOrderRQ.getEntryLiteType())){
JSONObject liteObject = new JSONObject();
WxpayOauth2Params wxpayOauth2Params = null;
if(mchAppConfigContext.isIsvsubMch()){ // 特约商户
// 先获取 特约商户的 小程序 配置信息
wxpayOauth2Params = (WxpayOauth2Params) configContextQueryService.queryNormalOauth2Params(payOrder.getIsvNo(), payOrder.getAppId(), CS.IF_CODE.WXPAY);
// 商户未配置 || 使用服务商的小程序时 查询服务商的配置信息
if(wxpayOauth2Params == null || wxpayOauth2Params.getIsUseSubmchAccount() == null || wxpayOauth2Params.getIsUseSubmchAccount() == CS.NO){
// 查询服务商的配置, 判断取哪个 配置条目。
// 查询商户 配置 的oauth2配置 ( 支付参数配置 ) selectOauth2InfoId: 配置的oauth2参数
String ifCodeByMchPassage = configContextQueryService.queryIfCodeByPageType(mchAppConfigContext, QRCodeParams.PAGE_TYPE_WECHAT_LITE);
String selectOauth2InfoId = configContextQueryService.queryAgentOrIsvSelectOauth2InfoId(mchAppConfigContext.getMchApplyment(), ifCodeByMchPassage);
wxpayOauth2Params = (WxpayOauth2Params) configContextQueryService.
queryIsvOauth2ParamsByAutoSelectOauth2InfoId(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.WXPAY, selectOauth2InfoId);
}
}else{
wxpayOauth2Params = (WxpayOauth2Params) configContextQueryService.queryNormalOauth2Params(payOrder.getIsvNo(), payOrder.getAppId(), CS.IF_CODE.WXPAY);
}
if(wxpayOauth2Params != null){
payUrl = WxpayOauth2Params.WX_TG_PRIFEX + dbApplicationConfig.genQrToken(QRCodeParams.TYPE_PAY_ORDER,payOrderId,null);
liteObject.put("appid", wxpayOauth2Params.getLiteAppId()); // 小程序的appId
liteObject.put("ghid", wxpayOauth2Params.getLiteGhid()); // 小程序原始ID
liteObject.put("path", wxpayOauth2Params.getLitePagePath()); // 小程序打开页面
liteObject.put("env", wxpayOauth2Params.getLiteEnv()); // 小程序版本
liteObject.put("payUrl", payUrl);
}
payUrl = liteObject.toJSONString();
}else if(QRCodeParams.ENTRY_LITE_ALIAPP.equals(qrCashierOrderRQ.getEntryLiteType())) {
JSONObject liteObject = new JSONObject();
AlipayOauth2Params alipayOauth2Params = null;
if(mchAppConfigContext.isIsvsubMch()){ // 特约商户
// 先获取 特约商户的 小程序 配置信息
alipayOauth2Params = (AlipayOauth2Params) configContextQueryService.queryNormalOauth2Params(payOrder.getIsvNo(), payOrder.getAppId(), CS.IF_CODE.ALIPAY);
// 商户未配置 || 使用服务商的小程序时 查询服务商的配置信息
if(alipayOauth2Params == null || alipayOauth2Params.getIsUseSubmchAccount() == null || alipayOauth2Params.getIsUseSubmchAccount() == CS.NO){
// 查询服务商的配置, 判断取哪个 配置条目。
// 查询商户 配置 的oauth2配置 ( 支付参数配置 ) selectOauth2InfoId: 配置的oauth2参数
String ifCodeByMchPassage = configContextQueryService.queryIfCodeByPageType(mchAppConfigContext, QRCodeParams.PAGE_TYPE_ALIPAY_LITE);
String selectOauth2InfoId = configContextQueryService.queryAgentOrIsvSelectOauth2InfoId(mchAppConfigContext.getMchApplyment(), ifCodeByMchPassage);
alipayOauth2Params = (AlipayOauth2Params) configContextQueryService.
queryIsvOauth2ParamsByAutoSelectOauth2InfoId(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.ALIPAY, selectOauth2InfoId);
}
}else{
alipayOauth2Params = (AlipayOauth2Params) configContextQueryService.queryNormalOauth2Params(payOrder.getIsvNo(), payOrder.getAppId(), CS.IF_CODE.ALIPAY);
}
if(alipayOauth2Params != null && alipayOauth2Params.getLiteParams() != null ){
liteObject.put("appId", alipayOauth2Params.getLiteParams().getAppId());// 小程appId
liteObject.put("path", alipayOauth2Params.getLiteParams().getPagePath()); // 小程序路径
liteObject.put("qrUrl", payUrl); // 聚合码支付URL
}
payUrl = liteObject.toJSONString();
}else if(QRCodeParams.ENTRY_LITE_WXH5.equals(qrCashierOrderRQ.getEntryLiteType())) {
// 调用微信的接口得到转入小程序的url
payUrl = wxLiteUrlLinkService.generateUrlLink(mchAppConfigContext, "q=" + URLUtil.encodeAll(payUrl), payOrder.getExpiredTime().getTime());
}else if(QRCodeParams.ENTRY_LITE_ALIH5.equals(qrCashierOrderRQ.getEntryLiteType())) {
AlipayOauth2Params alipayOauth2Params = null;
if(mchAppConfigContext.isIsvsubMch()){ // 特约商户
// 先获取 特约商户的 小程序 配置信息
alipayOauth2Params = (AlipayOauth2Params) configContextQueryService.queryNormalOauth2Params(payOrder.getIsvNo(), payOrder.getAppId(), CS.IF_CODE.ALIPAY);
// 商户未配置 || 使用服务商的小程序时 查询服务商的配置信息
if(alipayOauth2Params == null || alipayOauth2Params.getIsUseSubmchAccount() == null || alipayOauth2Params.getIsUseSubmchAccount() == CS.NO){
// 查询服务商的配置, 判断取哪个 配置条目。
// 查询商户 配置 的oauth2配置 ( 支付参数配置 ) selectOauth2InfoId: 配置的oauth2参数
String ifCodeByMchPassage = configContextQueryService.queryIfCodeByPageType(mchAppConfigContext, QRCodeParams.PAGE_TYPE_ALIPAY_LITE);
String selectOauth2InfoId = configContextQueryService.queryAgentOrIsvSelectOauth2InfoId(mchAppConfigContext.getMchApplyment(), ifCodeByMchPassage);
alipayOauth2Params = (AlipayOauth2Params) configContextQueryService.
queryIsvOauth2ParamsByAutoSelectOauth2InfoId(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.ALIPAY, selectOauth2InfoId);
}
}else{
alipayOauth2Params = (AlipayOauth2Params) configContextQueryService.queryNormalOauth2Params(payOrder.getIsvNo(), payOrder.getAppId(), CS.IF_CODE.ALIPAY);
}
if(alipayOauth2Params == null || alipayOauth2Params.getLiteParams() == null ){
throw new BizException("请配置支付宝小程序oauth2参数");
}
String alipayStartAppUrl = String.format("alipays://platformapi/startapp?appId=%s", alipayOauth2Params.getLiteParams().getAppId());
if (StringUtils.isNotBlank(alipayOauth2Params.getLiteParams().getPagePath())) {
alipayStartAppUrl = alipayStartAppUrl + "&page=" + alipayOauth2Params.getLiteParams().getPagePath();
}
if (StringUtils.isNotBlank(payUrl)) {
alipayStartAppUrl = alipayStartAppUrl + "&query=qrCode=" + payUrl;
}
payUrl = alipayStartAppUrl;
}else if(QRCodeParams.ENTRY_LITE_ALIJSAPIH5.equals(qrCashierOrderRQ.getEntryLiteType())) { // 支付宝 H5 --> 生活号服务窗。
// String alipayStartAppUrl = "alipays://platformapi/startapp?appId=20000067"; // 20000067 固定格式。
// if (StringUtils.isNotBlank(payUrl)) {
// alipayStartAppUrl = alipayStartAppUrl + "&url=" + URLUtil.encodeAll(payUrl);
// }
//TODO 更改
String alipayStartAppUrl = "https://ds.alipay.com/?scheme=";
String scheme = "alipays://platformapi/startapp?appId=10000007&qrcode=";
if (StringUtils.isNotBlank(payUrl)) {
alipayStartAppUrl = alipayStartAppUrl + URLUtil.encodeAll(scheme + URLUtil.encodeAll(payUrl));
}
payUrl = alipayStartAppUrl;
}
}
qrCashierOrderRS.setPayUrl(payUrl);
}
return packageApiResByPayOrder(bizRQ, qrCashierOrderRS, payOrder);
}
/** 统一收银台 */
private ApiRes webCashierPayWay(UnifiedOrderRQ bizRQ, MchAppConfigContext mchAppConfigContext,RouteManage routeManage){
WebCashierOrderRQ webCashierOrderRQ = (WebCashierOrderRQ)bizRQ;
// 判断商户是否拥有 可用通道
// int passageCount = mchPayPassageService.count(MchPayPassage.gw().eq(MchPayPassage::getAppId, mchApp.getAppId()).eq(MchPayPassage::getState, CS.YES));
// if(passageCount <= 0){
// throw new BizException("当前商户未开通任何支付通道!");
// }
//生成订单
PayOrder payOrder = genPayOrder(bizRQ, mchAppConfigContext, null, null, null,routeManage);
String payOrderId = payOrder.getPayOrderId();
//订单入库 订单状态: 生成状态 此时没有和任何上游渠道产生交互。
payOrderService.save(payOrder);
WebCashierOrderRS webCashierOrderRS = new WebCashierOrderRS();
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
String payUrl = dbApplicationConfig.genWebCashierPayUrl(payOrderId);
webCashierOrderRS.setPayUrl(payUrl);
return packageApiResByPayOrder(bizRQ, webCashierOrderRS, payOrder);
}
/** 检查设备类型和设备号传值必填性 */
private void checkDeviceParams(UnifiedOrderRQ bizRQ, String ifCode) {
if (StringUtils.isBlank(bizRQ.getDeviceInfo())) {
return;
}
UnifiedOrderRQ.DeviceInfo deviceInfo = JSON.parseObject(bizRQ.getDeviceInfo(), UnifiedOrderRQ.DeviceInfo.class);
if (deviceInfo == null) {
return;
}
String deviceType = deviceInfo.getDeviceType();
String deviceNo = deviceInfo.getDeviceNo();
String provider = deviceInfo.getProvider();
if (StringUtils.isBlank(deviceType)) {
throw new BizException("设备类型必填");
}
if (!PayOrder.DEVICE_TYPE_MAP.containsKey(deviceType)) {
throw new BizException("请传入正确的设备类型");
}
if (StringUtils.isBlank(deviceNo)) {
throw new BizException("设备号必填");
}
// 智能POS
if (StringUtils.equalsAny(deviceInfo.getDeviceType(), PayOrder.DEVICE_TYPE_AUTO_POS)) {
MchStoreDevice mchStoreDevice = mchStoreDeviceService.getByUniqueKey(ifCode, MchStoreDevice.DEVICE_TYPE_AUTO_POS, deviceInfo.getDeviceNo());
if (mchStoreDevice == null) {
throw new BizException("智能POS设备不存在");
}
if(mchStoreDevice.getBindState() == CS.NO || !bizRQ.getMchNo().equals(mchStoreDevice.getMchNo())){
throw new BizException("智能POS设备所属商户不匹配");
}
deviceInfo.setProvider(ifCode);
}
// 设备类型为扫码POS、收银插件
else if (StringUtils.equalsAny(deviceType, PayOrder.DEVICE_TYPE_SCAN_POS, PayOrder.DEVICE_TYPE_CASH_PLUGIN, PayOrder.DEVICE_TYPE_PRINTER)) {
if (StringUtils.isBlank(provider)) {
throw new BizException("设备厂商必填");
}
MchStoreDevice mchStoreDevice = mchStoreDeviceService.getByUniqueKey(provider, (Byte) MchStoreDevice.covertDeviceType(deviceType), deviceNo);
if (mchStoreDevice == null) {
throw new BizException("终端设备不存在!");
}
if(mchStoreDevice.getBindState() == CS.NO || !bizRQ.getMchNo().equals(mchStoreDevice.getMchNo())){
throw new BizException("终端设备所属商户不匹配!");
}
DeviceProvideConfig provideConfig = deviceProvideConfigService.getById(mchStoreDevice.getConfigId());
if (provideConfig == null || provideConfig.getState() == CS.NO || !provider.equals(provideConfig.getProvider())) {
throw new BizException("终端设备厂商错误!");
}
}
}
/** 获取服务商机构号和子商户号 */
private String[] getChannelMchNo(MchAppConfigContext mchAppConfigContext) {
final String[] channelMchNoArr = { null, null };
if (mchAppConfigContext.isIsvsubMch()) {
IsvParams isvParams = configContextQueryService.queryIsvParams(mchAppConfigContext.getMchApplyment().getIsvNo(), mchAppConfigContext.getMchApplyment().getIfCode());
channelMchNoArr[0] = ChannelMchIdKit.getChannelMchNo(isvParams, ChannelMchIdTypeEnum.ISV_NO);
// IsvsubMchParams isvsubMchParams = configContextQueryService.queryIsvsubMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode);
channelMchNoArr[1] = mchAppConfigContext.getMchApplyment().getChannelMchNo();
}else {
// NormalMchParams normalMchParams = configContextQueryService.queryNormalMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode);
// channelMchNoArr[1] = ChannelMchIdKit.getChannelMchNo(normalMchParams, ChannelMchIdTypeEnum.MCH_NO);
channelMchNoArr[1] = mchAppConfigContext.getMchApplyment().getChannelMchNo();
}
return channelMchNoArr;
}
}

View File

@@ -0,0 +1,154 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import cn.hutool.core.date.DateUtil;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.refund.RefundOrderRQ;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.service.impl.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/*
* 创建支付订单抽象类
*
* @author terrfly
*
* @date 2021/6/8 17:26
*/
@Slf4j
public abstract class AbstractRefundOrderController extends ApiController {
@Autowired private RefundOrderService refundOrderService;
@Autowired private PayMchNotifyService payMchNotifyService;
protected RefundOrder genRefundOrder(RefundOrderRQ rq, PayOrder payOrder, MchInfo mchInfo, MchApp mchApp){
Date nowTime = new Date();
RefundOrder refundOrder = new RefundOrder();
refundOrder.setRefundOrderId(SeqKit.genRefundOrderId()); //退款订单号
refundOrder.setPayOrderId(payOrder.getPayOrderId()); //支付订单号
refundOrder.setChannelPayOrderNo(payOrder.getChannelOrderNo()); //渠道支付单号
refundOrder.setMchNo(payOrder.getMchNo()); //商户号
refundOrder.setAgentNo(payOrder.getAgentNo()); // 服务商号
refundOrder.setTopAgentNo(payOrder.getTopAgentNo()); // 上级服务商号
refundOrder.setIsvNo(payOrder.getIsvNo()); //服务商号
refundOrder.setAppId(payOrder.getAppId()); //商户应用ID
refundOrder.setMchName(mchInfo.getMchShortName()); //商户名称
refundOrder.setStoreId(payOrder.getStoreId()); // 门店ID
refundOrder.setStoreName(payOrder.getStoreName()); // 门店名称
refundOrder.setStoreUserId(payOrder.getStoreUserId()); // 门店收银员ID
refundOrder.setMchType(mchInfo.getType()); //商户类型
refundOrder.setMchRefundNo(rq.getMchRefundNo()); //商户退款单号
refundOrder.setWayCode(payOrder.getWayCode()); //支付方式代码
refundOrder.setWayCodeType(payOrder.getWayCodeType()); //支付方式代码
refundOrder.setIfCode(payOrder.getIfCode()); //支付接口代码
refundOrder.setPayAmount(payOrder.getAmount()); //支付金额,单位分
refundOrder.setRefundAmount(rq.getRefundAmount()); //退款金额,单位分
refundOrder.setCurrency(rq.getCurrency()); //三位货币代码,人民币:cny
refundOrder.setState(RefundOrder.STATE_INIT); //退款状态:0-订单生成,1-退款中,2-退款成功,3-退款失败
refundOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(), getClientIp())); //客户端IP
refundOrder.setDeviceNo(payOrder.getDeviceNo()); // 设备号
refundOrder.setDeviceType(payOrder.getDeviceType()); // 设备类型
refundOrder.setDeviceProvider(payOrder.getDeviceProvider()); // 设备厂商
refundOrder.setRefundReason(rq.getRefundReason()); //退款原因
refundOrder.setChannelOrderNo(null); //渠道订单号
refundOrder.setErrCode(null); //渠道错误码
refundOrder.setErrMsg(null); //渠道错误描述
refundOrder.setChannelExtra(StringUtils.defaultString(rq.getChannelExtra(), payOrder.getChannelExtra())); //特定渠道发起时额外参数
refundOrder.setNotifyUrl(rq.getNotifyUrl()); //通知地址
refundOrder.setExtParam(rq.getExtParam()); //扩展参数
refundOrder.setChannelBizData(StringUtils.defaultIfBlank(rq.getChannelBizData(), payOrder.getChannelBizData())); // 渠道特殊业务数据
refundOrder.setRefundFeeAmount(0L); // 退还手续费默认0 退款成功,再更新。
// 补单规则: 一小时内 每分钟/次 超过1小时 一小时/次 异步通知不变。 超过7天 订单失效。
refundOrder.setExpiredTime(DateUtil.offsetDay(nowTime, 7)); // 订单超时关闭时间 默认7天
refundOrder.setSuccessTime(null); //订单退款成功时间
refundOrder.setCreatedAt(nowTime); //创建时间
refundOrder.setMchExtNo(payOrder.getMchExtNo());
if(rq.getRefundAmount() >= payOrder.getAmount()){
refundOrder.setRefundType(RefundOrder.ALL_REFUND_TYPE);
}else{
refundOrder.setRefundType(RefundOrder.PART_REFUND_TYPE);
}
return refundOrder;
}
/** 处理返回的渠道信息,并更新退款单状态
* payOrder将对部分信息进行 赋值操作。
* **/
protected void processChannelMsg(ChannelRetMsg channelRetMsg, RefundOrder refundOrder){
//对象为空 || 上游返回状态为空, 则无需操作
if(channelRetMsg == null || channelRetMsg.getChannelState() == null){
return ;
}
//明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == channelRetMsg.getChannelState()) {
this.updateInitOrderStateThrowException(RefundOrder.STATE_SUCCESS, refundOrder, channelRetMsg);
payMchNotifyService.refundOrderNotify(refundOrder);
//明确失败
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == channelRetMsg.getChannelState()) {
this.updateInitOrderStateThrowException(RefundOrder.STATE_FAIL, refundOrder, channelRetMsg);
payMchNotifyService.refundOrderNotify(refundOrder);
// 上游处理中 || 未知 || 上游接口返回异常 退款单为退款中状态
}else if( ChannelRetMsg.ChannelState.WAITING == channelRetMsg.getChannelState() ||
ChannelRetMsg.ChannelState.UNKNOWN == channelRetMsg.getChannelState() ||
ChannelRetMsg.ChannelState.API_RET_ERROR == channelRetMsg.getChannelState()
){
this.updateInitOrderStateThrowException(RefundOrder.STATE_ING, refundOrder, channelRetMsg);
// 系统异常: 退款单不再处理。 为: 生成状态
}else if( ChannelRetMsg.ChannelState.SYS_ERROR == channelRetMsg.getChannelState() ){
}else{
throw new BizException("ChannelState 返回异常!");
}
}
/** 更新退款单状态 --》 退款单生成--》 其他状态 (向外抛出异常) **/
protected void updateInitOrderStateThrowException(byte orderState, RefundOrder refundOrder, ChannelRetMsg channelRetMsg){
refundOrder.setState(orderState);
refundOrder.setChannelOrderNo(channelRetMsg.getChannelOrderId());
refundOrder.setErrCode(channelRetMsg.getChannelErrCode());
refundOrder.setErrMsg(channelRetMsg.getChannelErrMsg());
//TODO如果是快钱通道 部分退款当日不到账 与次日到账
if(CS.IF_CODE.KQPAY.equals(refundOrder.getIfCode()) && RefundOrder.PART_REFUND_TYPE == refundOrder.getRefundType()){
refundOrder.setErrMsg("快钱通道部分退款类型的退款金额将于第二个工作日凌晨4-5点到账");
}
boolean isSuccess = refundOrderService.updateInit2Ing(refundOrder.getRefundOrderId(), channelRetMsg.getChannelOrderId());
if(!isSuccess){
throw new BizException("更新退款单异常!");
}
isSuccess = refundOrderService.updateIng2SuccessOrFail(refundOrder.getRefundOrderId(), refundOrder.getState(),
channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelErrCode(), channelRetMsg.getChannelErrMsg());
if(!isSuccess){
throw new BizException("更新退款单异常!");
}
}
}

View File

@@ -0,0 +1,452 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IChannelNoticeService;
import com.jeequan.jeepay.core.interfaces.paychannel.IIsvmchStoreApplymentNotifyService;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchApplyment;
import com.jeequan.jeepay.db.entity.MchStore;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.RouteManage;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.pay.service.PayOrderMarketReissueService;
import com.jeequan.jeepay.pay.service.PayOrderProcessService;
import com.jeequan.jeepay.service.impl.*;
import com.jeequan.jeepay.thirdparty.channel.ryxpay.model.ReqMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 渠道侧的通知入口Controller 【分为同步跳转doReturn和异步回调(doNotify) 】
*
* @author terrfly
*
* @date 2021/6/8 17:26
*/
@Slf4j
@Controller
public class ChannelNoticeController extends AbstractCtrl {
@Autowired private PayOrderService payOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayMchNotifyService payMchNotifyService;
@Autowired private PayOrderProcessService payOrderProcessService;
@Autowired private MchApplymentService mchApplymentService;
@Autowired private MchStoreService mchStoreService;
@Autowired private SettleInfoService settleInfoService;
@Autowired private PayOrderMarketReissueService payOrderMarketReissueService;
@Autowired private RouteManageService routeManageService;
/** 同步通知入口
*
* payOrderId动态参数 可能是固定格式: 规则如下:
* JEEPAYISV_V1000001 (CS.PAY_RETURNURL_FIX_ISV_PREFIX)
* JEEPAYMCHAPP_M1000001
* 如果是以上格式, 那么需要通道内自行解析, & 不校验 urlOrderId 是合法性。
*
* payOrderId 前缀为 ONLYJUMP_表示直接跳转
*
* **/
@RequestMapping(value= {"/api/pay/return/{ifCode}", "/api/pay/return/{ifCode}/{payOrderId}"})
public String doReturn(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "payOrderId", required = false) String urlOrderId){
String payOrderId = null;
String logPrefix = "进入[" +ifCode+ "]支付同步跳转urlOrderId["+ StringUtils.defaultIfEmpty(urlOrderId, "") + "] ";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return this.toReturnPage("ifCode is empty");
}
//查询支付接口是否存在
IChannelNoticeService payNotifyService = SpringBeansUtil.getBean(ifCode + "ChannelNoticeService", IChannelNoticeService.class);
// 支付通道接口实现不存在
if(payNotifyService == null){
log.error("{}, interface not exists ", logPrefix);
return this.toReturnPage("[" + ifCode + "] interface not exists");
}
// 仅做跳转直接跳转订单的returnUrl
onlyJump(urlOrderId, logPrefix);
// 解析订单号 和 请求参数
MutablePair<String, Object> mutablePair = payNotifyService.parseParams(request, urlOrderId, IChannelNoticeService.NoticeTypeEnum.DO_RETURN);
if(mutablePair == null){ // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//解析到订单号
payOrderId = mutablePair.left;
log.info("{}, 解析数据为payOrderId:{}, params:{}", logPrefix, payOrderId, mutablePair.getRight());
// 判断url订单号是否合法
this.judgeUrlOrderId(urlOrderId, payOrderId, logPrefix);
//获取订单号 和 订单数据
PayOrder payOrder = payOrderService.getById(payOrderId);
// 订单不存在
if(payOrder == null){
log.error("{}, 订单不存在. payOrderId={} ", logPrefix, payOrderId);
return this.toReturnPage("支付订单不存在");
}
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo());
//调起接口的回调判断
ChannelRetMsg notifyResult = payNotifyService.doNotice(request, mutablePair.getRight(), payOrder, mchAppConfigContext, IChannelNoticeService.NoticeTypeEnum.DO_RETURN);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//判断订单状态
if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
payOrder.setState(PayOrder.STATE_SUCCESS);
}else if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL) {
payOrder.setState(PayOrder.STATE_FAIL);
}
boolean hasReturnUrl = StringUtils.isNotBlank(payOrder.getReturnUrl());
log.info("===== {}, 订单通知完成。 payOrderId={}, parseState = {}, hasReturnUrl={} =====", logPrefix, payOrderId, notifyResult.getChannelState(), hasReturnUrl);
//包含通知地址时
if(hasReturnUrl){
// 重定向
response.sendRedirect(payMchNotifyService.createReturnUrl(payOrder, mchAppConfigContext.getMchApp().getAppSecret()));
return null;
}else{
//跳转到支付成功页面
return this.toReturnPage(null);
}
} catch (BizException e) {
log.error("{}, payOrderId={}, BizException", logPrefix, payOrderId, e);
return this.toReturnPage(e.getMessage());
} catch (ResponseException e) {
log.error("{}, payOrderId={}, ResponseException", logPrefix, payOrderId, e);
return this.toReturnPage(e.getMessage());
} catch (Exception e) {
log.error("{}, payOrderId={}, 系统异常", logPrefix, payOrderId, e);
return this.toReturnPage(e.getMessage());
}
}
/** 异步回调入口 **/
@ResponseBody
@RequestMapping(value= {"/api/pay/notify/{ifCode}", "/api/pay/notify/{ifCode}/{payOrderId}"})
public ResponseEntity doNotify(HttpServletRequest request,HttpServletResponse response, @PathVariable("ifCode") String ifCode, @PathVariable(value = "payOrderId", required = false) String urlOrderId){
String payOrderId = null;
String logPrefix = "进入[" +ifCode+ "]支付回调urlOrderId["+ StringUtils.defaultIfEmpty(urlOrderId, "") + "] ";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询支付接口是否存在
IChannelNoticeService payNotifyService = SpringBeansUtil.getBean(ifCode + "ChannelNoticeService", IChannelNoticeService.class);
// 支付通道接口实现不存在
if(payNotifyService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 解析订单号 和 请求参数
MutablePair<String, Object> mutablePair = payNotifyService.parseParams(request, urlOrderId, IChannelNoticeService.NoticeTypeEnum.DO_NOTIFY);
if(mutablePair == null){ // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//解析到订单号
payOrderId = mutablePair.left;
log.info("{}, 解析数据为payOrderId:{}, params:{}", logPrefix, payOrderId, mutablePair.getRight());
// 判断url订单号是否合法
this.judgeUrlOrderId(urlOrderId, payOrderId, logPrefix);
//获取订单号 和 订单数据
PayOrder payOrder = payOrderService.getById(payOrderId);
// 订单不存在
if(payOrder == null){
log.error("{}, 订单不存在. payOrderId={} ", logPrefix, payOrderId);
//退款异步通知 由于瑞银信的特殊性 需要转发到退款回调去处理
JSONObject right = (JSONObject) mutablePair.right;
if(CS.IF_CODE.RYXPAY.equals(ifCode) && ReqMethod.ServiceCode.SMZF004.getValue().equals(right.getString("tranCode"))){
request.getRequestDispatcher("/api/refund/notify/" + ifCode).forward(request, response);
}
return payNotifyService.doNotifyOrderNotExists(request);
}
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo());
//调起接口的回调判断
ChannelRetMsg notifyResult = payNotifyService.doNotice(request, mutablePair.getRight(), payOrder, mchAppConfigContext, IChannelNoticeService.NoticeTypeEnum.DO_NOTIFY);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
boolean updateOrderSuccess = true; //默认更新成功
// 订单是 【支付中状态】
if(payOrder.getState() == PayOrder.STATE_ING || payOrder.getState() == PayOrder.STATE_INIT) {
// 渠道订单号
payOrder.setChannelOrderNo(notifyResult.getChannelOrderId());
//明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == notifyResult.getChannelState()) {
updateOrderSuccess = payOrderService.updateIng2Success(payOrderId, notifyResult);
//订单支付成功 其他业务逻辑
if(updateOrderSuccess){
payOrderProcessService.confirmSuccess(payOrder, mchAppConfigContext);
}
//TODO 初始化D0的结算数据
log.info("处理D0交易初始化订单号{}",payOrder.getPayOrderId());
settleInfoService.initD0Settle(payOrder);
//营销活动相关处理
payOrderMarketReissueService.reissue(payOrder);
if(StringUtils.isNotBlank(payOrder.getRouteId())){
routeManageService.upStoreRouteData(payOrder.getFindAmt(),payOrder.getStoreId(),payOrder.getRouteId(),true);
}
//明确失败
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == notifyResult.getChannelState()) {
updateOrderSuccess = payOrderService.updateIng2Fail(payOrderId, notifyResult);
}
}
// 更新订单 异常
if(!updateOrderSuccess){
log.error("{}, updateOrderSuccess = {} ",logPrefix, updateOrderSuccess);
return payNotifyService.doNotifyOrderStateUpdateFail(request);
}
log.info("===== {}, 订单通知完成。 payOrderId={}, parseState = {} =====", logPrefix, payOrderId, notifyResult.getChannelState());
return notifyResult.getResponseEntity();
} catch (BizException e) {
log.error("{}, payOrderId={}, BizException", logPrefix, payOrderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, payOrderId={}, ResponseException", logPrefix, payOrderId, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, payOrderId={}, 系统异常", logPrefix, payOrderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
/** 异步回调入口 **/
@ResponseBody
@RequestMapping(value= {"/api/mchStoreApplyment/notify/{storeId}/{applyId}"})
public ResponseEntity doNotifyStore(HttpServletRequest request, @PathVariable(value = "storeId") String storeId, @PathVariable(value = "applyId") String applyId){
String logPrefix = "进入[" +applyId+ "]门店入驻回调storeId["+ StringUtils.defaultIfEmpty(storeId, "") + "] ";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(applyId)){
return ResponseEntity.badRequest().body("applyId is empty");
}
// 参数有误
if(StringUtils.isEmpty(storeId)){
return ResponseEntity.badRequest().body("storeId is empty");
}
// 进件记录查询
MchApplyment tbMchApplyment = mchApplymentService.getById(applyId);
if (tbMchApplyment == null) {
return ResponseEntity.badRequest().body("进件记录不存在");
}
// 门店进件信息
if (StringUtils.isEmpty(tbMchApplyment.getStoreSuccResParameter())) {
return ResponseEntity.badRequest().body("门店入驻信息不存在");
}
// 支付接口代码
String ifCode = tbMchApplyment.getIfCode();
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询回调接口是否存在
IIsvmchStoreApplymentNotifyService notifyService = SpringBeansUtil.getBean(ifCode + "MchStoreApplymentNoticeService", IIsvmchStoreApplymentNotifyService.class);
// 门店回调接口实现不存在
if(notifyService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 门店信息是否存在
MchStore mchStore = mchStoreService.getById(storeId);
if (mchStore == null) {
return ResponseEntity.badRequest().body("入驻门店不存在");
}
// 获取当前入驻门店信息
JSONArray jsonArray = JSONArray.parseArray(tbMchApplyment.getStoreSuccResParameter());
JSONObject dbStoreApplyment = null;
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject json = (JSONObject) jsonArray.get(i);
if (storeId.equals(json.getString("storeId"))) {
dbStoreApplyment = json;
jsonArray.remove(i);
}
}
if(dbStoreApplyment == null){
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
String storeNo = dbStoreApplyment.getString("store_no");
//调起接口的回调判断
ChannelRetMsg notifyResult = notifyService.doNotice(request, storeNo, tbMchApplyment.getIsvNo(), IIsvmchStoreApplymentNotifyService.NoticeTypeEnum.DO_NOTIFY);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
if (notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
// 门店审核通过
dbStoreApplyment.put("state", MchApplyment.STATE_SUCCESS);
jsonArray.add(dbStoreApplyment);
}else if (notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL) {
// 审核失败
dbStoreApplyment.put("state", MchApplyment.STATE_REJECT_WAIT_MODIFY);
dbStoreApplyment.put("msg", notifyResult.getChannelErrMsg());
jsonArray.add(dbStoreApplyment);
}else {
return notifyResult.getResponseEntity();
}
// 更新入驻信息
MchApplyment updateApplyment = new MchApplyment();
updateApplyment.setApplyId(applyId);
updateApplyment.setStoreSuccResParameter(jsonArray.toJSONString());
boolean update = mchApplymentService.updateById(updateApplyment);
// 更新入驻信息 异常
if(!update){
log.error("{}, update = {} ",logPrefix, update);
}
log.info("===== {}, 门店入驻通知完成。 storeId={}, parseState = {} =====", logPrefix, storeId, notifyResult.getChannelState());
return notifyResult.getResponseEntity();
} catch (BizException e) {
log.error("{}, storeId={}, BizException", logPrefix, storeId, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, storeId={}, ResponseException", logPrefix, storeId, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, storeId={}, 系统异常", logPrefix, storeId, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
/* 跳转到支付成功页面 **/
private String toReturnPage(String errInfo){
return "cashier/returnPage";
}
/** 比较URL是否相等 */
private void judgeUrlOrderId(String urlOrderId, String payOrderId, String logPrefix){
// URL参数中的 orderId为空
if(StringUtils.isEmpty(urlOrderId)){
return ;
}
// 存在固定URL
if(CS.parsePayReturnUrlFix(urlOrderId, CS.PAY_RETURNURL_FIX_MCHAPP_PREFIX) != null || CS.parsePayReturnUrlFix(urlOrderId, CS.PAY_RETURNURL_FIX_ISV_PREFIX) != null){
return ;
}
// 比较URL参数和解析的是否相等
if(!urlOrderId.equals(payOrderId)){
log.error("{}, 订单号不匹配. urlOrderId={}, payOrderId={} ", logPrefix, urlOrderId, payOrderId);
throw new BizException("订单号不匹配!");
}
}
private void onlyJump(String urlOrderId, String logPrefix) throws IOException {
if (StringUtils.isNotBlank(urlOrderId) && urlOrderId.startsWith(CS.PAY_RETURNURL_FIX_ONLY_JUMP_PREFIX)) {
String payOrderId = urlOrderId.substring(CS.PAY_RETURNURL_FIX_ONLY_JUMP_PREFIX.length());
//获取订单号 和 订单数据
PayOrder payOrder = payOrderService.getById(payOrderId);
// 订单不存在
if(payOrder == null){
log.error("{}, 订单不存在. payOrderId={} ", logPrefix, payOrderId);
this.toReturnPage("支付订单不存在");
}
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo());
if (StringUtils.isBlank(payOrder.getReturnUrl())) {
this.toReturnPage(null);
}
response.sendRedirect(payMchNotifyService.createReturnUrl(payOrder, mchAppConfigContext.getMchApp().getAppSecret()));
}
}
}

View File

@@ -0,0 +1,479 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import cn.hutool.core.date.DateUtil;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IChannelOrderAcceptService;
import com.jeequan.jeepay.core.interfaces.paychannel.IDivisionService;
import com.jeequan.jeepay.core.model.applyment.PaywayFee;
import com.jeequan.jeepay.core.model.autopos.ChannelOrderAcceptParams;
import com.jeequan.jeepay.core.model.autopos.ChannelOrderAcceptRetMsg;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.mchconfig.MchDivisionConfig;
import com.jeequan.jeepay.core.model.params.IsvParams;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.pay.service.PayOrderDivisionRefundOrderCommonService;
import com.jeequan.jeepay.pay.service.PayOrderProcessService;
import com.jeequan.jeepay.pay.service.RefundOrderProcessService;
import com.jeequan.jeepay.service.impl.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
*
* * 渠道订单接收 ctrl
* * 渠道 --> 我方系统
* * 作用: 将渠道订单信息同步到我方系统中
* * 目前支持的有: 立码收
*
* 智能POS交易通知 POS直接收款交易结果通知接口
*
* @author zx
*
* @date 2021/6/8 17:26
*/
@Slf4j
@Controller
public class ChannelOrderAcceptController extends AbstractCtrl {
@Autowired private PayOrderService payOrderService;
@Autowired private RefundOrderService refundOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayOrderProcessService payOrderProcessService;
@Autowired private RefundOrderProcessService refundOrderProcessService;
@Autowired private MchStoreService mchStoreService;
@Autowired private RateConfigService rateConfigService;
@Autowired private MchConfigService mchConfigService;
@Autowired private MchStoreDeviceService mchStoreDeviceService;
@Autowired private PayOrderDivisionRefundOrderCommonService payOrderDivisionRefundOrderCommonService;
/** 通知入口 **/
@ResponseBody
// 联调 地址
// @RequestMapping("/api/orderPush/{ifCode}")
// 正式 URL
@RequestMapping(value= {
"/api/pay/posNotify/{ifCode}", // 存量配置 智能pos 订单推送模式(兼容历史数据)
"/api/channelOrderAccept/{ifCode}", // channelOrderAccept 新通用命名 不加密
"/api/channelOrderAccept/{ifCode}/{isvNo}" // channelOrderAccept 新通用命名 加密数据
})
public ResponseEntity channelOrderAccept(HttpServletRequest request, @PathVariable("ifCode") String ifCode,
@PathVariable(value = "isvNo", required = false) String isvNo){
String logPrefix = "[" +ifCode+ "]【接收订单推送】isvNo=" + StringUtils.defaultIfBlank(isvNo, "");
log.info("===== 进入{} =====" , logPrefix);
String channelOrderNo = "";
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询支付接口是否存在
IChannelOrderAcceptService orderAcceptService= SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "ChannelOrderAcceptService", IChannelOrderAcceptService.class);
// 支付通道接口实现不存在
if(orderAcceptService == null){
log.error("{}, interface not exists", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 服务商号不为空,查询服务商参数,一般用于通知数据加密时,需要服务商参数解密
IsvParams isvParams = null;
if (StringUtils.isNotBlank(isvNo)) {
isvParams = configContextQueryService.queryIsvParams(isvNo, ifCode);
if (isvParams == null) {
log.error("{}, 服务商参数未配置", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] system error");
}
}
// 解析请求参数
ChannelOrderAcceptParams acceptParams = orderAcceptService.parseParams(request, isvParams);
if(acceptParams == null || acceptParams.getDeviceType() == null || StringUtils.isBlank(acceptParams.getDeviceNo())){ // 解析数据失败, 响应已处理
log.error("{}, acceptParams is null ", logPrefix);
return ResponseEntity.accepted().body("[" + ifCode + "] data parse error");
}
// 解析商户号、应用、门店
MchStoreDevice mchStoreDevice = getDeviceByIfCodeAndDeviceNo(ifCode, acceptParams.getDeviceType(), acceptParams.getDeviceNo(), logPrefix);
// 查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchStoreDevice.getMchNo(), mchStoreDevice.getAppId());
// 接口处理订单数据
ChannelOrderAcceptRetMsg acceptRetMsg;
if (ChannelOrderAcceptParams.TRADE_TYPE_PAYMENT.equals(acceptParams.getTradeType())) {
acceptRetMsg = orderAcceptService.payNotice(request, acceptParams.getAcceptData(), mchAppConfigContext, ifCode);
} else if (ChannelOrderAcceptParams.TRADE_TYPE_REFUND.equals(acceptParams.getTradeType())) {
acceptRetMsg = orderAcceptService.refundNotice(request, acceptParams.getAcceptData(), mchAppConfigContext, ifCode);
} else {
throw new BizException("订单类型错误!");
}
// 返回null 表明出现异常。
if(acceptRetMsg == null || acceptRetMsg.getChannelState() == null || acceptRetMsg.getResponseEntity() == null
|| StringUtils.isBlank(acceptRetMsg.getChannelOrderId()) || acceptRetMsg.getChannelAmount() == null){
log.error("{}, 处理回调事件异常 acceptRetMsg data error, acceptRetMsg ={} ",logPrefix, acceptRetMsg);
throw new BizException("处理回调事件异常!");
}
channelOrderNo = acceptRetMsg.getChannelOrderId();
// 非支付成功状态,直接返回
if (acceptRetMsg.getChannelState() != ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
return acceptRetMsg.getResponseEntity();
}
if (ChannelOrderAcceptParams.TRADE_TYPE_PAYMENT.equals(acceptParams.getTradeType())) {
processPayOrder(acceptRetMsg, mchAppConfigContext, mchStoreDevice, ifCode, logPrefix);
} else if (ChannelOrderAcceptParams.TRADE_TYPE_REFUND.equals(acceptParams.getTradeType())) {
processRefundOrder(acceptRetMsg, mchAppConfigContext, ifCode, logPrefix);
}
log.info("===== {}, 订单通知完成。 channelOrderNo={}, parseState = {} =====", logPrefix, channelOrderNo, acceptRetMsg.getChannelState());
return acceptRetMsg.getResponseEntity();
} catch (BizException e) {
log.error("{}, channelOrderNo={}, BizException", logPrefix, channelOrderNo, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, channelOrderNo={}, ResponseException", logPrefix, channelOrderNo, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, channelOrderNo={}, 系统异常", logPrefix, channelOrderNo, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
// 处理订单
private void processPayOrder(ChannelOrderAcceptRetMsg channelResult, MchAppConfigContext mchAppConfigContext,
MchStoreDevice mchStoreDevice, String ifCode, String logPrefix) {
// 根据通道返回的支付订单号查询订单,存在则更新状态,不存在插入新订单
if (StringUtils.isNotBlank(channelResult.getBizOrderId())) {
PayOrder dbPayOrder = payOrderService.getById(channelResult.getBizOrderId());
if (dbPayOrder != null) {
payOrderProcessService.handlePayOrder4Channel(channelResult, dbPayOrder, mchAppConfigContext);
return;
}
}
// 查询当前支付接口 订单是否已创建
long count = payOrderService.count(PayOrder.gw()
.eq(PayOrder::getChannelOrderNo, channelResult.getChannelOrderId())
.eq(PayOrder::getIfCode, ifCode)
);
if (count > 0) {
log.error("{}, 当前订单已创建,渠道订单号:{}", logPrefix, channelResult.getChannelOrderId());
throw new ResponseException(channelResult.getResponseEntity());
}
PayOrder payOrder = genPayOrder(channelResult, mchAppConfigContext, mchStoreDevice, ifCode);
boolean saveResult = payOrderService.save(payOrder);
if (!saveResult) {
log.error("{}, 数据库更新异常", logPrefix);
throw new BizException("数据库更新异常!");
}
// 订单支付成功 其他业务逻辑
payOrderProcessService.confirmSuccess(payOrder, mchAppConfigContext);
}
private void processRefundOrder(ChannelOrderAcceptRetMsg channelResult, MchAppConfigContext mchAppConfigContext, String ifCode, String logPrefix) {
// 支付订单,根据退款通知中的 原渠道订单号(订单表的渠道订单号)查询
PayOrder payOrder = payOrderService.getOneByChannelOrderId(ifCode, channelResult.getChannelPayOrderId());
if (payOrder == null) {
throw new BizException("支付订单不存在");
}
if(payOrder.getRefundState() == PayOrder.REFUND_STATE_ALL || payOrder.getRefundAmount() >= payOrder.getAmount()){
throw new BizException("订单已全额退款");
}
if(payOrder.getState() != PayOrder.STATE_SUCCESS){
throw new BizException("仅支付成功状态的订单才可发起退款");
}
Long refundAmount = channelResult.getChannelAmount();
if(payOrder.getRefundAmount() + refundAmount > payOrder.getAmount()){
throw new BizException("退款金额超过订单可退款金额!");
}
// 全部退款金额 (退款订单表)
Long sumSuccessRefundAmount = refundOrderService.getBaseMapper().sumSuccessRefundAmount(payOrder.getPayOrderId());
if(sumSuccessRefundAmount >= payOrder.getAmount()){
throw new BizException("退款单已完成全部订单退款,本次申请失败");
}
if(sumSuccessRefundAmount + refundAmount > payOrder.getAmount()){
throw new BizException("申请金额超出订单可退款余额,请检查退款金额");
}
// 根据通道处理后 返回的退款订单号查询订单,存在则更新状态,不存在插入新订单
RefundOrder dbRefundOrder = refundOrderService.getById(channelResult.getBizOrderId());
if (dbRefundOrder != null) {
// 更新退款订单为成功状态 && 处理退分
updateRefundStateSuccessAndRefundDivision(true, channelResult, dbRefundOrder, mchAppConfigContext, logPrefix);
return;
}
// 查询当前支付接口 退款订单是否已创建
long count = refundOrderService.count(RefundOrder.gw()
.eq(RefundOrder::getChannelOrderNo, channelResult.getChannelOrderId())
.eq(RefundOrder::getIfCode, ifCode)
);
if (count > 0) {
log.error("{}, 当前退款订单已创建,渠道退款订单号:{}", logPrefix, channelResult.getChannelOrderId());
throw new ResponseException(channelResult.getResponseEntity());
}
RefundOrder refundOrder = genRefundOrder(channelResult, payOrder, mchAppConfigContext);
boolean saveResult = refundOrderService.save(refundOrder);
if (!saveResult) {
log.error("{}, 数据库更新异常", logPrefix);
throw new BizException("数据库更新异常!");
}
// 更新退款订单为成功状态 && 处理退分
updateRefundStateSuccessAndRefundDivision(false, channelResult, refundOrder, mchAppConfigContext, logPrefix);
}
private MchStoreDevice getDeviceByIfCodeAndDeviceNo(String ifCode, Byte deviceType, String deviceNo, String logPrefix) {
MchStoreDevice mchStoreDevice = mchStoreDeviceService.getOne(MchStoreDevice.gw()
.eq(MchStoreDevice::getProvider, ifCode)
.eq(MchStoreDevice::getDeviceType, deviceType)
.eq(MchStoreDevice::getDeviceNo, deviceNo)
.eq(MchStoreDevice::getBindState, CS.YES)
.eq(MchStoreDevice::getState, CS.YES)
);
if (mchStoreDevice == null) {
log.error("{}, 设备不存在deviceNo={} ", logPrefix, deviceNo);
throw new BizException("解析数据异常!");
}
return mchStoreDevice;
}
private PayOrder genPayOrder(ChannelOrderAcceptRetMsg channelResult, MchAppConfigContext mchAppConfigContext, MchStoreDevice mchStoreDevice, String ifCode){
MchInfo mchInfo = mchAppConfigContext.getMchInfo();
com.jeequan.jeepay.core.entity.MchApp mchApp = mchAppConfigContext.getMchApp();
// 商户退款回调地址
MchConfig payNotifyConfig = mchConfigService.getByMchNoAndConfigKey(mchInfo.getMchNo(), "mchPayNotifyUrl");
if (payNotifyConfig == null || StringUtils.isBlank(payNotifyConfig.getConfigVal())) {
log.error("商户支付回调地址未配置商户号mchNo{}", mchInfo.getMchNo());
}
PayOrder payOrder = new PayOrder();
payOrder.setPayOrderId(SeqKit.genPayOrderId()); //生成订单ID
payOrder.setMchNo(mchInfo.getMchNo()); //商户号
// payOrder.setIsvNo(mchInfo.getIsvNo()); //服务商号
payOrder.setAgentNo(mchInfo.getAgentNo()); // 服务商ID
payOrder.setTopAgentNo(mchInfo.getTopAgentNo()); // 上级服务商ID
payOrder.setAppId(mchApp.getAppId()); //商户应用appId
payOrder.setMchName(mchInfo.getMchShortName()); //商户名称(简称)
payOrder.setMchType(mchInfo.getType()); //商户类型
payOrder.setEpUserId(mchInfo.getCreatedUid()); // 商户拓展员
payOrder.setMchOrderNo(SeqKit.genMhoOrderId()); //商户订单号
payOrder.setIfCode(ifCode); //接口代码
payOrder.setWayCode(channelResult.getChannelWayCode()); //支付方式
payOrder.setWayCodeType(StringUtils.defaultString(channelResult.getChannelWayCodeType(), CS.PAY_WAY_CODE_TYPE.OTHER)); // 渠道返回的支付方式类型
payOrder.setAmount(channelResult.getChannelAmount()); //订单金额
MchStore mchStore;
String storeId = mchStoreDevice.getStoreId();
if (storeId != null) {
mchStore = mchStoreService.getById(storeId);
}else {
mchStore = mchStoreService.queryDefaultStore(mchInfo.getMchNo()); //查询默认门店
}
payOrder.setStoreId(mchStore.getStoreId()); // 门店ID
payOrder.setStoreName(mchStore.getStoreName()); // 门店名称
payOrder.setLng(mchStore.getLng()); // 经度
payOrder.setLat(mchStore.getLat()); // 纬度
payOrder.setAddress(mchStore.getAddress()); // 详细地址
payOrder.setDeviceType((String) MchStoreDevice.covertDeviceType(mchStoreDevice.getDeviceType())); // 设备类型
payOrder.setDeviceNo(mchStoreDevice.getDeviceNo()); // 设备号
payOrder.setDeviceProvider(mchStoreDevice.getProvider()); // 设备厂商
// 查询费率
if (!CS.PAY_WAY_CODE.OUT_TRADE.equals(channelResult.getChannelWayCode())) {
PaywayFee paywayFee = rateConfigService.queryCreateOrderPaywayFee(mchAppConfigContext.getMchApplyment().getApplyId(), mchAppConfigContext.getMchApplyment().getIsvNo(), mchAppConfigContext.getMchApplyment().getAutoConfigMchAppId(), ifCode, channelResult.getChannelWayCode());
if(paywayFee == null){
log.error("[{}]接收订单推送, 商户费率未配置", ifCode);
throw new BizException("商户费率未配置!");
}
MutablePair<String, Long> feeAndSnapshot = paywayFee.calFeeAndSnapshot(payOrder.getAmount());
payOrder.setMchFeeRate(feeAndSnapshot.left);
payOrder.setMchFeeAmount(feeAndSnapshot.right); //商户手续费,单位分
payOrder.setMchOrderFeeAmount(feeAndSnapshot.right); //商户手续费,单位分
}else if (channelResult.getChannelFee() != null) {
payOrder.setMchFeeAmount(channelResult.getChannelFee()); //商户手续费,单位分
payOrder.setMchOrderFeeAmount(channelResult.getChannelFee()); //商户手续费,单位分
}else {
payOrder.setMchFeeAmount(0L); // 默认无手续费
payOrder.setMchOrderFeeAmount(0L); // 默认无手续费
}
Date nowTime = new Date();
payOrder.setCurrency("cny"); //币种
payOrder.setState(PayOrder.STATE_SUCCESS); // 仅保存支付成功订单
payOrder.setSuccessTime(nowTime);
payOrder.setSubject("[" + mchStore.getStoreName() + "]收款"); //商品标题
payOrder.setBody("[" + mchStore.getStoreName() + "]收款"); //商品描述信息
payOrder.setChannelOrderNo(channelResult.getChannelOrderId()); // 渠道订单号
payOrder.setChannelUser(channelResult.getChannelUserId()); //渠道用户标志
payOrder.setPlatformOrderNo(channelResult.getPlatformOrderNo());
payOrder.setPlatformMchOrderNo(channelResult.getPlatformMchOrderNo());
// 渠道特殊参数,渠道自处理数据
if (channelResult.getChannelBizData() != null) {
payOrder.setChannelBizData(channelResult.getChannelBizData().toJSONString());
}
if (payNotifyConfig != null && StringUtils.isNotBlank(payNotifyConfig.getConfigVal())) {
payOrder.setNotifyUrl(payNotifyConfig.getConfigVal()); // 支付通知地址
}
// 分账模式
if (channelResult.getChannelDivisionMode() != null) {
payOrder.setDivisionMode(channelResult.getChannelDivisionMode());
} else {
payOrder.setDivisionMode(PayOrder.DIVISION_MODE_FORBID); // 默认不分账
// 当前商户配置模式为: 全局自动分账 && 金额满足
MchDivisionConfig mchDivisionConfig = mchConfigService.queryMchDivisionConfigByDefault(payOrder.getMchNo());
if(mchDivisionConfig.getOverrideAutoFlag() == CS.YES && payOrder.getAmount() >= mchDivisionConfig.getAutoDivisionRules().getAmountLimit()){
payOrder.setDivisionMode(PayOrder.DIVISION_MODE_AUTO);
}
}
payOrder.setCreatedAt(nowTime);
return payOrder;
}
private RefundOrder genRefundOrder(ChannelOrderAcceptRetMsg channelResult, PayOrder payOrder, MchAppConfigContext mchAppConfigContext){
MchInfo mchInfo = mchAppConfigContext.getMchInfo();
com.jeequan.jeepay.core.entity.MchApp mchApp = mchAppConfigContext.getMchApp();
// 商户退款回调地址
MchConfig refundNotifyConfig = mchConfigService.getByMchNoAndConfigKey(mchInfo.getMchNo(), "mchRefundNotifyUrl");
if (refundNotifyConfig == null || StringUtils.isBlank(refundNotifyConfig.getConfigVal())) {
log.error("商户退款回调地址未配置商户号mchNo{}", mchInfo.getMchNo());
}
RefundOrder refundOrder = new RefundOrder();
refundOrder.setRefundOrderId(SeqKit.genRefundOrderId()); //退款订单号
refundOrder.setPayOrderId(payOrder.getPayOrderId()); //支付订单号
refundOrder.setChannelPayOrderNo(payOrder.getChannelOrderNo()); //渠道支付单号
refundOrder.setMchNo(mchInfo.getMchNo()); //商户号
refundOrder.setAgentNo(mchInfo.getAgentNo()); // 服务商号
refundOrder.setTopAgentNo(mchInfo.getTopAgentNo()); // 上级服务商号
// refundOrder.setIsvNo(mchInfo.getIsvNo()); //服务商号
refundOrder.setAppId(mchApp.getAppId()); //商户应用ID
refundOrder.setMchName(mchInfo.getMchShortName()); //商户名称
refundOrder.setStoreId(payOrder.getStoreId()); // 门店ID
refundOrder.setStoreName(payOrder.getStoreName()); // 门店名称
refundOrder.setMchType(mchInfo.getType()); //商户类型
refundOrder.setMchRefundNo(SeqKit.genMhoRefundOrderId()); //商户退款单号
refundOrder.setWayCode(payOrder.getWayCode()); //支付方式代码
refundOrder.setWayCodeType(payOrder.getWayCodeType()); // 支付方式代码分类
refundOrder.setIfCode(payOrder.getIfCode()); //支付接口代码
refundOrder.setPayAmount(payOrder.getAmount()); //支付金额,单位分
refundOrder.setRefundAmount(channelResult.getChannelAmount()); //退款金额,单位分
refundOrder.setCurrency(payOrder.getCurrency()); //三位货币代码,人民币:cny
refundOrder.setState(RefundOrder.STATE_ING); // 插入退款中订单,再更新成退款成功
refundOrder.setClientIp(StringUtils.defaultIfEmpty(payOrder.getClientIp(), getClientIp())); //客户端IP
refundOrder.setRefundReason("退款"); //退款原因
refundOrder.setChannelOrderNo(channelResult.getChannelOrderId()); //渠道订单号
refundOrder.setErrCode(channelResult.getChannelErrCode()); //渠道错误码
refundOrder.setErrMsg(channelResult.getChannelErrMsg()); //渠道错误描述
refundOrder.setRefundFeeAmount(0L); // 退还手续费默认0 退款成功,再更新。
if (refundNotifyConfig != null && StringUtils.isNotBlank(refundNotifyConfig.getConfigVal())) {
refundOrder.setNotifyUrl(refundNotifyConfig.getConfigVal()); // 退款通知地址
}
// 退款不进行补单操作超过7天 订单失效。
Date nowTime = new Date();
refundOrder.setExpiredTime(DateUtil.offsetDay(nowTime, 7)); // 订单超时关闭时间 默认7天
refundOrder.setSuccessTime(nowTime); //订单退款成功时间
refundOrder.setCreatedAt(nowTime); //创建时间
return refundOrder;
}
// 更新退款订单为成功状态 && 处理退分
private void updateRefundStateSuccessAndRefundDivision(boolean isUpdateRefundOrder, ChannelOrderAcceptRetMsg channelResult, RefundOrder refundOrder,
MchAppConfigContext mchAppConfigContext, String logPrefix) {
IDivisionService divisionService = getDivisionService(refundOrder.getIfCode());
// 渠道是否自动退分账true-自动退,无需系统调用
boolean channelDivisionRefundMode = channelResult.getChannelDivisionRefundMode() != null &&
channelResult.getChannelDivisionRefundMode() == 1;
// 退款业务发生事前 分账。
// 直接更新
if(!channelDivisionRefundMode && !isUpdateRefundOrder && divisionService != null && !divisionService.divisionRefundIsOrderRefundAfterProc()){
// 处理分账回退逻辑
payOrderDivisionRefundOrderCommonService.processDivisionRefund(refundOrder, mchAppConfigContext);
}
// 处理退款订单
boolean updateOrderSuccess = refundOrderProcessService.handleRefundOrder4Channel(channelResult, refundOrder);
if(!updateOrderSuccess){
log.error("{}, updateOrderSuccess = {} ",logPrefix, updateOrderSuccess);
throw ResponseException.buildText("update status error");
}
}
private IDivisionService getDivisionService(String ifCode){
IDivisionService divisionService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "DivisionService", IDivisionService.class);
return divisionService;
}
}

View File

@@ -0,0 +1,116 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.paychannel.IPayOrderCloseService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.payorder.ClosePayOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.ClosePayOrderRS;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 关闭订单 controller
*
* @author xiaoyu
*
* @date 2022/1/25 9:19
*/
@Slf4j
@RestController
public class CloseOrderController extends ApiController {
@Autowired private PayOrderService payOrderService;
@Autowired private ConfigContextQueryService configContextQueryService;
/**
* @author: xiaoyu
* @date: 2022/1/25 9:19
* @describe: 关闭订单
*/
@MethodLog(remark = "支付订单关闭API")
@RequestMapping("/api/pay/close")
public ApiRes queryOrder(){
//获取参数 & 验签
ClosePayOrderRQ rq = getRQByWithMchSign(ClosePayOrderRQ.class);
if(StringUtils.isAllEmpty(rq.getPayOrderId(), rq.getMchOrderNo())){
throw new BizException("payOrderId 和 mchOrderNo 不能同时为空");
}
PayOrder payOrder = payOrderService.queryMchOrder(rq.getMchNo(), rq.getPayOrderId(), rq.getMchOrderNo());
if(payOrder == null){
throw new BizException("订单不存在");
}
if (payOrder.getState() != PayOrder.STATE_INIT && payOrder.getState() != PayOrder.STATE_ING) {
throw new BizException("当前订单不可关闭");
}
ClosePayOrderRS bizRes = new ClosePayOrderRS();
// 订单生成状态 直接修改订单状态
if (payOrder.getState() == com.jeequan.jeepay.core.entity.PayOrder.STATE_INIT) {
payOrderService.updateInit2Close(payOrder.getPayOrderId());
bizRes.setChannelRetMsg(ChannelRetMsg.confirmSuccess(null));
return ApiResKit.okWithSign(bizRes, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
}
try {
String payOrderId = payOrder.getPayOrderId();
log.error("{} 关单操作!", payOrderId);
//查询支付接口是否存在
IPayOrderCloseService closeService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(payOrder.getIfCode()) + "PayOrderCloseService", IPayOrderCloseService.class);
ChannelRetMsg channelRetMsg = ChannelRetMsg.confirmSuccess(null);
// 支付通道接口实现不存在
if(closeService == null){
log.error("{} interface not exists, 直接修改为关单成功!", payOrder.getIfCode());
}else{
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo());
channelRetMsg = closeService.close(payOrder, mchAppConfigContext);
if(channelRetMsg == null){
log.error("channelRetMsg is null, 直接修改为关单成功!");
channelRetMsg = ChannelRetMsg.confirmSuccess(null);
}
}
log.info("关闭订单[{}]结果为:{}", payOrderId, channelRetMsg);
// 关闭订单 成功
if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
payOrderService.updateIng2Close(payOrderId);
}else {
return ApiRes.customFail("渠道响应:" + channelRetMsg.getChannelErrMsg());
}
bizRes.setChannelRetMsg(channelRetMsg);
} catch (Exception e) { // 关闭订单异常
log.error("error payOrderId = {}", payOrder.getPayOrderId(), e);
throw new BizException(e.getMessage());
}
return ApiResKit.okWithSign(bizRes, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
}
}

View File

@@ -0,0 +1,175 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IPosNoticeService;
import com.jeequan.jeepay.core.model.autopos.ChannelOrderAcceptParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.RefundOrder;
import com.jeequan.jeepay.pay.service.PayOrderProcessService;
import com.jeequan.jeepay.pay.service.RefundOrderProcessService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.RefundOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* 智能POS交易通知 用于将订单信息推送到POS使用POS收款后的异步通知
*
* @author zx
*
* @date 2021/6/8 17:26
*/
@Slf4j
@Controller
public class PosNoticeController extends AbstractCtrl {
@Autowired private PayOrderService payOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayOrderProcessService payOrderProcessService;
@Autowired private RefundOrderService refundOrderService;
@Autowired private RefundOrderProcessService refundOrderProcessService;
/** 智能POS通知入口 **/
@ResponseBody
@RequestMapping("/api/pos/pay/notify/{ifCode}")
public ResponseEntity posNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode){
String orderId = null;
String logPrefix = "进入[" +ifCode+ "]智能POS【订单推送至POS机收款模式】回调";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询支付接口是否存在
IPosNoticeService payNotifyService = SpringBeansUtil.getBean(ifCode + "PosNoticeService", IPosNoticeService.class);
// 支付通道接口实现不存在
if(payNotifyService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 解析订单号 和 请求参数 Triple => <交易类型(PAYMENT-支付 REFUND-退款),支付订单号,请求数据>
Triple<String, String, Object> triple = payNotifyService.parseParams(request);
if(triple == null || StringUtils.isBlank(triple.getMiddle())){ // 解析数据失败, 响应已处理
log.error("{}, params is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
// 解析到订单号
orderId = triple.getMiddle();
// 接口验签 && 处理订单数据
ChannelRetMsg notifyResult;
// 支付订单
if (StringUtils.equals(triple.getLeft(), ChannelOrderAcceptParams.TRADE_TYPE_PAYMENT)) {
PayOrder payOrder = getPayOrder(orderId, logPrefix);
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo());
notifyResult = payNotifyService.payNotice(request, triple.getRight(), payOrder, mchAppConfigContext);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
payOrderProcessService.handlePayOrder4Channel(notifyResult, payOrder, mchAppConfigContext);
// 退款订单
} else if (StringUtils.equals(triple.getLeft(), ChannelOrderAcceptParams.TRADE_TYPE_REFUND)) {
RefundOrder refundOrder = getRefundOrder(orderId, logPrefix);
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(refundOrder.getMchNo(), refundOrder.getAppId(),refundOrder.getMchExtNo());
notifyResult = payNotifyService.refundNotice(request, triple.getRight(), refundOrder, mchAppConfigContext);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
// 处理退款订单
boolean updateOrderSuccess = refundOrderProcessService.handleRefundOrder4Channel(notifyResult, refundOrder);
// 更新退款订单 异常
if(!updateOrderSuccess){
log.error("{}, updateOrderSuccess = {} ",logPrefix, updateOrderSuccess);
throw ResponseException.buildText("update status error");
}
} else {
throw new BizException("通知类型错误!");
}
log.info("===== {}, 订单通知完成。 orderId={}, parseState = {} =====", logPrefix, orderId, notifyResult.getChannelState());
return notifyResult.getResponseEntity();
} catch (BizException e) {
log.error("{}, orderId={}, BizException", logPrefix, orderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, orderId={}, ResponseException", logPrefix, orderId, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, orderId={}, 系统异常", logPrefix, orderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
private PayOrder getPayOrder(String orderId, String logPrefix) {
// 获取订单号 和 订单数据
PayOrder payOrder = payOrderService.getById(orderId);
// 订单不存在
if(payOrder == null){
log.error("{}, 订单不存在. orderId={} ", logPrefix, orderId);
throw ResponseException.buildText("order not exists");
}
return payOrder;
}
private RefundOrder getRefundOrder(String orderId, String logPrefix) {
// 获取订单号 和 订单数据
RefundOrder refundOrder = refundOrderService.getById(orderId);
// 订单不存在
if(refundOrder == null){
log.error("{}, 退款订单不存在. refundOrder={} ", logPrefix, refundOrder);
throw ResponseException.buildText("refund order not exists");
}
return refundOrder;
}
}

View File

@@ -0,0 +1,80 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.payorder.QueryPayOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.QueryPayOrderRS;
import com.jeequan.jeepay.db.entity.MchConfig;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.MchConfigService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/*
* 商户查单controller
*
* @author terrfly
*
* @date 2021/6/8 17:26
*/
@Slf4j
@RestController
public class QueryOrderController extends ApiController {
@Autowired private PayOrderService payOrderService;
@Autowired private MchConfigService mchConfigService;
@Autowired private IConfigContextQueryService configContextQueryService;
/**
* 查单接口
* **/
@MethodLog(remark = "查询支付订单API")
@RequestMapping("/api/pay/query")
public ApiRes queryOrder(){
//获取参数 & 验签
QueryPayOrderRQ rq = getRQByWithMchSign(QueryPayOrderRQ.class);
if(StringUtils.isAllEmpty(rq.getMchOrderNo(), rq.getPayOrderId())){
throw new BizException("mchOrderNo 和 payOrderId不能同时为空");
}
PayOrder payOrder = payOrderService.queryMchOrder(rq.getMchNo(), rq.getPayOrderId(), rq.getMchOrderNo());
if(payOrder == null){
throw new BizException("订单不存在");
}
QueryPayOrderRS bizRes = QueryPayOrderRS.buildByPayOrder(payOrder);
JSONObject jsonObject = (JSONObject)JSONObject.toJSON(bizRes);
// 查询扩展参数
MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(payOrder.getMchNo(), "payOrderNotifyExtParams");
List<String> extParams = new ArrayList<>();
if(mchConfig != null && StringUtils.isNotBlank(mchConfig.getConfigVal())){
extParams = JSON.parseArray(mchConfig.getConfigVal(), String.class);
}
for (String key : QueryPayOrderRS.EXT_PRAMS) {
if(!extParams.contains(key)){
jsonObject.remove(key);
}
}
// 报文签名
return ApiResKit.okWithSign(jsonObject, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
}
}

View File

@@ -0,0 +1,101 @@
package com.jeequan.jeepay.pay.ctrl.payorder;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.AutoBarOrderRQ;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.PayWay;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.PayWayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 统一下单 controller
*
* @author terrfly
*
* @date 2021/6/8 17:27
*/
@Slf4j
@RestController
public class UnifiedOrderController extends AbstractPayOrderController {
@Autowired private PayWayService payWayService;
@Autowired private IConfigContextQueryService configContextQueryService;
/**
* 统一下单接口
* **/
@MethodLog(remark = "统一下单API")
@PostMapping("/api/pay/unifiedOrder")
public ApiRes unifiedOrder(){
//获取参数 & 验签
UnifiedOrderRQ rq = getRQByWithMchSign(UnifiedOrderRQ.class);
UnifiedOrderRQ bizRQ = buildBizRQ(rq);
//实现子类的res
ApiRes apiRes = unifiedOrder(bizRQ.getWayCode(), bizRQ);
if(apiRes.getData() == null){
return apiRes;
}
UnifiedOrderRS bizRes = (UnifiedOrderRS)apiRes.getData();
//聚合接口,返回的参数
UnifiedOrderRS res = new UnifiedOrderRS();
BeanUtils.copyProperties(bizRes, res);
//只有 订单生成QR_CASHIER || 支付中 || 支付成功返回该数据
if(bizRes.getOrderState() != null && (bizRes.getOrderState() == PayOrder.STATE_INIT || bizRes.getOrderState() == PayOrder.STATE_ING || bizRes.getOrderState() == PayOrder.STATE_SUCCESS) ){
res.setPayDataType(bizRes.buildPayDataType());
res.setPayData(bizRes.buildPayData());
}
return ApiResKit.okWithSign(res, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
}
private UnifiedOrderRQ buildBizRQ(UnifiedOrderRQ rq){
//支付方式 比如: ali_bar
String wayCode = rq.getWayCode();
//jsapi 收银台聚合支付场景 (不校验是否存在payWayCode)
if(CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)){
return rq.buildBizRQ();
}
//web收银台聚合支付场景 (不校验是否存在payWayCode)
if(CS.PAY_WAY_CODE.WEB_CASHIER.equals(wayCode)){
return rq.buildBizRQ();
}
//如果是自动分类条码
if(CS.PAY_WAY_CODE.AUTO_BAR.equals(wayCode)){
AutoBarOrderRQ bizRQ = (AutoBarOrderRQ)rq.buildBizRQ();
wayCode = JeepayKit.getPayWayCodeByBarCode(bizRQ.getAuthCode());
rq.setWayCode(wayCode.trim());
}
if(payWayService.count(PayWay.gw().eq(PayWay::getWayCode, wayCode)) <= 0){
throw new BizException("不支持的支付方式");
}
//转换为 bizRQ
return rq.buildBizRQ();
}
}

View File

@@ -0,0 +1,38 @@
package com.jeequan.jeepay.pay.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.AliBarOrderRQ;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 支付宝 条码支付 controller
*
* @author terrfly
*
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class AliBarOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/aliBarOrder")
public ApiRes aliBarOrder(){
//获取参数 & 验证
AliBarOrderRQ bizRQ = getRQByWithMchSign(AliBarOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.ALI_BAR, bizRQ);
}
}

View File

@@ -0,0 +1,38 @@
package com.jeequan.jeepay.pay.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.AliJsapiOrderRQ;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 支付宝 jspai controller
*
* @author terrfly
*
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class AliJsapiOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/aliJsapiOrder")
public ApiRes aliJsapiOrder(){
//获取参数 & 验证
AliJsapiOrderRQ bizRQ = getRQByWithMchSign(AliJsapiOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.ALI_JSAPI, bizRQ);
}
}

View File

@@ -0,0 +1,38 @@
package com.jeequan.jeepay.pay.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.YsfBarOrderRQ;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 云闪付 条码支付 controller
*
* @author pangxiaoyu
*
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class YsfBarOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/ysfBarOrder")
public ApiRes aliBarOrder(){
//获取参数 & 验证
YsfBarOrderRQ bizRQ = getRQByWithMchSign(YsfBarOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.YSF_BAR, bizRQ);
}
}

View File

@@ -0,0 +1,38 @@
package com.jeequan.jeepay.pay.ctrl.payorder.payway;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.YsfJsapiOrderRQ;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 云闪付 jsapi支付 controller
*
* @author pangxiaoyu
*
* @date 2021/6/8 17:25
*/
@Slf4j
@RestController
public class YsfJsapiOrderController extends AbstractPayOrderController {
/**
* 统一下单接口
* **/
@PostMapping("/api/pay/ysfJsapiOrder")
public ApiRes aliJsapiOrder(){
//获取参数 & 验证
YsfJsapiOrderRQ bizRQ = getRQByWithMchSign(YsfJsapiOrderRQ.class);
// 统一下单接口
return unifiedOrder(CS.PAY_WAY_CODE.YSF_JSAPI, bizRQ);
}
}

View File

@@ -0,0 +1,136 @@
package com.jeequan.jeepay.pay.ctrl.qr;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IChannelUserService;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.ChannelUserIdRQ;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.core.utils.StringKit;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 商户获取渠道用户ID接口
*
* @author terrfly
*
* @date 2021/6/8 17:27
*/
@RestController
@RequestMapping("/api/channelUserId")
public class ChannelUserIdController extends AbstractPayOrderController {
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private SysConfigService sysConfigService;
/** 重定向到微信地址 **/
@MethodLog(remark = "获取渠道用户IDAPI")
@RequestMapping("/jump")
public void jump() throws Exception {
//获取请求数据
ChannelUserIdRQ rq = getRQByWithMchSign(ChannelUserIdRQ.class);
String ifCode = "AUTO".equalsIgnoreCase(rq.getIfCode()) ? getIfCodeByUA() : rq.getIfCode();
// 获取接口
IChannelUserService channelUserService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "ChannelUserService", IChannelUserService.class);
if(channelUserService == null){
throw new BizException("不支持的客户端");
}
if(!StringKit.isAvailableUrl(rq.getRedirectUrl())){
throw new BizException("跳转地址有误!");
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("mchNo", rq.getMchNo());
jsonObject.put("appId", rq.getAppId());
jsonObject.put("extParam", rq.getExtParam());
jsonObject.put("ifCode", ifCode);
jsonObject.put("redirectUrl", rq.getRedirectUrl());
//回调地址
String callbackUrl = sysConfigService.getDBApplicationConfig().genMchChannelUserIdApiOauth2RedirectUrlEncode(jsonObject);
//获取商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(rq.getMchNo(), rq.getAppId(),null);
String redirectUrl = channelUserService.buildUserRedirectUrl(callbackUrl, mchAppConfigContext).getRedirectUrl();
response.sendRedirect(redirectUrl);
}
/** 回调地址 **/
@RequestMapping("/oauth2Callback/{aesData}")
public void oauth2Callback(@PathVariable("aesData") String aesData) throws Exception {
JSONObject callbackData = JSON.parseObject(JeepayKit.aesDecode(aesData));
String mchNo = callbackData.getString("mchNo");
String appId = callbackData.getString("appId");
String ifCode = callbackData.getString("ifCode");
String extParam = callbackData.getString("extParam");
String redirectUrl = callbackData.getString("redirectUrl");
// 获取接口
IChannelUserService channelUserService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "ChannelUserService", IChannelUserService.class);
//获取商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(mchNo, appId,null);
//获取渠道用户ID
String pageType = null;
if(CS.IF_CODE.WXPAY.equals(ifCode)){
pageType = QRCodeParams.PAGE_TYPE_WECHAT_H5;
}
if(CS.IF_CODE.ALIPAY.equals(ifCode)){
pageType = QRCodeParams.PAGE_TYPE_ALIPAY_H5;
}
String channelUserId = channelUserService.getChannelUserId(pageType, getReqParamJSON(), mchAppConfigContext, ifCode);
//同步跳转
JSONObject appendParams = new JSONObject();
appendParams.put("appId", appId);
appendParams.put("channelUserId", channelUserId);
appendParams.put("extParam", extParam);
// Fortify 扫描软件可忽略该问题: 因为所有的redirectUrl 全部来自三方(比如微信)固定格式。
response.sendRedirect(StringKit.appendUrlQuery(redirectUrl, appendParams));
}
/** 根据UA获取支付接口 */
private String getIfCodeByUA() {
String ua = request.getHeader("User-Agent");
// 无法识别扫码客户端
if (StringUtils.isBlank(ua)) {
return null;
}
if(ua.contains("Alipay")) {
return CS.IF_CODE.ALIPAY; //支付宝服务窗支付
}else if(ua.contains("MicroMessenger")) {
return CS.IF_CODE.WXPAY;
}
return null;
}
}

View File

@@ -0,0 +1,992 @@
package com.jeequan.jeepay.pay.ctrl.qr;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.components.mq.model.PushWxMpMsgMQ;
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.openapi.ParamException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.device.IPrinterService;
import com.jeequan.jeepay.core.interfaces.device.ISpeakerService;
import com.jeequan.jeepay.core.interfaces.paychannel.IChannelUserService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.AppletResult;
import com.jeequan.jeepay.core.model.DBMarketingConfig;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.device.PayOrderInfo4Device;
import com.jeequan.jeepay.core.model.oauth2.AlipayOauth2Params;
import com.jeequan.jeepay.core.model.oauth2.WxpayOauth2Params;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelUserInfoMsg;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.*;
import com.jeequan.jeepay.core.utils.*;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.service.ChannelOrderReissueService;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.pay.service.PayOrderMarketReissueService;
import com.jeequan.jeepay.service.impl.*;
import com.jeequan.jeepay.thirdparty.service.wxmp.WxTgAppletService;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 聚合码支付二维码收银台controller
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/6/8 17:27
*/
@RestController
@RequestMapping("/api/cashier")
public class QrCashierController extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private SysConfigService sysConfigService;
@Autowired private PayMchNotifyService payMchNotifyService;
@Autowired private MchQrcodeCardService mchQrcodeCardService;
@Autowired private MchStoreDeviceService mchStoreDeviceService;
@Autowired private MchConfigService mchConfigService;
@Autowired private MchQrcDeviceRelaService mchQrcDeviceRelaService;
@Autowired private MchPayPassageService mchPayPassageService;
@Autowired private MchStoreService mchStoreService;
@Autowired private SysUserService sysUserService;
@Autowired private WxTgAppletService wxTgAppletService;
@Autowired private ChannelOrderReissueService channelOrderReissueService;
@Autowired private RedPacketAccountService redPacketAccountService;
@Autowired private RedPacketRuleService redPacketRuleService;
@Autowired private RedPacketInfoService redPacketInfoService;
@Autowired private PayOrderMarketReissueService payOrderMarketReissueService;
@Autowired private CacheTkService cacheTkService;
@Autowired private MchVideoCacheService mchVideoCacheService;
@Autowired private RouteService routeService;
/**
* 返回 oauth2【获取uerId跳转地址】
* **/
@PostMapping("/redirectUrl")
public ApiRes redirectUrl(){
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
//码牌
if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC){
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if(mchQrCode == null){
throw new BizException("当前码牌已被删除,无法使用,请更换新的码牌进行支付");
}
if(mchQrCode.getBindState() != CS.YES){
throw new BizException("当前码牌暂未绑定门店或暂未开启使用状态,请绑定后重试");
}
// 空码 跳转到注册页面
if(mchQrCode.getBindState() != CS.YES){
String mchRegisterUrl = String.format("%s/register?"+JeepayKit.TOKEN_KEY+"=%s", sysConfigService.getDBApplicationConfig().getMchSiteUrl(), getToken());
String redirectUrl;
if (StringUtils.isNotEmpty(mchQrCode.getAgentNo())) {
// 如果已划拨到服务商,携带服务商下默认用户的邀请码
SysUserEntity sysUserEntity = sysUserService.getBaseMapper().selectOne(SysUserEntity.gw()
.eq(SysUserEntity::getSysType, CS.SYS_ROLE_TYPE.AGENT)
.eq(SysUserEntity::getBelongInfoId, mchQrCode.getAgentNo())
.orderByAsc(SysUserEntity::getCreatedAt)
.last("limit 1")
);
redirectUrl = String.format("%s&c=%s", mchRegisterUrl, sysUserEntity.getInviteCode());
} else {
redirectUrl = mchRegisterUrl;
}
JSONObject result = JsonKit.newJson("redirectFlag", true);
result.put("redirectUrl", redirectUrl);
return ApiRes.ok(result);
}
// 支付宝内 && wap支付 无需获取
if(QRCodeParams.PAGE_TYPE_ALIPAY_H5.equals(qrCodeParams.getPageType()) && CS.PAY_WAY_CODE.ALI_WAP.equals(mchQrCode.getAlipayWayCode())){
return ApiRes.ok(JsonKit.newJson("redirectFlag", false));
}
}
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, true);
//回调地址
String redirectUrlEncode = sysConfigService.getDBApplicationConfig().genOauth2RedirectUrlEncode(getToken());
//获取接口并返回数据
MutablePair<String, IChannelUserService> mutablePair = getServiceByWayCode(qrCodeParams.getPageType(), "ChannelUserService", mchAppConfigContext, IChannelUserService.class);
// String ifCode = mutablePair.getLeft();
IChannelUserService channelUserService = mutablePair.getRight();
JSONObject result = JsonKit.newJson("redirectFlag", true);
ChannelUserInfoMsg channelUserInfoMsg = channelUserService.buildUserRedirectUrl(redirectUrlEncode, mchAppConfigContext);
result.put("redirectUrl", channelUserInfoMsg.getRedirectUrl());
result.put("channelAppId", channelUserInfoMsg.getChannelAppId());
// 用户ID 缓存key 要求前端做缓存, 不存在说明: 不要求前端做缓存。
if(StringUtils.isNotEmpty(channelUserInfoMsg.getChannelAppId())){
// 规则: channelUserId_页面类型_渠道appId 不再设置jeepay系统的appId 同一个服务商不同商户也支持查询缓存userId
result.put("channelUserIdCacheKey", String.format("jeepayChannelUserId_%s_%s", qrCodeParams.getPageType(), channelUserInfoMsg.getChannelAppId()));
}
return ApiRes.ok(result);
}
/**
* 获取userId
* **/
@PostMapping("/channelUserId")
public ApiRes channelUserId() throws Exception {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
//获取商户配置信息
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, true);
JSONObject reqParam = getReqParamJSON();
// 获取响应结果类型纯粹为了做小程序兼容20221011 避免后端上线后,线上小程序报错) onlyChannelUserId channelUserJSON
String jeepayResType = StringUtils.defaultIfEmpty(reqParam.getString("jeepayResType"), "onlyChannelUserId");
if (StringUtils.isNotBlank(getAppUpIdentifier())) {
reqParam.put("appUpIdentifier", getAppUpIdentifier()); // 通过UA获取银联app标志
}
reqParam.put("clientIp", getClientIp());
//获取商户配置信息
MutablePair<String, IChannelUserService> mutablePair = getServiceByWayCode(qrCodeParams.getPageType(), "ChannelUserService", mchAppConfigContext, IChannelUserService.class);
String ifCode = mutablePair.getLeft();
IChannelUserService channelUserService = mutablePair.getRight();
String channelUserId = channelUserService.getChannelUserId(qrCodeParams.getPageType(), reqParam, mchAppConfigContext, ifCode);
logger.info("{}获取到channelUserId={}", qrCodeParams, channelUserId);
// 仅获取userId
if("onlyChannelUserId".equals(jeepayResType)){
return ApiRes.ok(channelUserId);
}else{ // 集合数据, 包含了 channelUserId, channelUserIdCacheKey
JSONObject result = new JSONObject();
result.put("channelUserId", channelUserId);
ChannelUserInfoMsg channelUserInfoMsg = channelUserService.buildUserRedirectUrl("", mchAppConfigContext);
// 用户ID 缓存key 要求前端做缓存, 不存在说明: 不要求前端做缓存。
if(StringUtils.isNotEmpty(channelUserInfoMsg.getChannelAppId())){
// 规则: channelUserId_页面类型_渠道appId 不再设置jeepay系统的appId 同一个服务商不同商户也支持查询缓存userId
result.put("channelUserIdCacheKey", String.format("jeepayChannelUserId_%s_%s", qrCodeParams.getPageType(), channelUserInfoMsg.getChannelAppId()));
}
return ApiRes.ok(result);
}
}
/**
* 获取订单支付信息
* **/
@PostMapping("/payOrderInfo")
public ApiRes payOrderInfo() throws Exception {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
//获取商户配置信息
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, false);
PayOrder resOrder = new PayOrder();
//强制备注默认为否
resOrder.addExt("isForceReamrk",false);
JSONObject randomOneVo = mchVideoCacheService.getRandomOneVo(mchAppConfigContext.getMchNo());
if(randomOneVo != null){
resOrder.addExt("video",randomOneVo);
}
// 订单类型 需要查询订单
if(qrCodeParams.getType() == QRCodeParams.TYPE_PAY_ORDER){
PayOrder payOrder = getPayOrder(false);
//门店名称
resOrder.setStoreName(StringUtils.isNotBlank(payOrder.getStoreName()) ? payOrder.getStoreName() : "");
resOrder.setMchName(StringUtils.isNotBlank(payOrder.getMchName()) ? payOrder.getMchName() : "");
resOrder.setAmount(payOrder.getAmount());
resOrder.setState(payOrder.getState());
resOrder.setReturnUrl(payMchNotifyService.createReturnUrl(payOrder, configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo()).getMchApp().getAppSecret()));
resOrder.addExt("autoPay", true); // 自动调起支付
resOrder.addExt("fixedFlag", true); //固定金额,不可变更。
// 商户自行创建的二维码
}else if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC || qrCodeParams.getType() == QRCodeParams.TYPE_QRC_DEVICE || qrCodeParams.getType() == QRCodeParams.TYPE_ROUTE){
if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC){
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if(mchQrCode == null){
throw new BizException("当前码牌已被删除,无法使用,请更换新的码牌进行支付");
}
if(mchQrCode.getQrcState() != CS.YES){
throw new BizException("当前码牌暂未绑定门店或暂未开启使用状态,请绑定后重试");
}
MchStore mchStore = mchStoreService.getById(mchQrCode.getStoreId());
if(mchStore != null && StringUtils.isNotBlank(mchStore.getStoreName())) {
//门店名称
resOrder.setStoreName(mchStore.getStoreName());
resOrder.setMchName(mchQrCode.getMchApplyName());
}else {
resOrder.setMchName(mchAppConfigContext.getMchInfo().getMchShortName());
}
if(mchQrCode.getFixedFlag() == CS.YES){
resOrder.setAmount(mchQrCode.getFixedPayAmount()); // 固定金额
resOrder.addExt("fixedFlag", true); //固定金额,不可变更。
}
resOrder.addExt("isForceReamrk",CS.YES == mchQrCode.getIsForceReamrk() ? true : false);
}
if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC_DEVICE){
Long amt = qrCodeParams.getAmt();
MchStoreDevice storeDevice = mchStoreDeviceService.getById(qrCodeParams.getId());
if(MchStoreDevice.DEVICE_BIND_TYPE_STORE == storeDevice.getBindType()){
MchStore store = mchStoreService.getById(storeDevice.getStoreId());
//门店名称
resOrder.setStoreName(store.getStoreName());
resOrder.setMchName(storeDevice.getMchApplyName());
resOrder.setStoreId(store.getStoreId());
if(amt != null){
resOrder.setAmount(amt); // 固定金额
resOrder.addExt("fixedFlag", true);
}
}else{
List<MchQrcDeviceRela> list = mchQrcDeviceRelaService.list(MchQrcDeviceRela.gw()
.eq(MchQrcDeviceRela::getDeviceId, storeDevice.getDeviceId()));
if(list.isEmpty()){
throw new BizException("设备绑定的码牌信息异常");
}
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(list.get(0).getQrcId());
MchStore mchStore = mchStoreService.getById(mchQrCode.getStoreId());
if(mchStore != null && StringUtils.isNotBlank(mchStore.getStoreName())) {
//门店名称
resOrder.setStoreName(mchStore.getStoreName());
resOrder.setMchName(mchQrCode.getMchApplyName());
resOrder.setStoreId(mchStore.getStoreId());
}else {
resOrder.setMchName(mchAppConfigContext.getMchInfo().getMchShortName());
}
if(amt != null){
resOrder.setAmount(amt); // 固定金额
resOrder.addExt("fixedFlag", true);
}else{
if(mchQrCode.getFixedFlag() == CS.YES){
resOrder.setAmount(mchQrCode.getFixedPayAmount()); // 固定金额
resOrder.addExt("fixedFlag", true); //固定金额,不可变更。
}
}
resOrder.addExt("isForceReamrk",CS.YES == mchQrCode.getIsForceReamrk() ? true : false);
}
}
}
return ApiRes.ok(ApiResBodyAdviceKit.procAndConvertJSON(resOrder));
}
/** 调起下单接口, 返回支付数据包 **/
@PostMapping("/pay")
public ApiRes pay() throws Exception {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
//获取商户配置信息
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, true);
if(mchAppConfigContext == null){
throw new BizException("商户或商户应用不存在");
}
if(mchAppConfigContext.getMchInfo() == null || mchAppConfigContext.getMchInfo().getState() != CS.YES){
throw new BizException("商户信息不存在或商户状态不可用");
}
// 封装下单参数
UnifiedOrderRQ rq = packagePayRQ(qrCodeParams);
rq.setIsMarketRed(Boolean.FALSE);
PayOrder payOrder = null;
// 订单类型 需要查询订单
if(qrCodeParams.getType() == QRCodeParams.TYPE_PAY_ORDER){
payOrder = getPayOrder(true);
// 码牌或设备二维码
}else if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC || qrCodeParams.getType() == QRCodeParams.TYPE_QRC_DEVICE || qrCodeParams.getType() == QRCodeParams.TYPE_ROUTE){
rq.setMchNo(mchAppConfigContext.getMchNo());
rq.setAppId(mchAppConfigContext.getAppId());
rq.setMchOrderNo(SeqKit.genMhoOrderId());
rq.setAmount(getRequiredAmountL("amount"));
rq.setCurrency("cny");
rq.setStoreId(getValString("storeId"));
JSONObject discount = getVal("discount",JSONObject.class);
rq.setDiscount(discount);
if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC){
MchQrcodeCard mchQrcodeCard = mchQrcodeCardService.getById(qrCodeParams.getId());
rq.setSubject(StringUtils.isNotBlank(mchQrcodeCard.getQrcAlias()) ? mchQrcodeCard.getQrcAlias() : "静态码支付");
rq.setBody(StringUtils.isNotBlank(mchQrcodeCard.getQrcAlias()) ? mchQrcodeCard.getQrcAlias() : "静态码支付");
rq.setQrcId(mchQrcodeCard.getQrcId());
rq.setStoreId(mchQrcodeCard.getStoreId());
}else if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC_DEVICE){
MchStoreDevice storeDevice = mchStoreDeviceService.getById(qrCodeParams.getId());
MchStore mchStore = mchStoreService.getById(storeDevice.getStoreId());
if(MchStoreDevice.DEVICE_BIND_TYPE_QRC == storeDevice.getBindType()){
List<MchQrcDeviceRela> list = mchQrcDeviceRelaService.list(MchQrcDeviceRela.gw()
.eq(MchQrcDeviceRela::getDeviceId, storeDevice.getDeviceId()));
if(list.isEmpty()){
throw new BizException("设备绑定的码牌信息异常");
}
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(list.get(0).getQrcId());
rq.setSubject(StringUtils.isNotBlank(mchQrCode.getQrcAlias()) ? mchQrCode.getQrcAlias() : "静态码支付");
rq.setBody(StringUtils.isNotBlank(mchQrCode.getQrcAlias()) ? mchQrCode.getQrcAlias() : "静态码支付");
rq.setQrcId(mchQrCode.getQrcId());
rq.setStoreId(mchQrCode.getStoreId());
}else{
rq.setSubject(mchStore.getStoreName());
rq.setBody(mchStore.getStoreName());
rq.setStoreId(mchStore.getStoreId());
}
// 设备信息
UnifiedOrderRQ.DeviceInfo deviceInfo = new UnifiedOrderRQ.DeviceInfo();
deviceInfo.setDeviceNo(storeDevice.getDeviceNo());
deviceInfo.setDeviceType(PayOrder.DEVICE_TYPE_QR_BOX_KXP);
deviceInfo.setProvider(storeDevice.getProvider());
rq.setDeviceInfo(JSONObject.toJSONString(deviceInfo));
}else{
String storeName = getValString("storeName");
if(StringUtils.isNotBlank(storeName)){
rq.setSubject(storeName);
rq.setBody(storeName);
}
}
if(StringUtils.isNotBlank(getValString("buyerRemark"))) {
rq.setBuyerRemark(getValString("buyerRemark"));
}
// 会员信息
if(StringUtils.isNotBlank(getValString("mbrId"))) {
rq.setMbrId(getValString("mbrId"));
}
if(StringUtils.isNotBlank(getValString("mbrTel"))) {
rq.setMbrTel(getValString("mbrTel"));
}
//判断是否开启了红包
Boolean isRedPacket = sysConfigService.getDbMarketPlatRedPacket();
if(isRedPacket){
DBMarketingConfig marketConfig = sysConfigService.getDBMarketingConfig();
if(StringUtils.isEmpty(marketConfig.getMchNo())){
throw new BizException("红包营销功能已开启,未设置红包分账方商户号,请在系统配置-营销配置里面填写");
}
rq.setIsMarketRed(Boolean.TRUE);
}
boolean checkStoreRedRule = redPacketRuleService.isCheckStoreRedRule(mchAppConfigContext.getMchNo(),rq.getStoreId());
if(isRedPacket && !checkStoreRedRule){
throw new BizException("当前门店未配置红包规则,请在运营中心-营销红包-红包规则中开启后再支付");
}
}
rq.setSignType(MchApp.SIGN_TYPE_MD5); // 设置默认签名方式为MD5
ApiRes apiRes = this.unifiedOrder(rq.getWayCode(), rq, payOrder,null);
logger.info("收银台下单返回支付数据包apiRes{}", JSON.toJSONString(apiRes));
return ApiRes.ok(apiRes);
}
/**
* 订单取消支付 语音播报
* **/
@PostMapping("/payCancelBoardCast")
public ApiRes payCancelBoardCast() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
String payOrderId = getValString("payOrderId");
payCancel(payOrderId);
return ApiRes.ok();
}
/**
* 微信小程序、微信公众号、支付宝服务窗、支付宝小程序 --- js调起支付后返回的内容
*/
@PostMapping("/payEventBoardCast")
public ApiRes payEventBoardCast() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
String payOrderId = getValString("payOrderId");
String resData = getValString("resData");
if (StringUtils.isBlank(payOrderId)) {
logger.error("调起微信、支付宝JSAPI收款上报数据缺失参数payOrderId");
return ApiRes.ok();
}
// 异步处理前端支付事件消息
SpringUtil.getBean(QrCashierController.class).asyncPayEventBoardCast(payOrderId, resData);
return ApiRes.ok();
}
/** 会员配置查询 **/
@GetMapping("/memberConfig")
public ApiRes getMemberConfig() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
// 获取到当前应用类型
String currentPageType = qrCodeParams.getPageType();
JSONObject result = new JSONObject();
result.put("memberIsOpen", false);
// 是否有会员模块
SysConfig memberConfig = sysConfigService.getById(SysConfigService.MEMBER_ENT_CONFIG);
// 支付宝WAP、自建二维码、动态聚合码不支持功能
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if (memberConfig != null && mchQrCode != null && mchQrCode.getQrcBelongType() == MchQrcodeCard.QRC_BELONG_TYPE_ISV_PRE
&& !CS.PAY_WAY_CODE.ALI_WAP.equals(mchQrCode.getAlipayWayCode())) {
// 是否开启会员模块
MchConfig memberModelConfig = mchConfigService.getByMchNoAndConfigKey(mchQrCode.getMchNo(), "memberModelState");
if (memberModelConfig != null && MchConfig.YES.equals(memberModelConfig.getConfigVal())) {
// 商户是否开启会员支付功能
MchConfig memberPayConfig = mchConfigService.getByMchNoAndConfigKey(mchQrCode.getMchNo(), "memberPayState");
if (memberPayConfig != null && MchConfig.YES.equals(memberPayConfig.getConfigVal())) {
result.put("memberIsOpen", true);
result.put("memberSiteUrl", memberConfig.getConfigVal());
}
}
}
return ApiRes.ok(result);
}
/** 获取配置信息 **/
@GetMapping("/config")
public ApiRes getConfig() {
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
JSONObject result = new JSONObject();
String configKey = getValString("configKey");
if(QRCodeParams.CONFIG_MARKETING_TYPE.equals(configKey)){
String channelUserId = getValStringRequired("channelUserId");
JSONObject redConfig = new JSONObject();
DBMarketingConfig dbMarketingConfig = sysConfigService.getDBMarketingConfig();
boolean isOpen = CS.YES == Byte.valueOf(dbMarketingConfig.getPlatRedPacket()) ? true : false;
redConfig.put("isOpen",isOpen);
if(isOpen){
String storeId = getValString("storeId");
RedPacketAccount account = redPacketAccountService.getByStorePayUserId(channelUserId, storeId);
redConfig.put("balance",account == null ? 0 : account.getBalance());
}
JSONObject config = JSON.parseObject(JSON.toJSONString(dbMarketingConfig));
config.put("isAdsOpen",CS.YES == Byte.valueOf(dbMarketingConfig.getAdsStatus()) ? true : false);
redConfig.putAll(config);
//检查配置
result.put(configKey,redConfig);
return ApiRes.ok(result);
}
return ApiRes.fail(ApiCodeEnum.PARAMS_ERROR);
}
private UnifiedOrderRQ packagePayRQ(QRCodeParams qrCodeParams){
String channelUserId = getValString("channelUserId");
if(QRCodeParams.TYPE_ROUTE != qrCodeParams.getType()){
if(StringUtils.isEmpty(channelUserId)){
String code = getValString("code");
if(StringUtils.isNotEmpty(code)){
try {
ApiRes apiRes = channelUserId();
if(apiRes.getData() instanceof String ){
channelUserId = apiRes.getData().toString();
}else if(apiRes.getData() instanceof JSONObject ){
channelUserId = ((JSONObject)apiRes.getData()).getString("channelUserId");
}
}catch (Exception e){
throw new ParamException("用户信息获取异常");
}
}else{
throw new ParamException("非法请求");
}
}
}
// 是否商户自己的小程序调起的
Byte isUseSubmchAccount = getValByteDefault("isUseSubmchAccount", CS.NO);
// 获取到当前应用类型
String currentPageType = qrCodeParams.getPageType();
// 支付宝内 && 自行创建二维码
if(QRCodeParams.PAGE_TYPE_ALIPAY_H5.equals(currentPageType) && qrCodeParams.getType() == QRCodeParams.TYPE_QRC){
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
if(CS.PAY_WAY_CODE.ALI_WAP.equals(mchQrCode.getAlipayWayCode())){ //wap支付 无需获取
return new AliWapOrderRQ();
}
}
if(currentPageType.equals(QRCodeParams.PAGE_TYPE_ALIPAY_H5) ){
AliJsapiOrderRQ rq = new AliJsapiOrderRQ();
rq.setBuyerUserId(channelUserId);
return rq;
}else if( currentPageType.equals(QRCodeParams.PAGE_TYPE_ALIPAY_LITE)) {
AliLiteOrderRQ rq = new AliLiteOrderRQ();
rq.setBuyerUserId(channelUserId);
return rq;
}else if(currentPageType.equals(QRCodeParams.PAGE_TYPE_WECHAT_H5) ){
WxJsapiOrderRQ rq = new WxJsapiOrderRQ();
rq.setOpenid(channelUserId);
return rq;
}else if(currentPageType.equals(QRCodeParams.PAGE_TYPE_WECHAT_LITE) ){
WxLiteOrderRQ rq = new WxLiteOrderRQ();
rq.setOpenid(channelUserId);
rq.setIsSubOpenId(isUseSubmchAccount); // 是否 商户自己的小程序调起
return rq;
}else if(currentPageType.equals(QRCodeParams.PAGE_TYPE_YSFPAY_H5) ){
YsfJsapiOrderRQ rq = new YsfJsapiOrderRQ();
rq.setUserId(channelUserId);
return rq;
}
return null;
}
private String getToken(){
String token = request.getHeader(JeepayKit.TOKEN_KEY);
if(StringUtils.isEmpty(token)) {
throw new BizException("获取二维码参数失败");
}
return token;
}
private String getCurrentPageType(){
String pageType = request.getHeader("pageType");
if(StringUtils.isEmpty(pageType)) {
throw new BizException("不支持的客户端");
}
return pageType;
}
private PayOrder getPayOrder(boolean checkState){
CacheTk cache = cacheTkService.getById(getToken());
PayOrder payOrder = payOrderService.getById(cache.getSourceId());
if(checkState == false) {
return payOrder;
}
if(payOrder == null) {
throw new BizException("订单不存");
}
if(payOrder.getState() == PayOrder.STATE_SUCCESS) {
throw new BizException("订单已支付");
}
if(payOrder.getState() != PayOrder.STATE_INIT && payOrder.getState() != PayOrder.STATE_ING) {
throw new BizException("订单状态不正确");
}
return payOrder;
}
private MchAppConfigContext getQrParamsMchAppConfigContext(QRCodeParams qrCodeParams, boolean checkState){
// 订单
if(qrCodeParams.getType() == QRCodeParams.TYPE_PAY_ORDER){
PayOrder payOrder = getPayOrder(checkState);
return configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo());
// 商户自建二维码
}else if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC){
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(qrCodeParams.getId());
return configContextQueryService.queryMchInfoAndAppInfoV2(mchQrCode.getMchNo(), mchQrCode.getAppId(),mchQrCode.getMchApplyId());
// 商户设备二维码
}else if(qrCodeParams.getType() == QRCodeParams.TYPE_QRC_DEVICE){
MchStoreDevice storeDevice = mchStoreDeviceService.getById(qrCodeParams.getId());
if(storeDevice == null){
throw new BizException("设备信息有误");
}
if(storeDevice.getBindState() == MchStoreDevice.NO_BIND){
throw new BizException("当前设备未绑定或者设备已解绑");
}
if(storeDevice.getBindType() == MchStoreDevice.DEVICE_BIND_TYPE_STORE){
MchStore store = mchStoreService.getById(storeDevice.getStoreId());
if(store == null){
throw new BizException("设备绑定的门店信息异常");
}
return configContextQueryService.queryMchInfoAndAppInfoV2(storeDevice.getMchNo(), storeDevice.getAppId(),store.getMchApplyId());
}else if (storeDevice.getBindType() == MchStoreDevice.DEVICE_BIND_TYPE_QRC){
List<MchQrcDeviceRela> list = mchQrcDeviceRelaService.list(MchQrcDeviceRela.gw()
.eq(MchQrcDeviceRela::getDeviceId, storeDevice.getDeviceId()));
if(list.isEmpty()){
throw new BizException("设备绑定的码牌信息异常");
}
MchQrcodeCard mchQrCode = mchQrcodeCardService.getById(list.get(0).getQrcId());
return configContextQueryService.queryMchInfoAndAppInfoV2(mchQrCode.getMchNo(), mchQrCode.getAppId(),null);
}
}
throw new BizException("二维码参数有误");
}
private QRCodeParams tokenConvert(){
CacheTk cacheTk = cacheTkService.getById(getToken());
if(cacheTk == null){
throw new BizException("二维码参数有误");
}
QRCodeParams qrCodeParams = new QRCodeParams(cacheTk.getSourceId(),cacheTk.getType(),cacheTk.getAmount());
qrCodeParams.setPageType(getCurrentPageType());
qrCodeParams.setTk(cacheTk.getTk());
return qrCodeParams;
}
private <T> MutablePair<String, T> getServiceByWayCode(String currentPageType, String serviceSuffix, MchAppConfigContext mchAppConfigContext, Class<T> cls){
if( QRCodeParams.PAGE_TYPE_ALIPAY_LITE.equals(currentPageType) ||QRCodeParams.PAGE_TYPE_ALIPAY_H5.equals(currentPageType) ){
return MutablePair.of(CS.IF_CODE.ALIPAY, SpringBeansUtil.getBean(CS.IF_CODE.ALIPAY + serviceSuffix, cls));
}else if( QRCodeParams.PAGE_TYPE_WECHAT_LITE.equals(currentPageType) ||QRCodeParams.PAGE_TYPE_WECHAT_H5.equals(currentPageType) ){
return MutablePair.of(CS.IF_CODE.WXPAY, SpringBeansUtil.getBean(CS.IF_CODE.WXPAY + serviceSuffix, cls));
}
// 云闪付JSAPI根据支付通道配置获取ifcode
else if( QRCodeParams.PAGE_TYPE_YSFPAY_H5.equals(currentPageType)){
// 根据支付方式, 查询出 该商户 可用的支付接口
MchPayPassage mchPayPassage = mchPayPassageService.findMchPayPassage(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), CS.PAY_WAY_CODE.YSF_JSAPI);
if(mchPayPassage == null){
throw new BizException("商户应用不支持该支付方式");
}
return MutablePair.of(mchPayPassage.getIfCode(), SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(mchPayPassage.getIfCode()) + serviceSuffix, cls));
}
return null;
}
@Async
public void asyncPayEventBoardCast(String payOrderId, String resData) {
PayOrder payOrder = payOrderService.getById(payOrderId);
if (payOrder == null) {
logger.error("调起微信、支付宝JSAPI收款上报数据订单不存在payOrderId={}", payOrderId);
return ;
}
if (StringUtils.isNotBlank(resData)) {
JSONObject resJSON = JSONObject.parseObject(resData);
logger.info("订单payOrderId={}支付方式payway={}调起JSAPI收款后返回resData={}", payOrderId, payOrder.getWayCode(), resData);
// 取消支付,异步语音播报
// 支付宝返回6001 微信H5返回 get_brand_wcpay_request:cancel 微信小程序返回 requestPayment:fail cancel
if ((resJSON.getInteger("resultCode") != null && resJSON.getInteger("resultCode") == 6001)
|| "get_brand_wcpay_request:cancel".equalsIgnoreCase(resJSON.getString("err_msg"))
|| "requestPayment:fail cancel".equalsIgnoreCase(resJSON.getString("errMsg"))
) {
payCancel(payOrderId);
}else{
payOrderMarketReissueService.marketReissue(payOrder);
}
}
}
/**
* 处理取消支付事件
* @param payOrderId
*/
public void payCancel(String payOrderId) {
String logPrefix = "【订单取消支付防逃单】";
logger.info("开始处理{}", logPrefix);
if (StringUtils.isBlank(payOrderId)) {
logger.error("{}缺失参数payOrderId", logPrefix);
return ;
}
PayOrder payOrder = payOrderService.getById(payOrderId);
if (payOrder == null) {
logger.error("{}订单不存在payOrderId={}", logPrefix, payOrderId);
return ;
}
if (payOrder.getQrcId() == null && StringUtils.isEmpty(payOrder.getDeviceNo())) {
logger.error("{}非扫码牌支付payOrderId={}", logPrefix, payOrderId);
return ;
}
// MchConfig mchConfig = mchConfigService.getByMchNoAndConfigKey(payOrder.getMchNo(), "qrcEscaping");
// if (mchConfig == null || !mchConfig.getConfigVal().equals(MchConfig.YES)) {
// logger.error("{}商户未启用该功能payOrderId={}", logPrefix, payOrderId);
// return ;
// }
// 消息内容
PayOrderInfo4Device payOrderInfo = new PayOrderInfo4Device();
payOrderInfo.setAmount(payOrder.getAmount());
payOrderInfo.setPayOrderId(String.valueOf(DateKit.currentTimeMillis()));
payOrderInfo.setWayCodeType(PayOrderInfo4Device.BOARD_TYPE_CANCEL); // 取消收款播报
try {
// 查询云打印设备
List<JSONObject> printList = mchStoreDeviceService.selectAvailableDeviceList(payOrder.getMchNo(), payOrder.getStoreId(),
Arrays.asList( DeviceProvideConfig.DEVICE_TYPE_PRINTER));
// 查询云喇叭设备
List<JSONObject> speakList = mchStoreDeviceService.selectAvailableDeviceList(payOrder.getMchNo(), payOrder.getStoreId(),
Arrays.asList(DeviceProvideConfig.DEVICE_TYPE_SPEAKER));
if (CollUtil.isNotEmpty(speakList)) {
// 绑定类型为 【门店】 或 【已绑定下单码牌】
speakList = speakList.stream()
.filter(item -> {
if (item.getByte("bindType") == MchStoreDevice.DEVICE_BIND_TYPE_STORE) {
return true;
}else if (item.getByte("bindType") == MchStoreDevice.DEVICE_BIND_TYPE_QRC) {
MchQrcDeviceRela rela = mchQrcDeviceRelaService.getOne(MchQrcDeviceRela.gw()
.eq(MchQrcDeviceRela::getDeviceId, item.getString("deviceId"))
.eq(MchQrcDeviceRela::getQrcId, payOrder.getQrcId())
);
if (rela != null) {
return true;
}
}
return false;
})
.collect(Collectors.toList());
}
// 循环调用云喇叭设备播报
for (JSONObject deviceParams : speakList) {
logger.info("{}要播报的云喇叭设备ID{}", logPrefix, deviceParams.getString("deviceId"));
// 设备类型
String provider = deviceParams.getString("provider");
ISpeakerService speakerService = SpringBeansUtil.getBean(provider + "SpeakerService", ISpeakerService.class);
if (null == speakerService) {
continue;
}
try {
speakerService.sendCustomMsg(deviceParams, payOrderInfo);
logger.info("{}云喇叭播报成功设备厂商provider={}, payOderId={}", logPrefix, provider, payOrder.getPayOrderId());
}catch (Exception e) {
logger.error("{}云喇叭播报失败设备厂商provider={}, payOderId={}", logPrefix, provider, payOrder.getPayOrderId(), e);
}
}
// 循环调用云打印设备播报
for (JSONObject deviceParams : printList) {
logger.info("{}要播报的云打印设备ID{}", logPrefix, deviceParams.getString("deviceId"));
// 设备类型
String provider = deviceParams.getString("provider");
IPrinterService printerService = SpringBeansUtil.getBean(provider + "PrinterService", IPrinterService.class);
if (null == printerService) {
continue;
}
try {
printerService.sendCustomMsg(deviceParams, payOrderInfo);
logger.info("{}云打印防逃单播报成功设备厂商provider={}, payOderId={}", logPrefix, provider, payOrder.getPayOrderId());
}catch (Exception e) {
logger.error("{}云打印防逃单播报失败设备厂商provider={}, payOderId={}", logPrefix, provider, payOrder.getPayOrderId(), e);
}
}
} catch (Exception e) {
logger.error("error", e);
}
}
/** 通过UA获取银联app标志 **/
private static final String APPUPIDENTIFIER_REGEX = "UnionPay\\/\\d{1,2}\\.\\d{1,2} [A-Za-z0-9]+";
private String getAppUpIdentifier(){
Matcher matcher = Pattern.compile(APPUPIDENTIFIER_REGEX).matcher(request.getHeader("User-Agent"));
if(matcher.find()){
return matcher.group();
}
return null;
}
@RequestMapping(value = "toAppletPayment")
public ApiRes toAppletPayment(){
QRCodeParams qrCodeParams = tokenConvert();
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, true);
PayOrder payOrder = getPayOrder(true);
AppletResult result = wxTgAppletService.toAppletPayment(payOrder,mchAppConfigContext,qrCodeParams);
return ApiRes.ok(result);
}
/**
* 查询订单状态
* @return
*/
@RequestMapping(value = "orderStatus")
public ApiRes getOrderStatus(){
QRCodeParams qrCodeParams = tokenConvert();
PayOrder order = payOrderService.getById(qrCodeParams.getId());
if(order == null){
return ApiRes.fail(ApiCodeEnum.SYS_OPERATION_FAIL_SEARCH);
}
//订单支付中的状态去查询订单实际支付状态
if(PayOrder.STATE_ING == order.getState()){
//查询支付接口是否存在
ChannelRetMsg retMsg = channelOrderReissueService.processPayOrder(order);
if(retMsg != null){
if(retMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
order.setState(PayOrder.STATE_SUCCESS);
}if(retMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL){
order.setState(PayOrder.STATE_FAIL);
order.setErrMsg(retMsg.getChannelErrMsg());
}
}
}
return ApiRes.ok(order);
}
/**
* 输入金额计算优惠
* @return
*/
@GetMapping(value = "calcDiscount")
public ApiRes calcDiscount(){
QRCodeParams qrCodeParams = tokenConvert();
MchAppConfigContext mchAppConfigContext = getQrParamsMchAppConfigContext(qrCodeParams, true);
JSONObject result = new JSONObject();
String channelUserId = getValString("channelUserId");
String storeId = getValString("storeId");
Long amount = getAmountL("amount");
result.put("findAmt",amount);
result.put("discountAmt",BigDecimal.ZERO);
//判断是否开启了红包
RedPacketRule ruleByMchStrore = redPacketRuleService.getRuleByMchStrore(mchAppConfigContext.getMchNo(), storeId);
if(ruleByMchStrore != null && amount != null){
RedPacketAccount account = redPacketAccountService.getByStorePayUserId(channelUserId, storeId);
if(account != null){
BigDecimal platScale = ruleByMchStrore.getPlatScale().compareTo(BigDecimal.TEN) >= 0 ? ruleByMchStrore.getPlatScale().divide(BigDecimal.valueOf(100)) : ruleByMchStrore.getPlatScale().divide(BigDecimal.TEN);
BigDecimal exAmt = new BigDecimal(amount).multiply(platScale).setScale(0, RoundingMode.DOWN);
BigDecimal discountAmt = new BigDecimal(amount).subtract(exAmt);
if(account.getBalance() >= discountAmt.longValue()){
result.put("discountAmt",discountAmt.longValue());
result.put("findAmt",amount - discountAmt.longValue());
}else{
result.put("discountAmt",account.getBalance());
result.put("findAmt",amount - account.getBalance());
}
}
result.put("discountScale",ruleByMchStrore.getPlatScale());
}
return ApiRes.ok(result);
}
/**
* 获取支付成功后的红包详情
* @return
*/
@GetMapping(value = "/getDiscountDetail/{payOrderId}")
public ApiRes getDiscountDetail(@PathVariable("payOrderId") String payOrderId){
QRCodeParams qrCodeParams = tokenConvert();
PayOrder payOrder = payOrderService.getById(payOrderId);
if(payOrder == null){
return ApiRes.fail(ApiCodeEnum.PARAMS_ERROR,"订单数据异常");
}
JSONObject result = new JSONObject(2);
result.put("order",payOrder);
RedPacketInfo redPacketInfo = redPacketInfoService.getById(payOrderId);
result.put("redPacketInfo",redPacketInfo);
return ApiRes.ok(result);
}
/**
* 获取路由
* @return
*/
@PostMapping("/doRoute")
public ApiRes doRoute(){
// token转换对象
QRCodeParams qrCodeParams = tokenConvert();
qrCodeParams.setAmt(getRequiredAmountL("amount"));
String remark = getValString("remark");
String pageType = qrCodeParams.getPageType().contains("wechat") ? CS.PAY_WAY_CODE_TYPE.WECHAT : qrCodeParams.getPageType().contains("alipay") ? CS.PAY_WAY_CODE_TYPE.ALIPAY : null;
RouteManage manage = routeService.doRoute(qrCodeParams.getId(),qrCodeParams.getAmt(),pageType);
//获取商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(manage.getMchNo(), manage.getAppid(),manage.getApplyId());
if(CS.PAY_WAY_CODE_TYPE.WECHAT.equals(pageType)){
WxpayOauth2Params oauth2Params = (WxpayOauth2Params)configContextQueryService.queryIsvOauth2Params(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.WXPAY);
if(oauth2Params == null){
return ApiRes.fail(ApiCodeEnum.CUSTOM_FAIL,"服务商微信Oauth2授权参数未配置");
}
if(CS.NO == manage.getRoute().getPayMethod() && StringUtils.isEmpty(oauth2Params.getAppId())){
return ApiRes.fail(ApiCodeEnum.CUSTOM_FAIL,"服务商微信Oauth2授权APPID参数未配置");
}
if(CS.YES == manage.getRoute().getPayMethod() && StringUtils.isEmpty(oauth2Params.getLiteAppId())){
return ApiRes.fail(ApiCodeEnum.CUSTOM_FAIL,"服务商微信Oauth2授权小程序APPID参数未配置");
}
}else if(CS.PAY_WAY_CODE_TYPE.ALIPAY.equals(pageType)){
AlipayOauth2Params oauth2Params = (AlipayOauth2Params)configContextQueryService.queryIsvOauth2Params(mchAppConfigContext.getMchApplyment().getIsvNo(), CS.IF_CODE.ALIPAY);
if(oauth2Params == null){
return ApiRes.fail(ApiCodeEnum.CUSTOM_FAIL,"服务商支付宝Oauth2授权参数未配置");
}
if(CS.NO == manage.getRoute().getPayMethod() && StringUtils.isEmpty(oauth2Params.getAppId())){
return ApiRes.fail(ApiCodeEnum.CUSTOM_FAIL,"服务商支付宝Oauth2授权APPID参数未配置");
}
if(CS.YES == manage.getRoute().getPayMethod() && StringUtils.isEmpty(oauth2Params.getLiteParams().getAppId())){
return ApiRes.fail(ApiCodeEnum.CUSTOM_FAIL,"服务商支付宝Oauth2授权小程序APPID参数未配置");
}
}else{
return ApiRes.fail(ApiCodeEnum.CUSTOM_FAIL,"请使用支付宝或微信支付");
}
// 封装下单参数
UnifiedOrderRQ rq = packagePayRQ(qrCodeParams);
rq.setMchNo(mchAppConfigContext.getMchNo());
rq.setAppId(mchAppConfigContext.getAppId());
rq.setMchOrderNo(SeqKit.genMhoOrderId());
rq.setAmount(qrCodeParams.getAmt());
rq.setCurrency("cny");
rq.setStoreId(manage.getStoreId());
rq.setSubject(mchAppConfigContext.getMchApplyment().getMchShortName());
rq.setBody(mchAppConfigContext.getMchApplyment().getMchShortName());
rq.setIsRoute(true);
rq.setTk(qrCodeParams.getTk());
rq.setBuyerRemark(remark);
rq.setSignType(MchApp.SIGN_TYPE_MD5); // 设置默认签名方式为MD5
ApiRes apiRes = this.unifiedOrder(rq.getWayCode(), rq, null,manage);
return apiRes;
}
}

View File

@@ -0,0 +1,102 @@
package com.jeequan.jeepay.pay.ctrl.qr;
import cn.hutool.core.util.CharsetUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.DBApplicationConfig;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.db.entity.MchStore;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.SysAdvertConfig;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.service.impl.MchStoreService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.SysAdvertConfigService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* 微信点金计划controller
*
* @author zx
*
* @date 2022/7/1 17:27
*/
@Controller
@RequestMapping("/api/goldplan")
public class WxGoldPlanController extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private MchStoreService mchStoreService;
@Autowired private SysConfigService sysConfigService;
@Autowired private SysAdvertConfigService advertConfigService;
/**
* 点金计划 -- 商家小票链接地址
**/
@GetMapping("/custom")
public String goldplan(){
try {
JSONObject paramJSON = getReqParamJSON();
logger.info("点金计划微信请求参数:{}", paramJSON.toJSONString());
String platformMchOrderNo = paramJSON.getString("out_trade_no");
String subMchId = paramJSON.getString("sub_mch_id");
String checkCode = paramJSON.getString("check_code");
PayOrder payOrder = payOrderService.selectOneByPlatformMchOrderNo(platformMchOrderNo);
String transactionId = payOrder.getPlatformOrderNo();
String paySiteUrl = sysConfigService.getDBApplicationConfig().getPaySiteUrl();
paySiteUrl = paySiteUrl.endsWith("/") ? paySiteUrl : paySiteUrl + "/";
String md5Str = String.format(paySiteUrl + "api/goldplan/custom/?sub_mch_id=%s&out_trade_no=%s&transaction_id=%s", subMchId, platformMchOrderNo, transactionId);
String myCheckCode = JeepayKit.md5(md5Str, CharsetUtil.UTF_8);
if (!myCheckCode.equals(checkCode)) {
logger.info("点金计划微信checkCode={}系统checkCode={}", checkCode, myCheckCode);
throw new BizException("checkCode校验失败");
}
MchStore mchStore = mchStoreService.getById(payOrder.getStoreId());
request.setAttribute("amount", payOrder.getAmount());
request.setAttribute("mchShortName", mchStore.getStoreName());
request.setAttribute("subject", payOrder.getSubject());
// 商家小票跳转URL优先级默认取商户传的returnUrl当为空时取平台配置的点金计划支付后广告链接若平台未配置则隐藏页面上跳转按钮
if (StringUtils.isNotBlank(payOrder.getReturnUrl())) {
request.setAttribute("returnUrl", payOrder.getReturnUrl());
}else {
// 查询平台点金计划支付后广告
LambdaQueryWrapper<SysAdvertConfig> wrapper = SysAdvertConfig.gw();
wrapper.eq(SysAdvertConfig::getAdvertType, SysAdvertConfig.ADVERT_TYPE_AFTER);
wrapper.eq(SysAdvertConfig::getReleaseState, CS.YES);
wrapper.eq(SysAdvertConfig::getAppPlace, SysAdvertConfig.APP_PLACE_TOUCH);
wrapper.orderByDesc(SysAdvertConfig::getCreatedAt);
List<SysAdvertConfig> list = advertConfigService.list(wrapper);
if (list.size() > 0) {
request.setAttribute("returnUrl", list.get(0).getLinkUrl());
}else {
request.setAttribute("returnUrl", "");
}
}
return "goldplan/custom";
}catch (Exception e) {
logger.error("点金计划解析错误", e);
// 静态CDN地址
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
request.setAttribute("staticCdnHost", dbApplicationConfig.getStaticCdnHost());
request.setAttribute("errMsg", "订单解析错误");
return "goldplan/error";
}
}
}

View File

@@ -0,0 +1,133 @@
package com.jeequan.jeepay.pay.ctrl.refund;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IChannelRefundNoticeService;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.RefundOrder;
import com.jeequan.jeepay.pay.service.RefundOrderProcessService;
import com.jeequan.jeepay.service.impl.AccountInfoService;
import com.jeequan.jeepay.service.impl.RefundOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/*
* 渠道侧的退款通知入口Controller 【异步回调(doNotify) 】
*
* @author jmdhappy
*
* @date 2021/9/25 22:35
*/
@Slf4j
@Controller
public class ChannelRefundNoticeController extends AbstractCtrl {
@Autowired private RefundOrderService refundOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private RefundOrderProcessService refundOrderProcessService;
@Autowired private AccountInfoService accountInfoService;
/** 异步回调入口 **/
@ResponseBody
@RequestMapping(value= {"/api/refund/notify/{ifCode}", "/api/refund/notify/{ifCode}/{refundOrderId}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "refundOrderId", required = false) String urlOrderId){
String refundOrderId = null;
String logPrefix = "进入[" +ifCode+ "]退款回调urlOrderId["+ StringUtils.defaultIfEmpty(urlOrderId, "") + "] ";
log.info("===== {} =====" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询退款接口是否存在
IChannelRefundNoticeService refundNotifyService = SpringBeansUtil.getBean(ifCode + "ChannelRefundNoticeService", IChannelRefundNoticeService.class);
// 支付通道接口实现不存在
if(refundNotifyService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 解析订单号 和 请求参数
MutablePair<String, Object> mutablePair = refundNotifyService.parseParams(request, urlOrderId, IChannelRefundNoticeService.NoticeTypeEnum.DO_NOTIFY);
if(mutablePair == null){ // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
// 解析到订单号
refundOrderId = mutablePair.left;
log.info("{}, 解析数据为refundOrderId:{}, params:{}", logPrefix, refundOrderId, mutablePair.getRight());
if(StringUtils.isNotEmpty(urlOrderId) && !urlOrderId.equals(refundOrderId)){
log.error("{}, 订单号不匹配. urlOrderId={}, refundOrderId={} ", logPrefix, urlOrderId, refundOrderId);
throw new BizException("退款单号不匹配!");
}
//获取订单号 和 订单数据
RefundOrder refundOrder = refundOrderService.getById(refundOrderId);
// 订单不存在
if(refundOrder == null){
log.error("{}, 退款订单不存在. refundOrder={} ", logPrefix, refundOrder);
return refundNotifyService.doNotifyOrderNotExists(request);
}
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(refundOrder.getMchNo(), refundOrder.getAppId(),refundOrder.getMchExtNo());
//调起接口的回调判断
ChannelRetMsg notifyResult = refundNotifyService.doNotice(request, mutablePair.getRight(), refundOrder, mchAppConfigContext, IChannelRefundNoticeService.NoticeTypeEnum.DO_NOTIFY);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, notifyResult);
throw new BizException("处理回调事件异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
//如果是退款成功状态 则直接返回响应参数
if(RefundOrder.STATE_SUCCESS == refundOrder.getState()){
return notifyResult.getResponseEntity();
}
// 处理退款订单
boolean updateOrderSuccess = refundOrderProcessService.handleRefundOrder4Channel(notifyResult, refundOrder);
// 更新退款订单 异常
if(!updateOrderSuccess){
log.error("{}, updateOrderSuccess = {} ",logPrefix, updateOrderSuccess);
return refundNotifyService.doNotifyOrderStateUpdateFail(request);
}
log.info("===== {}, 订单通知完成。 refundOrderId={}, parseState = {} =====", logPrefix, refundOrderId, notifyResult.getChannelState());
return notifyResult.getResponseEntity();
} catch (BizException e) {
log.error("{}, refundOrderId={}, BizException", logPrefix, refundOrderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, refundOrderId={}, ResponseException", logPrefix, refundOrderId, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, refundOrderId={}, 系统异常", logPrefix, refundOrderId, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}

View File

@@ -0,0 +1,57 @@
package com.jeequan.jeepay.pay.ctrl.refund;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.refund.QueryRefundOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.refund.QueryRefundOrderRS;
import com.jeequan.jeepay.db.entity.RefundOrder;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.RefundOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 商户退款单查询controller
*
* @author terrfly
*
* @date 2021/6/17 15:20
*/
@Slf4j
@RestController
public class QueryRefundOrderController extends ApiController {
@Autowired private RefundOrderService refundOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
/**
* 查单接口
* **/
@MethodLog(remark = "查询退款订单API")
@RequestMapping("/api/refund/query")
public ApiRes queryRefundOrder(){
//获取参数 & 验签
QueryRefundOrderRQ rq = getRQByWithMchSign(QueryRefundOrderRQ.class);
if(StringUtils.isAllEmpty(rq.getMchRefundNo(), rq.getRefundOrderId())){
throw new BizException("mchRefundNo 和 refundOrderId不能同时为空");
}
RefundOrder refundOrder = refundOrderService.queryMchOrder(rq.getMchNo(), rq.getMchRefundNo(), rq.getRefundOrderId());
if(refundOrder == null){
throw new BizException("订单不存在");
}
QueryRefundOrderRS bizRes = QueryRefundOrderRS.buildByRefundOrder(refundOrder);
return ApiResKit.okWithSign(bizRes, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
}
}

View File

@@ -0,0 +1,201 @@
package com.jeequan.jeepay.pay.ctrl.refund;
import cn.hutool.core.date.DateUtil;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ChannelException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IDivisionService;
import com.jeequan.jeepay.core.interfaces.paychannel.IRefundService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.refund.RefundOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.refund.RefundOrderRS;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.core.utils.StringKit;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.db.entity.RefundOrder;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractRefundOrderController;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.pay.service.PayOrderDivisionRefundOrderCommonService;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.RefundOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/*
* 商户发起退款 controller
*
* @author terrfly
*
* @date 2021/6/16 15:54
*/
@Slf4j
@RestController
public class RefundOrderController extends AbstractRefundOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private RefundOrderService refundOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayOrderDivisionRefundOrderCommonService payOrderDivisionRefundOrderCommonService;
/** 申请退款 **/
@MethodLog(remark = "发起支付退款API")
@PostMapping("/api/refund/refundOrder")
public ApiRes refundOrder(){
//获取参数 & 验签
RefundOrderRQ rq = getRQByWithMchSign(RefundOrderRQ.class);
return this.refund(rq);
}
public ApiRes refund(RefundOrderRQ rq){
RefundOrder refundOrder = null;
try {
if(StringUtils.isAllEmpty(rq.getMchOrderNo(), rq.getPayOrderId())){
throw new BizException("mchOrderNo 和 payOrderId不能同时为空");
}
PayOrder payOrder = payOrderService.queryMchOrder(rq.getMchNo(), rq.getPayOrderId(), rq.getMchOrderNo());
if(payOrder == null){
throw new BizException("退款订单不存在");
}
if(payOrder.getRefundState() == PayOrder.REFUND_STATE_ALL || payOrder.getRefundAmount() >= payOrder.getAmount()){
throw new BizException("订单已全额退款,本次申请失败");
}
if(payOrder.getState() != PayOrder.STATE_SUCCESS && PayOrder.REFUND_STATE_ALL == payOrder.getRefundState()){
throw new BizException("当前订单已退款完成或订单状态不正确");
}
if(payOrder.getRefundAmount() + rq.getRefundAmount() > payOrder.getAmount()){
throw new BizException("申请金额超出订单可退款余额,请检查退款金额");
}
if(refundOrderService.count(RefundOrder.gw().eq(RefundOrder::getPayOrderId, payOrder.getPayOrderId()).eq(RefundOrder::getState, RefundOrder.STATE_ING)) > 0){
throw new BizException("支付订单具有在途退款申请,请稍后再试");
}
//全部退款金额 (退款订单表)
Long sumSuccessRefundAmount = refundOrderService.getBaseMapper().sumSuccessRefundAmount(payOrder.getPayOrderId());
if(sumSuccessRefundAmount >= payOrder.getAmount()){
throw new BizException("退款单已完成全部订单退款,本次申请失败");
}
if(sumSuccessRefundAmount + rq.getRefundAmount() > payOrder.getAmount()){
throw new BizException("申请金额超出订单可退款余额,请检查退款金额");
}
String mchNo = rq.getMchNo();
String appId = rq.getAppId();
// 校验退款单号是否重复
if(refundOrderService.count(RefundOrder.gw().eq(RefundOrder::getMchNo, mchNo).eq(RefundOrder::getMchRefundNo, rq.getMchRefundNo())) > 0){
throw new BizException("商户退款订单号["+rq.getMchRefundNo()+"]已存在");
}
if(StringUtils.isNotEmpty(rq.getNotifyUrl()) && !StringKit.isAvailableUrl(rq.getNotifyUrl())){
throw new BizException("异步通知地址协议仅支持http:// 或 https:// !");
}
//获取支付参数 (缓存数据) 和 商户信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(mchNo, appId,payOrder.getMchExtNo());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
MchInfo mchInfo = mchAppConfigContext.getMchInfo();
MchApp mchApp = mchAppConfigContext.getMchApp();
// 是否有退款权限
if (!mchInfo.getRefundMode().contains(MchInfo.REFUND_MODEL_API)) {
throw new BizException("无退款权限!");
}
//获取退款接口
IRefundService refundService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(payOrder.getIfCode()) + "RefundService", IRefundService.class);
if(refundService == null){
throw new BizException("当前通道不支持退款!");
}
refundOrder = genRefundOrder(rq, payOrder, mchInfo, mchApp);
refundService.preCheck(rq,refundOrder,payOrder);
//退款单入库 退款单状态:生成状态 此时没有和任何上游渠道产生交互。
refundOrderService.save(refundOrder);
IDivisionService divisionService = getDivisionService(payOrder);
// 退款业务发生事前 分账。
if(divisionService != null && divisionService.isSupport() && !divisionService.divisionRefundIsOrderRefundAfterProc()){
// 处理分账回退逻辑
payOrderDivisionRefundOrderCommonService.processDivisionRefund(refundOrder, mchAppConfigContext);
}
// 调起退款接口
ChannelRetMsg channelRetMsg = refundService.refund(rq, refundOrder, payOrder, mchAppConfigContext);
//处理退款单状态
this.processChannelMsg(channelRetMsg, refundOrder);
// 退款业务发生 事后 分账。 明确成功再退分。
if(divisionService != null && divisionService.isSupport() && divisionService.divisionRefundIsOrderRefundAfterProc()
&& channelRetMsg.getChannelState() != null && channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
// 处理分账回退逻辑
payOrderDivisionRefundOrderCommonService.processDivisionRefundByAfter(refundOrder);
}
RefundOrderRS bizRes = RefundOrderRS.buildByRefundOrder(refundOrder);
return ApiResKit.okWithSign(bizRes, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (ChannelException e) {
//处理上游返回数据
this.processChannelMsg(e.getChannelRetMsg(), refundOrder);
if(e.getChannelRetMsg().getChannelState() == ChannelRetMsg.ChannelState.SYS_ERROR ){
return ApiRes.customFail(e.getMessage());
}
RefundOrderRS bizRes = RefundOrderRS.buildByRefundOrder(refundOrder);
return ApiResKit.okWithSign(bizRes, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
private IDivisionService getDivisionService(PayOrder payOrder){
if(PayOrder.DIVISION_STATE_UNHAPPEN == payOrder.getDivisionMode()){
return null;
}
IDivisionService divisionService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(payOrder.getIfCode()) + "DivisionService", IDivisionService.class);
return divisionService;
}
}

View File

@@ -0,0 +1,114 @@
package com.jeequan.jeepay.pay.ctrl.repay;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.paychannel.IRepayNoticeService;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.AccountChangeInfo;
import com.jeequan.jeepay.db.entity.AccountFundInfo;
import com.jeequan.jeepay.service.impl.AccountChangeInfoService;
import com.jeequan.jeepay.service.impl.AccountFundInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* TODO
*
* @author crystal
* @date 2024/3/27 10:22
*/
@Controller
@Slf4j
public class ChannelRepayNoticeController extends AbstractCtrl {
@Autowired private AccountFundInfoService accountFundInfoService;
@ResponseBody
@RequestMapping(value= {"/api/channel/payment/notify/{ifCode}", "/api/channel/payment/notify/{ifCode}/{rid}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "rid", required = false) String urlRid){
String rid = null;
String logPrefix = "进入[" +ifCode+ "]打款回调urlRid["+ StringUtils.defaultIfEmpty(urlRid, "") + "] ";
log.info("打款回调:{}" , logPrefix);
try {
// 参数有误
if(StringUtils.isEmpty(ifCode)){
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询通知接口是否存在
IRepayNoticeService noticeService = SpringBeansUtil.getBean(ifCode + "RepayNoticeService", IRepayNoticeService.class);
// 提现通知接口实现不存在
if(noticeService == null){
log.error("{}, interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] interface not exists");
}
// 解析提现单号 和 请求参数
MutablePair<String, Object> mutablePair = noticeService.parseParams(request, urlRid);
if(mutablePair == null){
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!");
}
rid = mutablePair.left;
AccountFundInfo fundInfo = accountFundInfoService.getById(rid);
if(fundInfo == null){
log.error("{}, 数据不存在. rid={} ", logPrefix, rid);
return ResponseEntity.badRequest().body("data record not exists");
}
//解析到提现单号
log.info("{}, 解析数据为rid:{}, params:{}", logPrefix, rid, mutablePair.getRight());
//调起接口的回调判断
ChannelRetMsg retMsg = noticeService.doNotice(request,mutablePair.left, mutablePair.getRight());
// 返回null 表明出现异常, 无需处理通知下游等操作。
if(retMsg == null || retMsg.getChannelState() == null || retMsg.getResponseEntity() == null){
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ",logPrefix, retMsg);
//需要实现类自行抛出ResponseException, 不应该在这抛此异常。
throw new BizException("处理回调事件异常!");
}
boolean updateSuccess = true; //默认更新成功
// 提现单是 【提现中状态】
if(fundInfo.getState() == AccountFundInfo.PAYMENT) {
// 渠道提现单号
fundInfo.setChannelOrderId(retMsg.getChannelOrderId());
// 明确成功
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == retMsg.getChannelState()) {
updateSuccess = accountFundInfoService.updateIng2Success(rid, retMsg.getChannelOrderId());
//明确失败
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == retMsg.getChannelState()) {
updateSuccess = accountFundInfoService.updateIng2Fail(rid, retMsg.getChannelErrMsg());
}
else if(ChannelRetMsg.ChannelState.WAITING == retMsg.getChannelState()) {
updateSuccess = accountFundInfoService.updateChannelRemark(rid, retMsg.getChannelErrMsg());
}
}
// 更新提现单 异常
if(!updateSuccess){
log.error("{}, updateOrderSuccess = {} ",logPrefix, updateSuccess);
return ResponseEntity.badRequest().body("update error");
}
log.info("{}, 打款通知完成。 rid={}, parseState = {} =====", logPrefix, rid, retMsg.getChannelState());
return retMsg.getResponseEntity();
} catch (BizException e) {
log.error("{}, rid={}, BizException", logPrefix, rid, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, rid={}, ResponseException", logPrefix, rid, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, rid={}, 系统异常", logPrefix, rid, e);
return ResponseEntity.badRequest().body("error");
}
}
}

View File

@@ -0,0 +1,52 @@
package com.jeequan.jeepay.pay.ctrl.scanimg;
import com.jeequan.jeepay.core.model.QRCodeParams;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.db.entity.CacheTk;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.util.CodeImgUtil;
import com.jeequan.jeepay.service.impl.CacheTkService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/*
* jeepay 扫描图片生成器
*
* @author terrfly
*
* @date 2021/6/8 17:28
*/
@RestController
@RequestMapping("/api/scan")
public class ScanImgController extends AbstractPayOrderController {
@Resource
private SysConfigService sysConfigService;
@Resource
private CacheTkService cacheTkService;
/** 返回 图片地址信息 **/
@RequestMapping("/imgs/{aesStr}.png")
public void qrImgs(@PathVariable("aesStr") String aesStr) throws Exception {
if(aesStr.startsWith(SeqKit.TOKEN_PREFIX)){
CacheTk cacheTk = cacheTkService.getById(aesStr);
if(cacheTk.getType() == QRCodeParams.TYPE_ROUTE){
aesStr = sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/pages/route/index?"+JeepayKit.TOKEN_KEY+"="+cacheTk.getTk();
}else{
String extParams = "&price="+ AmountUtil.convertCent2Dollar(cacheTk.getAmount()) + "&order_no="+ cacheTk.getSourceId() + "&env=scan";
aesStr = sysConfigService.getDBApplicationConfig().getPaySiteUrl() + "/pages/payment/index?"+JeepayKit.TOKEN_KEY+"="+cacheTk.getTk() + extParams;
}
}else{
aesStr = JeepayKit.aesDecode(aesStr);
}
int width = getValIntegerDefault("width", 200);
int height = getValIntegerDefault("height", 200);
CodeImgUtil.writeQrCode(response.getOutputStream(), aesStr, width, height);
}
}

View File

@@ -0,0 +1,445 @@
package com.jeequan.jeepay.pay.ctrl.test;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.components.mq.model.PushWxMpMsgMQ;
import com.jeequan.jeepay.components.mq.model.ZftSettOrderMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.components.oss.ctrl.OssFileController;
import com.jeequan.jeepay.components.oss.model.MockMultipartFile;
import com.jeequan.jeepay.components.oss.model.OssFileConfig;
import com.jeequan.jeepay.converter.MchInfoConverter;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.IPayOrderQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.ITransferService;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.params.lklspay.LklspayIsvParams;
import com.jeequan.jeepay.core.model.params.yspay.YspayIsvParams;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.transfer.TransferOrderRQ;
import com.jeequan.jeepay.core.service.ISysConfigService;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.pay.service.PayOrderDivisionProcessService;
import com.jeequan.jeepay.pay.service.StatisticsService;
import com.jeequan.jeepay.pay.service.payorderpush.impl.PushWxMpTemplateMsgService;
import com.jeequan.jeepay.service.impl.*;
import com.jeequan.jeepay.thirdparty.channel.lklspay.LklspayKit;
import com.jeequan.jeepay.thirdparty.channel.lklspay.model.LklPayMethod;
import com.jeequan.jeepay.thirdparty.channel.yspay.model.ReqMethod;
import com.jeequan.jeepay.thirdparty.channel.yspay.utils.YspayKit;
import com.jeequan.jeepay.thirdparty.device.speaker.lklsSpeaker.LklsSpeakerService;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* TODO
*
* @author crystal
* @date 2023/12/25 17:12
*/
@RequestMapping(value = "test")
@RestController
@Slf4j
public class TestController {
@Autowired
private ConfigContextQueryService queryService;
@Autowired private PayOrderDivisionProcessService payOrderDivisionProcessService;
@Autowired
private TransferOrderService transferOrderService;
@Autowired
private StatisticsService statisticsService;
@Autowired
private MchStoreDeviceService mchStoreDeviceService;
@Autowired
private PushWxMpTemplateMsgService pushWxMpTemplateMsgService;
@Autowired
private PayOrderService payOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired
private MchApplymentService mchApplymentService;
@Autowired
private LklsSpeakerService lklsSpeakerService;
@Autowired
private MchInfoConverter mchInfoConverter;
@Autowired
private DeviceProvideConfigService deviceProvideConfigService;
@Autowired
private OssFileController ossFileController;
@Autowired private IMQSender mqSender;
@RequestMapping("/lkl/bind")
public void bind(){
JSONObject request = new JSONObject();
request.put("ver","1.0.0");
request.put("reqId", SeqKit.genMhoOrderId());
request.put("timestamp", DateUtil.format(new DateTime(), DatePattern.PURE_DATETIME_PATTERN));
JSONObject reqData = new JSONObject();
reqData.put("merId","8225210899900DS");
reqData.put("termId","D4039820");
reqData.put("sn","QR939200003565");
reqData.put("businessType","1");
reqData.put("shopName","测试门店");
reqData.put("source","LOAP");
reqData.put("merOrgNo","967080");
request.put("reqData",reqData);
String isvNo = "V1698312342";
String ifCode = CS.IF_CODE.LKLSPAY;
LklspayIsvParams isvParams = (LklspayIsvParams)queryService.queryIsvParams(isvNo, ifCode);
JSONObject jsonObject = LklspayKit.reqApi(LklPayMethod.Api.DEVICE_SPEAKER_BOX_ACTIVATE_URL, isvParams, JSON.toJSONString(request));
log.info("接口响应参数:{}",jsonObject);
}
@RequestMapping("/lkl/unBind")
public void unBind(){
JSONObject request = new JSONObject();
request.put("ver","1.0.0");
request.put("reqId", SeqKit.genMhoOrderId());
request.put("timestamp", DateUtil.format(new DateTime(), DatePattern.PURE_DATETIME_PATTERN));
JSONObject reqData = new JSONObject();
reqData.put("merId","8225210899900DS");
reqData.put("termId","D4039820");
reqData.put("sn","QR939200003565");
reqData.put("source","KSAAS");
request.put("reqData",reqData);
String isvNo = "V1698312342";
String ifCode = CS.IF_CODE.LKLSPAY;
LklspayIsvParams isvParams = (LklspayIsvParams)queryService.queryIsvParams(isvNo, ifCode);
JSONObject jsonObject = LklspayKit.reqApi(LklPayMethod.Api.DEVICE_SPEAKER_BOX_DEACTIVATE_URL, isvParams, JSON.toJSONString(request));
log.info("接口响应参数:{}",jsonObject);
}
@RequestMapping("/lkl/queryStatus")
public void queryStatus(){
JSONObject request = new JSONObject();
request.put("ver","1.0.0");
request.put("reqId", SeqKit.genMhoOrderId());
request.put("timestamp", DateUtil.format(new DateTime(), DatePattern.PURE_DATETIME_PATTERN));
JSONObject reqData = new JSONObject();
reqData.put("merId","8225210899900DS");
reqData.put("termId","D4039820");
reqData.put("sn","QR939200003565");
request.put("reqData",reqData);
String isvNo = "V1698312342";
String ifCode = CS.IF_CODE.LKLSPAY;
LklspayIsvParams isvParams = (LklspayIsvParams)queryService.queryIsvParams(isvNo, ifCode);
JSONObject jsonObject = LklspayKit.reqApi(LklPayMethod.Api.DEVICE_SPEAKER_BOX_ACTIVATE_STATUS_QUERY_URL, isvParams, JSON.toJSONString(request));
log.info("接口响应参数:{}",jsonObject);
}
@RequestMapping("/lkl/pushQrcode")
public void pushQrCode(){
JSONObject request = new JSONObject();
request.put("ver","1.0.0");
request.put("reqId", SeqKit.genMhoOrderId());
request.put("timestamp", DateUtil.format(new DateTime(), DatePattern.PURE_DATETIME_PATTERN));
JSONObject reqData = new JSONObject();
reqData.put("merId","8225210899900DS");
reqData.put("termId","D4039820");
reqData.put("deviceSn","QR939200003565");
reqData.put("qrcode","https://www.baidu.com");
reqData.put("expireTime","60");
reqData.put("ts",System.currentTimeMillis() + "");
reqData.put("nonce",SeqKit.genTransferId());
request.put("reqData",reqData);
String isvNo = "V1698312342";
String ifCode = CS.IF_CODE.LKLSPAY;
LklspayIsvParams isvParams = (LklspayIsvParams)queryService.queryIsvParams(isvNo, ifCode);
JSONObject jsonObject = LklspayKit.reqApi(LklPayMethod.Api.DEVICE_SPEAKER_BOX_PUSH_QRCODE_URL, isvParams, JSON.toJSONString(request));
log.info("接口响应参数:{}",jsonObject);
}
@RequestMapping("/lkl/getActiveSn")
public void getActiveSn(){
JSONObject request = new JSONObject();
request.put("ver","1.0.0");
request.put("reqId", SeqKit.genMhoOrderId());
request.put("timestamp", DateUtil.format(new DateTime(), DatePattern.PURE_DATETIME_PATTERN));
JSONObject reqData = new JSONObject();
reqData.put("merId","8225210899900DS");
reqData.put("termId","D4039820");
reqData.put("source","TKSAAS");
reqData.put("ts",System.currentTimeMillis() + "");
reqData.put("nonce",SeqKit.genTransferId());
request.put("reqData",reqData);
String isvNo = "V1698312342";
String ifCode = CS.IF_CODE.LKLSPAY;
LklspayIsvParams isvParams = (LklspayIsvParams)queryService.queryIsvParams(isvNo, ifCode);
JSONObject jsonObject = LklspayKit.reqApi(LklPayMethod.Api.DEVICE_SPEAKER_BOX_QUERY_SN_URL, isvParams, JSON.toJSONString(request));
log.info("接口响应参数:{}",jsonObject);
}
/**
* 分账test
*/
@RequestMapping("/division/{payOrderId}")
public void divisionTest(@PathVariable("payOrderId") String payOrderId){
payOrderDivisionProcessService.processPayOrderDivision(payOrderId,(byte)1,null,false,false);
}
@RequestMapping("/state/count")
public void stateCount(){
String begin = "2024-01-29 00:00:00";
String end = "2024-01-29 23:59:59";
DateTime startDate = DateUtil.parse(begin, DatePattern.NORM_DATETIME_PATTERN);
DateTime endDate = DateUtil.parse(end, DatePattern.NORM_DATETIME_PATTERN);
statisticsService.doStats(startDate,endDate);
}
@RequestMapping("/transfer/{transferId}")
public String transfer(@PathVariable("transferId") String transferId){
ITransferService transferService = SpringBeansUtil.getBean("sybpayTransferService", ITransferService.class);
if(transferService == null){
throw new BizException("无此转账通道接口");
}
TransferOrderRQ rq = new TransferOrderRQ();
TransferOrderEntity transferOrder = transferOrderService.getById(transferId);
transferService.preCheck(rq,transferOrder);
try {
ChannelRetMsg transfer = transferService.transfer(rq, transferOrder);
return JSON.toJSONString(transfer);
}catch (Exception e){
e.printStackTrace();
return "错误请求";
}
}
@RequestMapping("/device/lkl/{startSn}/{endSn}")
public String deviceInto(@PathVariable("startSn") String startSn,@PathVariable("endSn") String endSn){
Long start = Long.valueOf(startSn.replace("QR", ""));
Long end = Long.valueOf(endSn.replace("QR", ""));
String batch_id = DateUtil.format(new DateTime(),DatePattern.PURE_DATE_PATTERN);
List<MchStoreDevice> list = new ArrayList<>();
for (long i = start; i <= end ; i++) {
MchStoreDevice mchStoreDevice = new MchStoreDevice();
mchStoreDevice.setDeviceName(RandomUtil.randomNumbers(4));
mchStoreDevice.setBatchId(batch_id);
mchStoreDevice.setDeviceType((byte)1);
mchStoreDevice.setProvider("lkls");
mchStoreDevice.setDeviceNo("QR"+ i);
JSONObject deviceParams = new JSONObject();
deviceParams.put("deviceNo","QR" + i);
mchStoreDevice.setDeviceParams(deviceParams.toJSONString());
list.add(mchStoreDevice);
}
mchStoreDeviceService.saveBatch(list);
return "SUCCESS";
}
@RequestMapping("/send/order/msg/{orderNo}")
public String sendOrderMsg(@PathVariable("orderNo") String orderNo){
pushWxMpTemplateMsgService.send(orderNo, PushWxMpMsgMQ.ORDER_NOTICE);
return "SUCCESS";
}
@RequestMapping("/send/zft/msg/{settleNo}")
public String sendZftSettleMsg(@PathVariable("settleNo") String settleNo){
mqSender.send(ZftSettOrderMQ.build(settleNo));
return "SUCCESS";
}
@RequestMapping("/statistics")
public String statistics(){
DateTime now = DateUtil.date();
DateTime yesterday = DateUtil.offsetDay(now, -1);
Date beginDate = DateUtil.beginOfDay(yesterday);
Date endDate = DateUtil.endOfDay(yesterday);
log.info("【处理数据统计任务】统计日期: {}", DateUtil.formatDate(beginDate));
statisticsService.doStats(beginDate, endDate);
return "SUCCESS";
}
@RequestMapping("/genTk")
public String genTk(){
ISysConfigService sysService = SpringBeansUtil.getBean("sysConfigService", ISysConfigService.class);
return sysService.genQrToken((byte)1,"123123",1L);
}
@RequestMapping("/tradeQuery/{orderId}")
public String tradeQuery(@PathVariable("orderId") String payOrderId){
IPayOrderQueryService queryService = SpringBeansUtil.getBean("yspayPayOrderQueryService", IPayOrderQueryService.class);
if(queryService == null){
throw new BizException("无此支付通道接口");
}
PayOrder payOrder = payOrderService.getById(payOrderId);
//查询出商户应用的配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(payOrder.getMchNo(), payOrder.getAppId(),payOrder.getMchExtNo());
try {
ChannelRetMsg query = queryService.query(payOrder,mchAppConfigContext);
return JSON.toJSONString(query);
} catch (Exception e) {
e.printStackTrace();
}
return "error";
}
@RequestMapping("/div/query/{orderId}")
public String divQuery(@PathVariable("orderId") String payOrderId){
PayOrder payOrder = payOrderService.getById(payOrderId);
JSONObject bizData = new JSONObject();
bizData.put("mercId",payOrder.getChannelMchNo());
bizData.put("orderId",payOrderId);
MchApplyment applyment = mchApplymentService.getById(payOrder.getMchExtNo());
YspayIsvParams yspayIsvParams = configContextQueryService.queryIsvParams(mchInfoConverter.toModel(applyment));
try {
JSONObject result = YspayKit.reqV3(ReqMethod.Method.QUERY_D0_DIVISION_SETTLE,yspayIsvParams, bizData);
log.info("返回结果:{}",result);
return result.toJSONString();
}catch (BizException e){
e.printStackTrace();
}
return "ERROR";
}
@RequestMapping("/get/box/market/{sn}")
public String getBoxMarket(@PathVariable("sn") String sn){
MchStoreDevice device = mchStoreDeviceService.getDeviceInfo(sn, (byte) 1, "lkls");
DeviceProvideConfig provideConfig = deviceProvideConfigService.getById(device.getConfigId());
JSONObject deviceParams = new JSONObject();
deviceParams.put("deviceParams", device.getDeviceParams());
deviceParams.put("bizConfigParams", device.getBizConfigParams());
deviceParams.put("providerParams", provideConfig.getProviderParams());
JSONObject resp = lklsSpeakerService.getMarketAreaAdvert(deviceParams);
return resp.toString();
}
@RequestMapping("/set/box/market/{sn}")
public String setBoxMarket(@PathVariable("sn") String sn){
MchStoreDevice device = mchStoreDeviceService.getDeviceInfo(sn, (byte) 1, "lkls");
DeviceProvideConfig provideConfig = deviceProvideConfigService.getById(device.getConfigId());
JSONObject deviceParams = new JSONObject();
deviceParams.put("deviceParams", device.getDeviceParams());
deviceParams.put("bizConfigParams", device.getBizConfigParams());
deviceParams.put("providerParams", provideConfig.getProviderParams());
JSONObject reqData = new JSONObject();
reqData.put("translsNo",SeqKit.genFlowNo("F"));
reqData.put("devSn",device.getDeviceNo());
ArrayList markPicVos = new ArrayList();
String url1 = "https://syb-jq-public.oss-cn-hangzhou.aliyuncs.com/notice/069507a2-e4a0-4a64-9122-0f40727aa60d.jpg";
String url2 = "https://syb-jq-public.oss-cn-hangzhou.aliyuncs.com/notice/c55f884e-ad79-4daf-b710-6edef4c658b3.jpg";
JSONObject item1 = new JSONObject();
item1.put("displayTime",5);
item1.put("enable",1);
item1.put("picName",url1);
item1.put("picSize",71693L);
item1.put("picWidth",800);
item1.put("picHeight",960);
JSONObject item2 = new JSONObject();
item2.put("displayTime",5);
item2.put("enable",1);
item2.put("picName",url2);
item2.put("picSize",87993L);
item2.put("picWidth",800);
item2.put("picHeight",960);
markPicVos.add(item1);
markPicVos.add(item2);
reqData.put("markPicVos",markPicVos);
JSONObject result = lklsSpeakerService.setMarketAreaAdvert(deviceParams,reqData);
return result.toString();
}
/**
* 设置跑马灯
* @param sn
* @return
*/
@RequestMapping("/set/box/marquee/{sn}")
public String setBoxMarquee(@PathVariable("sn") String sn){
MchStoreDevice device = mchStoreDeviceService.getDeviceInfo(sn, (byte) 1, "lkls");
DeviceProvideConfig provideConfig = deviceProvideConfigService.getById(device.getConfigId());
JSONObject deviceParams = new JSONObject();
deviceParams.put("deviceParams", device.getDeviceParams());
deviceParams.put("bizConfigParams", device.getBizConfigParams());
deviceParams.put("providerParams", provideConfig.getProviderParams());
JSONObject bizData = new JSONObject();
bizData.put("translsNo",SeqKit.genFlowNo("F"));
bizData.put("devSn",device.getDeviceNo());
JSONObject marqueeVo = new JSONObject();
marqueeVo.put("inputText","快来玩啊小哥哥");
marqueeVo.put("fontSize",20);
marqueeVo.put("fontColor","#FFFFFF");
marqueeVo.put("bgColor","#409EFF");
marqueeVo.put("displayAreX",0);
marqueeVo.put("displayAreY",300);
marqueeVo.put("picWidth",840);
marqueeVo.put("picHeight",40);
bizData.put("marqueeVo",marqueeVo);
JSONObject result = lklsSpeakerService.setMarqueeInfo(deviceParams,bizData);
return result.toString();
}
@RequestMapping("/set/split/image")
public String setBoxMarquee(){
// 源图片路径
String sourceImagePath = "D:\\pic\\pic1.jpg";
// 目标图片路径
String targetImagePath = "D:\\pic\\223.jpg";
// 目标宽度
int targetWidth = 800;
// 目标高度
int targetHeight = 960;
// 读取源图片
BufferedImage image = ImgUtil.read(FileUtil.file(sourceImagePath));
// 缩放图片
Image scaledImage = ImgUtil.scale(image, targetWidth, targetHeight);
// 将Image对象转换为BufferedImage
BufferedImage bufferedImage = ImgUtil.castToBufferedImage(scaledImage,ImgUtil.IMAGE_TYPE_JPG);
// 将BufferedImage转换为字节数组
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(bufferedImage, "jpg", os);
ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray());
String url = ossFileController.singleFileUpload(new MockMultipartFile(UUID.fastUUID() + ".jpg", 1L, bis), OssFileConfig.BIZ_TYPE.MATERIAL);
return url;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,109 @@
package com.jeequan.jeepay.pay.ctrl.transfer;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.rqrs.transfer.QueryTransferOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.transfer.QueryTransferOrderRS;
import com.jeequan.jeepay.db.entity.TransferOrderEntity;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.PayInterfaceConfigService;
import com.jeequan.jeepay.service.impl.TransferOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 商户转账单查询controller
*
* @author terrfly
*
* @date 2021/8/13 15:20
*/
@Slf4j
@RestController
public class QueryTransferOrderController extends ApiController {
@Autowired private TransferOrderService transferOrderService;
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
/**
* 查单接口
* **/
@MethodLog(remark = "查询转账订单API")
@RequestMapping("/api/transfer/query")
public ApiRes queryTransferOrder(){
//获取参数 & 验签
QueryTransferOrderRQ rq = getRQByWithMchSign(QueryTransferOrderRQ.class);
if(StringUtils.isAllEmpty(rq.getMchOrderNo(), rq.getTransferId())){
throw new BizException("mchOrderNo 和 transferId不能同时为空");
}
TransferOrderEntity refundOrder = transferOrderService.queryMchOrder(rq.getMchNo(), rq.getMchOrderNo(), rq.getTransferId());
if(refundOrder == null){
throw new BizException("订单不存在");
}
QueryTransferOrderRS bizRes = QueryTransferOrderRS.buildByRecord(refundOrder);
return ApiResKit.okWithSign(bizRes, configContextQueryService.queryMchApp(rq.getMchNo(), rq.getAppId()), rq.getSignType());
}
/**
* 查询转账账户余额接口
* **/
@MethodLog(remark = "查询转账可用余额API")
@RequestMapping("/api/transfer/balance/query")
public ApiRes queryTransferBalance(){
return ApiRes.ok();
// //获取参数 & 验签
// QueryTransferBalanceRQ bizRQ = getRQByWithMchSign(QueryTransferBalanceRQ.class);
//
// try {
// String mchNo = bizRQ.getMchNo();
// String appId = bizRQ.getAppId();
// String ifCode = bizRQ.getIfCode();
//
// // 商户配置信息
// MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(mchNo, appId,bizRQ.getMchExtNo());
// if(mchAppConfigContext == null){
// throw new BizException("获取商户应用信息失败");
// }
//
// MchApp mchApp = mchAppConfigContext.getMchApp();
//
// // 是否已正确配置
// if(!payInterfaceConfigService.mchAppHasAvailableIfCode(appId, ifCode)){
// throw new BizException("应用未开通此接口配置!");
// }
//
// ITransferService transferService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "TransferService", ITransferService.class);
// if(transferService == null){
// throw new BizException("无此转账通道接口");
// }
//
// // 调起上游接口
// MutablePair<String, Long> pair = transferService.queryBalanceAmount(mchAppConfigContext, bizRQ.getIfCode());
//
// QueryTransferBalanceRS bizRes = new QueryTransferBalanceRS();
// bizRes.setBalanceAmount(pair.getRight());
//
// return ApiResKit.okWithSign(bizRes, mchApp, bizRQ.getSignType());
//
// } catch (BizException e) {
// return ApiRes.customFail(e.getMessage());
//
// } catch (Exception e) {
// log.error("系统异常:", e);
// return ApiRes.customFail("系统异常");
// }
}
}

View File

@@ -0,0 +1,162 @@
package com.jeequan.jeepay.pay.ctrl.transfer;
import cn.hutool.core.util.ObjUtil;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.entity.TransferOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.core.interfaces.paychannel.ITransferNoticeService;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.OrderProfitSettRecord;
import com.jeequan.jeepay.db.entity.TransferOrderEntity;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.service.impl.OrderProfitSettRecordService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.service.impl.TransferOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* 转账异步通知入口Controller
*
* @author zx
* @since 2022/12/30 10:26
*/
@Slf4j
@Controller
public class TransferNoticeController extends AbstractCtrl {
@Autowired
private TransferOrderService transferOrderService;
@Autowired
private PayMchNotifyService payMchNotifyService;
@Autowired
private OrderProfitSettRecordService orderProfitSettRecordService;
@Autowired
private PayOrderService payOrderService;
/**
* 异步回调入口
**/
@ResponseBody
@RequestMapping(value = {"/api/transfer/notify/{ifCode}", "/api/transfer/notify/{ifCode}/{transferId}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "transferId", required = false) String urlOrderId) {
String transferId = null;
String logPrefix = "进入[" + ifCode + "]转账回调urlOrderId[" + StringUtils.defaultIfEmpty(urlOrderId, "") + "] ";
log.info("===== {} =====", logPrefix);
try {
// 参数有误
if (StringUtils.isEmpty(ifCode)) {
return ResponseEntity.badRequest().body("ifCode is empty");
}
//查询转账接口是否存在
ITransferNoticeService transferNotifyService = SpringBeansUtil.getBean(ifCode + "TransferNoticeService", ITransferNoticeService.class);
// 支付通道转账接口实现不存在
if (transferNotifyService == null) {
log.error("{}, transfer interface not exists ", logPrefix);
return ResponseEntity.badRequest().body("[" + ifCode + "] transfer interface not exists");
}
// 解析转账单号 和 请求参数
MutablePair<String, Object> mutablePair = transferNotifyService.parseParams(request, urlOrderId);
if (mutablePair == null) { // 解析数据失败, 响应已处理
log.error("{}, mutablePair is null ", logPrefix);
throw new BizException("解析数据异常!"); //需要实现类自行抛出ResponseException, 不应该在这抛此异常。
}
// 解析到转账单号
transferId = mutablePair.left;
if (ObjUtil.isEmpty(transferId)) {
log.error("解析回调数据转账单号为空, 转账单不存在");
return ResponseEntity.ok(transferNotifyService.retOk(null));
}
log.info("{}, 解析数据为transferId:{}, params:{}", logPrefix, transferId, mutablePair.getRight());
// 获取转账单号 和 转账单数据
TransferOrderEntity transferOrder = transferOrderService.getById(transferId);
// 转账单不存在
if (transferOrder == null) {
log.error("{}, 转账单不存在. transferId={} ", logPrefix, transferId);
return ResponseEntity.ok(transferNotifyService.retOk(null));
}
//查询出商户应用的配置信息
// MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(transferOrder.getMchNo(), transferOrder.getAppId(), transferOrder.getMchExtNo());
//调起接口的回调判断
ChannelRetMsg notifyResult = transferNotifyService.doNotice(request, mutablePair.getRight(), transferOrder);
// 返回null 表明出现异常, 无需处理通知下游等操作。
if (notifyResult == null || notifyResult.getChannelState() == null || notifyResult.getResponseEntity() == null) {
log.error("{}, 处理回调事件异常 notifyResult data error, notifyResult ={} ", logPrefix, notifyResult);
return ResponseEntity.ok(transferNotifyService.retOk(null));
}
// 转账单是 【转账中状态】
if (transferOrder.getState() == TransferOrder.STATE_ING) {
if (notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
// 转账成功
boolean isUpdateSuccess = transferOrderService.updateIng2Success(transferId, notifyResult.getChannelOrderId());
if (isUpdateSuccess) {
//更新营销活动补贴金额
payOrderService.updateMarketAmt(transferOrder.getFlowNo(), transferOrder.getAmount());
// 计算订单的分润记录 ( 若异常 不影响订单后续逻辑, service内的事务)
try {
boolean isRunTask = orderProfitSettRecordService.insertProfitByPayOrder(transferOrder.getTransferId(), OrderProfitSettRecord.CAL_TYPE_TRANS_ORDER);
// 实时结算
if (isRunTask) {
orderProfitSettRecordService.profit2BalanceAmount(transferOrder.getTransferId(), OrderProfitSettRecord.CAL_TYPE_TRANS_ORDER);
}
} catch (Exception e) {
log.error("计算转账分润错误, transferId={}", transferOrder.getTransferId(), e);
}
payMchNotifyService.transferOrderNotify(transferOrderService.getById(transferId));
}
} else if (notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL) {
// 转账失败
transferOrderService.updateIng2Fail(transferId, notifyResult.getChannelOrderId(), notifyResult.getChannelUserId(), notifyResult.getChannelErrCode());
payMchNotifyService.transferOrderNotify(transferOrderService.getById(transferId));
}
}
log.info("===== {}, 转账单通知完成。 transferId={}, parseState = {} =====", logPrefix, transferId, notifyResult.getChannelState());
return notifyResult.getResponseEntity();
} catch (BizException e) {
log.error("{}, transferId={}, BizException", logPrefix, transferId, e);
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResponseException e) {
log.error("{}, transferId={}, ResponseException", logPrefix, transferId, e);
return e.getResponseEntity();
} catch (Exception e) {
log.error("{}, transferId={}, 系统异常", logPrefix, transferId, e);
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}

View File

@@ -0,0 +1,190 @@
package com.jeequan.jeepay.pay.ctrl.transfer;
import com.jeequan.jeepay.core.aop.MethodLog;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ChannelException;
import com.jeequan.jeepay.core.interfaces.IConfigContextQueryService;
import com.jeequan.jeepay.core.interfaces.paychannel.ITransferService;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.applyment.PaywayFee;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.transfer.TransferOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.transfer.TransferOrderRS;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.core.utils.StringKit;
import com.jeequan.jeepay.db.entity.TransferOrderEntity;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.service.TransferOrderReissueService;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.PayInterfaceConfigService;
import com.jeequan.jeepay.service.impl.RateConfigService;
import com.jeequan.jeepay.service.impl.TransferOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* 转账接口
*
* @author terrfly
*
* @date 2021/8/11 11:07
*/
@Slf4j
@RestController
public class TransferOrderController extends ApiController {
@Autowired private IConfigContextQueryService configContextQueryService;
@Autowired private TransferOrderService transferOrderService;
@Autowired private PayInterfaceConfigService payInterfaceConfigService;
@Autowired private TransferOrderReissueService transferOrderReissueService;
@Autowired private RateConfigService rateConfigService;
/**
* 转账
* **/
@MethodLog(remark = "发起转账订单API")
@PostMapping("/api/transferOrder")
public ApiRes transferOrder(){
TransferOrderEntity transferOrder = null;
//获取参数 & 验签
TransferOrderRQ bizRQ = getRQByWithMchSign(TransferOrderRQ.class);
try {
String mchNo = bizRQ.getMchNo();
String appId = bizRQ.getAppId();
String ifCode = bizRQ.getIfCode();
// 商户订单号是否重复
if(transferOrderService.count(TransferOrderEntity.gw().eq(TransferOrderEntity::getMchNo, mchNo).eq(TransferOrderEntity::getMchOrderNo, bizRQ.getMchOrderNo())) > 0){
throw new BizException("商户订单["+bizRQ.getMchOrderNo()+"]已存在");
}
if(StringUtils.isNotEmpty(bizRQ.getNotifyUrl()) && !StringKit.isAvailableUrl(bizRQ.getNotifyUrl())){
throw new BizException("异步通知地址协议仅支持http:// 或 https:// !");
}
// 商户配置信息
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(mchNo, appId,bizRQ.getMchNo());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
MchInfo mchInfo = mchAppConfigContext.getMchInfo();
MchApp mchApp = mchAppConfigContext.getMchApp();
// 是否已正确配置
if(!payInterfaceConfigService.mchAppHasAvailableIfCode(appId, ifCode)){
throw new BizException("应用未开通此接口配置!");
}
ITransferService transferService = SpringBeansUtil.getBean(JeepayKit.getIfCodeOrigin(ifCode) + "TransferService", ITransferService.class);
if(transferService == null){
throw new BizException("无此转账通道接口");
}
if(!transferService.isSupport(bizRQ.getEntryType())){
throw new BizException("该接口不支持该入账方式");
}
// 查询商户的转账费率
PaywayFee paywayFee = rateConfigService.queryTransferCreateOrderPaywayFee(mchAppConfigContext.getMchApplyment().getApplyId(), mchAppConfigContext.getMchApplyment().getIsvNo(), mchAppConfigContext.getAppId(), ifCode, CS.PAY_WAY_CODE.TRANSFER);
transferOrder = genTransferOrder(bizRQ, mchInfo, mchApp, ifCode, paywayFee);
//预先校验
String errMsg = transferService.preCheck(bizRQ, transferOrder);
if(StringUtils.isNotEmpty(errMsg)){
throw new BizException(errMsg);
}
// 入库
transferOrderService.save(transferOrder);
// 调起上游接口
ChannelRetMsg channelRetMsg = transferService.transfer(bizRQ, transferOrder);
//处理转账单状态
transferOrderReissueService.processChannelMsg(channelRetMsg, transferOrder);
TransferOrderRS bizRes = TransferOrderRS.buildByRecord(transferOrder);
return ApiResKit.okWithSign(bizRes, mchApp, bizRQ.getSignType());
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (ChannelException e) {
//处理上游返回数据
transferOrderReissueService.processChannelMsg(e.getChannelRetMsg(), transferOrder);
if(e.getChannelRetMsg().getChannelState() == ChannelRetMsg.ChannelState.SYS_ERROR ){
return ApiRes.customFail(e.getMessage());
}
TransferOrderRS bizRes = TransferOrderRS.buildByRecord(transferOrder);
return ApiResKit.okWithSign(bizRes, configContextQueryService.queryMchApp(bizRQ.getMchNo(), bizRQ.getAppId()), bizRQ.getSignType());
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
private TransferOrderEntity genTransferOrder(TransferOrderRQ rq, MchInfo mchInfo, MchApp mchApp, String ifCode, PaywayFee paywayFee){
TransferOrderEntity transferOrder = new TransferOrderEntity();
transferOrder.setTransferId(SeqKit.genTransferId()); //生成转账订单号
transferOrder.setMchNo(mchInfo.getMchNo()); //商户号
// transferOrder.setIsvNo(mchInfo.getIsvNo()); //服务商号
transferOrder.setAgentNo(mchInfo.getAgentNo()); // 服务商号
transferOrder.setAppId(mchApp.getAppId()); //商户应用appId
transferOrder.setMchName(mchInfo.getMchShortName()); //商户名称(简称)
transferOrder.setMchType(mchInfo.getType()); //商户类型
transferOrder.setMchOrderNo(rq.getMchOrderNo()); //商户订单号
transferOrder.setIfCode(ifCode); //接口代码
transferOrder.setEntryType(rq.getEntryType()); //入账方式
transferOrder.setAmount(rq.getAmount()); //订单金额
transferOrder.setCurrency(rq.getCurrency()); //币种
transferOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(), getClientIp())); //客户端IP
transferOrder.setState(TransferOrderEntity.STATE_INIT); //订单状态, 默认订单生成状态
transferOrder.setAccountNo(rq.getAccountNo()); //收款账号
transferOrder.setAccountName(rq.getAccountName()); //账户姓名
transferOrder.setBankName(rq.getBankName()); //银行名称
transferOrder.setTransferDesc(rq.getTransferDesc()); //转账备注
transferOrder.setExtParam(rq.getExtParam()); //商户扩展参数
transferOrder.setNotifyUrl(rq.getNotifyUrl()); //异步通知地址
// 转账手续费, 可以为空。
if(paywayFee != null){
MutablePair<String, Long> feeAndSnapshot = paywayFee.calFeeAndSnapshot(transferOrder.getAmount());
transferOrder.setMchFeeRate(feeAndSnapshot.left);
transferOrder.setMchOrderFeeAmount(feeAndSnapshot.right); //商户手续费,单位分
}
transferOrder.setCreatedAt(new Date()); //订单创建时间
return transferOrder;
}
}

View File

@@ -0,0 +1,47 @@
package com.jeequan.jeepay.pay.ctrl.transfer;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.beans.RequestKitBean;
import com.jeequan.jeepay.core.ctrls.AbstractCtrl;
import com.jeequan.jeepay.core.interfaces.transfer.IChannelTransferSubjectService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* 转账主体功能开通回调
*
* @author deng
* @since 2024/5/8
*/
@Slf4j
@RestController
public class TransferSubjectChannelNotifyController extends AbstractCtrl {
@Autowired
private RequestKitBean requestKitBean;
@ResponseBody
@RequestMapping(value = {"/api/transferSubject/notify/{ifCode}", "/api/transferSubject/notify/{ifCode}/{customApplyId}"})
public ResponseEntity doNotify(HttpServletRequest request, @PathVariable("ifCode") String ifCode, @PathVariable(value = "customApplyId", required = false) String customApplyId) {
JSONObject reqParamJSON = requestKitBean.getReqParamJSON();
log.info("代付参数, {}", reqParamJSON);
IChannelTransferSubjectService channelTransferSubjectService = SpringUtil.getBean(ifCode + "ChannelTransferSubjectService", IChannelTransferSubjectService.class);
if (channelTransferSubjectService == null) {
return ResponseEntity.ok("SUCCESS");
} else {
Object result = channelTransferSubjectService.callback(reqParamJSON);
return ResponseEntity.ok(result);
}
}
}

View File

@@ -0,0 +1,61 @@
package com.jeequan.jeepay.pay.ctrl.webcashier;
import com.jeequan.jeepay.db.entity.ThirdAuthDataEntity;
import com.jeequan.jeepay.service.impl.ThirdAuthDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class CashierController {
@Autowired
private ThirdAuthDataService wxThirdAuthDataService;
/**
* 小程序的公用路径,这里可以根据服务商编号做特定区分
*
* @param agentNo 服务商编号
* @param fileName 文件名称
*/
@GetMapping("/pages/hub/{agentNo}/{fileName}")
public String getWxAuthData(@PathVariable("agentNo") String agentNo, @PathVariable("fileName") String fileName) {
ThirdAuthDataEntity one = wxThirdAuthDataService.lambdaQuery()
.eq(ThirdAuthDataEntity::getAgentNo, agentNo)
.eq(ThirdAuthDataEntity::getSubType, ThirdAuthDataEntity.SUB_TYPE_LITE)
.eq(ThirdAuthDataEntity::getType, ThirdAuthDataEntity.TYPE_WX)
.eq(ThirdAuthDataEntity::getAuthFileName, fileName)
.last("limit 1")
.one();
if (one != null) {
return one.getValue();
}
return "";
}
/**
* 公众号根目录授权
*
* @param fileName 文件名称
*/
@GetMapping("/{fileName}")
public String getWxAuthData(@PathVariable("fileName") String fileName) {
ThirdAuthDataEntity one = wxThirdAuthDataService.lambdaQuery()
.eq(ThirdAuthDataEntity::getSubType, ThirdAuthDataEntity.SUB_TYPE_H5)
.eq(ThirdAuthDataEntity::getType, ThirdAuthDataEntity.TYPE_WX)
.eq(ThirdAuthDataEntity::getAuthFileName, fileName)
.last("limit 1")
.one();
if (one != null) {
return one.getValue();
}
return "";
}
}

View File

@@ -0,0 +1,324 @@
package com.jeequan.jeepay.pay.ctrl.webcashier;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentParser;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.ApiField;
import com.jeequan.jeepay.JeepayClient;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.*;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.JsonKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.exception.JeepayException;
import com.jeequan.jeepay.model.DeviceInfo;
import com.jeequan.jeepay.model.JeepayObject;
import com.jeequan.jeepay.model.PayOrderCreateResModel;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.request.PayOrderCreateRequest;
import com.jeequan.jeepay.response.PayOrderCreateResponse;
import com.jeequan.jeepay.service.impl.*;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 便捷收银台controller
*
* @author xiaoyu
*
* @date 2023/3/1 17:27
*/
@Controller
@RequestMapping("/api/cashierPage")
public class CashierPageController extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private SysConfigService sysConfigService;
@Autowired private PayMchNotifyService payMchNotifyService;
@Autowired private MchAppService mchAppService;
@Autowired private MchInfoService mchInfoService;
@Autowired private MchPayPassageService mchPayPassageService;
@Autowired private MchConfigService mchConfigService;
@Autowired private MchStoreService mchStoreService;
/** 跳转到页面 **/
@GetMapping("/{appId}/{storeId}")
public String toPage(@PathVariable("appId") String appId, @PathVariable("storeId") String storeId){
try {
request.setAttribute("appId", appId);
request.setAttribute("storeId", storeId);
// 应用ID、 门店ID 解码
appId = JeepayKit.aesDecode(appId);
storeId = JeepayKit.aesDecode(storeId);
MchAppEntity mchAppEntity = mchAppService.getById(appId);
MchInfo mchInfo = mchInfoService.getById(mchAppEntity.getMchNo());
MchStore mchStore = mchStoreService.getById(storeId);
// 查询商户收银台配置信息
CashierConfig cashierConfig = mchConfigService.queryMchCashierConfigByDefault(mchAppEntity.getMchNo(), MchConfig.SELF_CASHIER_GROUP_KEY);
if(cashierConfig.getState() != CS.YES){
throw new BizException("商户未开通[便捷收银台]功能");
}
// 静态CDN地址
setPublicCdnHost();
request.setAttribute("cashierConfig", cashierConfig);
request.setAttribute("mchName", mchInfo.getMchName());
request.setAttribute("storeName", mchStore != null ? mchStore.getStoreName():"");
DBOEMConfig oemConfig = sysConfigService.getOemConfig();
// 得到所有的支付方式
Set<String> allWaycode = new HashSet<>();
mchPayPassageService.list(MchPayPassage.gw().eq(MchPayPassage::getMchNo, mchAppEntity.getMchNo()).eq(MchPayPassage::getAppId, mchAppEntity.getAppId()).eq(MchPayPassage::getState, CS.YES))
.stream().forEach( r -> allWaycode.add(r.getWayCode()));
// 处理 wxH5支付方式 and aliwap支付方式。
cashierConfig.processWacodesWithWxH5AndALiwap(allWaycode);
// 放置jsAPI
if(allWaycode.contains(CS.PAY_WAY_CODE.WX_JSAPI) || allWaycode.contains(CS.PAY_WAY_CODE.ALI_JSAPI) || allWaycode.contains(CS.PAY_WAY_CODE.YSF_JSAPI) ){
allWaycode.add(CS.PAY_WAY_CODE.QR_CASHIER);
}
request.setAttribute("allWaycode", JSON.toJSONString(allWaycode));
request.setAttribute("oemConfig", oemConfig);
UserAgent userAgent = UserAgentParser.parse(request.getHeader("User-Agent"));
if(userAgent.getPlatform().isMobile()){
return "cashierPage/cashierPageH5";
}
return "cashierPage/cashierPage";
} catch (BizException e) {
logger.error("业务异常", e);
request.setAttribute("errMsg", e.getMessage());
return "cashierPage/error";
} catch (Exception e) {
logger.error("异常", e);
request.setAttribute("errMsg", e.getMessage());
return "cashierPage/error";
}
}
/* 跳转到支付成功页面 **/
@GetMapping("/paySuccess")
public String toReturnSuccessPage(){
// 静态CDN地址
setPublicCdnHost();
return "cashier/returnPage";
}
/** 支付方式的转换 **/
@PostMapping ("/convertPayway/{appId}/{storeId}")
@ResponseBody
public ApiRes convertPayway(@PathVariable("appId") String appId, @PathVariable("storeId") String storeId){
PayOrderCreateRequest request = new PayOrderCreateRequest();
PayOrderCreateReqModelAddStoreId model = new PayOrderCreateReqModelAddStoreId();
// 需要转换的支付方式
appId = JeepayKit.aesDecode(appId);
String wayCode = getValStringRequired("wayCode");
// 需要的方式url跳转 or 二维码展示)
String payDataType = getValString("payDataType");
// 买家备注
String buyerRemark = getValString("buyerRemark");
MchAppEntity mchAppEntity = mchAppService.getById(appId);
if(StringUtils.isNotBlank(payDataType)){ // payDataType
model.setChannelExtra(JsonKit.newJson("payDataType", payDataType).toString());
}
// 任意一种签名方式
// model.setSignType(appSignType.get(0));
// 支付方式
model.setWayCode(wayCode);
// 订单金额
model.setAmount(getRequiredAmountL("amount"));
// 商户订单号
model.setMchOrderNo(SeqKit.genMhoOrderId());
// 门店ID
model.setStoreId(JeepayKit.aesDecode(storeId));
// appId
model.setAppId(appId);
// 商户号
model.setMchNo(mchAppEntity.getMchNo());
// 货币代码
model.setCurrency("CNY");
model.setSubject("收银台订单 [" + mchAppEntity.getMchNo() + "]");
model.setBody("收银台订单 [" + mchAppEntity.getMchNo() + "]");
// 失效时间 15分钟 60 * 15
String expiredTime = "900";
model.setExpiredTime(expiredTime);
model.setBuyerRemark(buyerRemark);
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
// model.setNotifyUrl(dbApplicationConfig.getMchSiteUrl() + "/api/anon/paytestNotify/payOrder"); //回调地址
model.setPas(SysConfigService.PLATFORM_API_SECRET); // 通信秘钥
request.setBizModel(model);
// 判断是否 需要 h5 ==》 小程序
CashierConfig cashierConfig = mchConfigService.queryMchCashierConfigByDefault(mchAppEntity.getMchNo(), MchConfig.SELF_CASHIER_GROUP_KEY);
// 得到所有的支付方式
Set<String> allWaycode = new HashSet<>();
mchPayPassageService.list(MchPayPassage.gw().select(MchPayPassage::getWayCode)
.eq(MchPayPassage::getMchNo, mchAppEntity.getMchNo()).eq(MchPayPassage::getAppId, mchAppEntity.getAppId()).eq(MchPayPassage::getState, CS.YES))
.stream().forEach( r -> allWaycode.add(r.getWayCode()));
// 商户可用 < 合并 > 配置
List<String> wxH5PaywayListWithIntersectionDistinct = cashierConfig.getWxH5PaywayListWithIntersectionDistinct(allWaycode);
List<String> aliWapPaywayListWithIntersectionDistinct = cashierConfig.getAliWapPaywayListWithIntersectionDistinct(allWaycode);
// 微信H5支付方式 && 配置的支付方式首先类型并不是 微信H5 , 那么需要做转换。
if(CS.PAY_WAY_CODE.WX_H5.equals(wayCode) && !wxH5PaywayListWithIntersectionDistinct.isEmpty() && !wxH5PaywayListWithIntersectionDistinct.get(0).equals(CS.PAY_WAY_CODE.WX_H5)){
// 微信小程序URL
model.setChannelExtra(JsonKit.newJson("entryLiteType", QRCodeParams.ENTRY_LITE_WXH5).toString());
model.setWayCode(CS.PAY_WAY_CODE.QR_CASHIER);
}
// 支付宝wap 支付方式 && 配置的支付方式首先类型并不是 支付宝wap , 那么需要做转换。
if(CS.PAY_WAY_CODE.ALI_WAP.equals(wayCode) && !aliWapPaywayListWithIntersectionDistinct.isEmpty() && !aliWapPaywayListWithIntersectionDistinct.get(0).equals(CS.PAY_WAY_CODE.ALI_WAP)){
// 生活号的形式
if(aliWapPaywayListWithIntersectionDistinct.get(0).equals(CS.PAY_WAY_CODE.ALI_JSAPI)){
model.setChannelExtra(JsonKit.newJson("entryLiteType", QRCodeParams.ENTRY_LITE_ALIJSAPIH5).toString());
model.setWayCode(CS.PAY_WAY_CODE.QR_CASHIER);
}else{ // 支付宝小程序URL
model.setChannelExtra(JsonKit.newJson("entryLiteType", QRCodeParams.ENTRY_LITE_ALIH5).toString());
model.setWayCode(CS.PAY_WAY_CODE.QR_CASHIER);
}
}
JeepayClient jeepayClient = new JeepayClient(dbApplicationConfig.getPaySiteUrl(), mchAppEntity.getAppSecret());
try {
PayOrderCreateResponse response = jeepayClient.execute(request);
if(response.getCode() != 0){
throw new BizException(response.getMsg());
}
// 存入下单超时时间
PayOrderCreateResModel resModel = response.get();
JSONObject resJson = (JSONObject) JSONObject.toJSON(resModel);
resJson.put("expiredTime", expiredTime);
return ApiRes.ok(resJson);
} catch (JeepayException e) {
throw new BizException(e.getMessage());
}
}
/** 查单 **/
@PostMapping("/orderState/{payOrderId}")
@ResponseBody
public ApiRes orderState(@PathVariable("payOrderId") String payOrderId){
PayOrder payOrder = payOrderService.getById(payOrderId);
JSONObject result = new JSONObject();
result.put("state", payOrder.getState());
// 支付成功
if(payOrder.getState() == PayOrder.STATE_SUCCESS){
// 包含同步回调地址
if(StringUtils.isNotBlank(payOrder.getReturnUrl())){
MchAppEntity mchAppEntity = mchAppService.getById(payOrder.getAppId());
result.put("returnUrl", payMchNotifyService.createReturnUrl(payOrder, mchAppEntity.getAppSecret()));
}else{
result.put("returnUrl", "/api/cashierPage/paySuccess");
}
}
return ApiRes.ok(result);
}
/** 与阿里云sdk一样 直接extends PayOrderCreateReqModel无法传入到接口中对应的参数。 需要把所有的参数放进来。。。 = = 。 **/
@Data
public static class PayOrderCreateReqModelAddStoreId extends JeepayObject {
@ApiField("pas")
private String pas;
@ApiField("storeId")
private String storeId;
private static final long serialVersionUID = -3998573128290306948L;
@ApiField("mchNo")
private String mchNo; // 商户号
@ApiField("appId")
private String appId; // 应用ID
@ApiField("mchOrderNo")
String mchOrderNo; // 商户订单号
@ApiField("wayCode")
String wayCode; // 支付方式
@ApiField("amount")
Long amount; // 支付金额
@ApiField("currency")
String currency; // 货币代码当前只支持cny
@ApiField("clientIp")
String clientIp; // 客户端IP
@ApiField("subject")
String subject; // 商品标题
@ApiField("body")
String body; // 商品描述
@ApiField("channelBizData")
String channelBizData; // 渠道业务参数
@ApiField("notifyUrl")
String notifyUrl; // 异步通知地址
@ApiField("returnUrl")
String returnUrl; // 跳转通知地址
@ApiField("expiredTime")
String expiredTime; // 订单失效时间
@ApiField("channelExtra")
String channelExtra; // 特定渠道额外支付参数
@ApiField("channelUser")
String channelUser; // 渠道用户标识,如微信openId,支付宝账号
@ApiField("extParam")
String extParam; // 商户扩展参数
@ApiField("divisionMode")
private Byte divisionMode; // 分账模式: 0-该笔订单不允许分账[默认], 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)
@ApiField("sellerRemark")
String sellerRemark; // 卖家备注
@ApiField("buyerRemark")
String buyerRemark; // 买家备注
@ApiField("deviceInfo")
private DeviceInfo deviceInfo; // 设备信息
}
public void setPublicCdnHost(){
// 静态CDN地址
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
request.setAttribute("staticCdnHost", dbApplicationConfig.getStaticCdnHost());
}
}

View File

@@ -0,0 +1,307 @@
package com.jeequan.jeepay.pay.ctrl.webcashier;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentParser;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.*;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.QrCashierOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.QrCashierOrderRS;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.DateKit;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.JsonKit;
import com.jeequan.jeepay.db.entity.MchAppEntity;
import com.jeequan.jeepay.db.entity.MchConfig;
import com.jeequan.jeepay.db.entity.MchPayPassage;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
import com.jeequan.jeepay.service.impl.*;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* WEB收银台controller
*
* @author terrfly
*
* @date 2022/7/1 17:27
*/
@Controller
@RequestMapping("/api/webCashier")
public class WebCashierController extends AbstractPayOrderController {
@Autowired private PayOrderService payOrderService;
@Autowired private SysConfigService sysConfigService;
@Autowired private PayMchNotifyService payMchNotifyService;
@Autowired private MchAppService mchAppService;
@Autowired private MchPayPassageService mchPayPassageService;
@Autowired private MchConfigService mchConfigService;
@Autowired private ConfigContextQueryService configContextQueryService;
/** 跳转到页面 **/
@GetMapping("/{payOrderToken}")
public String toPage(@PathVariable("payOrderToken") String payOrderToken){
try {
PayOrder payOrder = getPayOrder(payOrderToken);
request.setAttribute("payOrder", payOrder);
request.setAttribute("payOrderToken", payOrderToken);
request.setAttribute("payAmount", AmountUtil.convertCent2Dollar(payOrder.getAmount()));
// 查询商户收银台配置信息
CashierConfig cashierConfig = mchConfigService.queryMchCashierConfigByDefault(payOrder.getMchNo(), MchConfig.WEB_CASHIER_GROUP_KEY);
if(cashierConfig.getState() != CS.YES){
throw new BizException("商户未开通[web收银台]功能");
}
DBOEMConfig oemConfig = sysConfigService.getOemConfig();
// 静态CDN地址
setPublicCdnHost();
// 得到所有的支付方式
Set<String> allWaycode = new HashSet<>();
mchPayPassageService.list(MchPayPassage.gw().eq(MchPayPassage::getMchNo, payOrder.getMchNo()).eq(MchPayPassage::getAppId, payOrder.getAppId()).eq(MchPayPassage::getState, CS.YES))
.stream().forEach( r -> allWaycode.add(r.getWayCode()));
// 处理 wxH5支付方式 and aliwap支付方式。
cashierConfig.processWacodesWithWxH5AndALiwap(allWaycode);
// 放置jsAPI
if(allWaycode.contains(CS.PAY_WAY_CODE.WX_JSAPI) || allWaycode.contains(CS.PAY_WAY_CODE.ALI_JSAPI) || allWaycode.contains(CS.PAY_WAY_CODE.YSF_JSAPI) ){
allWaycode.add(CS.PAY_WAY_CODE.QR_CASHIER);
}
request.setAttribute("allWaycode", JSON.toJSONString(allWaycode));
request.setAttribute("oemConfig", oemConfig);
request.setAttribute("cashierConfig", cashierConfig);
// 倒计时 时间, 单位: s (不存在负值的情况)
long downSecond = (payOrder.getExpiredTime().getTime() - DateKit.currentTimeMillis()) / 1000;
request.setAttribute("downSecond", downSecond < 0 ? 0L : downSecond);
UserAgent userAgent = UserAgentParser.parse(request.getHeader("User-Agent"));
if(userAgent.getPlatform().isMobile()){
return "webcashier/webCashierH5";
}
return "webcashier/webCashier";
} catch (BizException e) {
logger.error("业务异常", e);
request.setAttribute("errMsg", e.getMessage());
return "webcashier/error";
} catch (Exception e) {
logger.error("异常", e);
request.setAttribute("errMsg", e.getMessage());
return "webcashier/error";
}
}
/* 跳转到支付成功页面 **/
@GetMapping("/paySuccess")
public String toReturnSuccessPage(){
// 静态CDN地址
setPublicCdnHost();
return "cashier/returnPage";
}
/** 支付方式的转换 **/
@PostMapping ("/convertPayway/{payOrderToken}")
@ResponseBody
public ApiRes convertPayway(@PathVariable("payOrderToken") String payOrderToken){
// 需要转换的支付方式
String wayCode = getValStringRequired("wayCode");
// 需要的方式url跳转 or 二维码展示)
String payDataType = getValString("payDataType");
PayOrder payOrder = getPayOrder(payOrderToken);
String payOrderId = payOrder.getPayOrderId();
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
MchAppEntity mchAppEntity = mchAppService.getById(payOrder.getAppId());
List<String> appSignType = JSON.parseArray(mchAppEntity.getAppSignType(), String.class);
// 聚合扫码
if(CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)){
boolean updateflag = payOrderService.update(new LambdaUpdateWrapper<PayOrder>()
.set(PayOrder::getWayCode, CS.PAY_WAY_CODE.QR_CASHIER)
.eq(PayOrder::getPayOrderId, payOrder.getPayOrderId()).eq(PayOrder::getState, PayOrder.STATE_INIT));
if(!updateflag){
throw new BizException("更新订单状态异常");
}
QrCashierOrderRS result = new QrCashierOrderRS();
String payUrl = dbApplicationConfig.genUniJsapiPayUrl(QRCodeParams.TYPE_PAY_ORDER, QRCodeParams.ENTRY_PAGE_H5, payOrderId, payOrder.getIsvNo());
// 需要二维码参数
if(CS.PAY_DATA_TYPE.CODE_IMG_URL.equals(payDataType)){
result.setCodeImgUrl(dbApplicationConfig.genScanImgUrl(payUrl));
}else{
result.setPayUrl(payUrl);
}
result.setPayDataType(result.buildPayDataType());
result.setPayData(result.buildPayData());
return ApiRes.ok(result);
}else{ // 转换为: 其他的支付方式类型
// 判断是否 需要 h5 ==》 小程序
CashierConfig cashierConfig = mchConfigService.queryMchCashierConfigByDefault(payOrder.getMchNo(), MchConfig.WEB_CASHIER_GROUP_KEY);
// 得到所有的支付方式
Set<String> allWaycode = new HashSet<>();
mchPayPassageService.list(MchPayPassage.gw().select(MchPayPassage::getWayCode)
.eq(MchPayPassage::getMchNo, mchAppEntity.getMchNo()).eq(MchPayPassage::getAppId, mchAppEntity.getAppId()).eq(MchPayPassage::getState, CS.YES)).forEach(r -> allWaycode.add(r.getWayCode()));
// 商户可用 < 合并 > 配置
List<String> wxH5PaywayListWithIntersectionDistinct = cashierConfig.getWxH5PaywayListWithIntersectionDistinct(allWaycode);
List<String> aliWapPaywayListWithIntersectionDistinct = cashierConfig.getAliWapPaywayListWithIntersectionDistinct(allWaycode);
// 微信H5支付方式 && 配置的支付方式首先类型并不是 微信H5 , 那么需要做转换。
if(CS.PAY_WAY_CODE.WX_H5.equals(wayCode) && !wxH5PaywayListWithIntersectionDistinct.isEmpty() && !wxH5PaywayListWithIntersectionDistinct.get(0).equals(CS.PAY_WAY_CODE.WX_H5)){
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(mchAppEntity.getMchNo(), mchAppEntity.getAppId(),payOrder.getMchExtNo());
QrCashierOrderRQ bizRQ = new QrCashierOrderRQ();
bizRQ.setSignType(appSignType.get(0)); // 任意一种签名方式
bizRQ.setMchNo(payOrder.getMchNo()); //
bizRQ.setAppId(payOrder.getAppId()); //
// 微信小程序URL
bizRQ.setEntryLiteType(QRCodeParams.ENTRY_LITE_WXH5);
ApiRes apiRes = qrCashierPayWay(bizRQ, mchAppConfigContext, payOrderId,null);
return commonAutoBuildRes(apiRes);
}
// 支付宝wap 支付方式 && 配置的支付方式首先类型并不是 支付宝wap , 那么需要做转换。
if(CS.PAY_WAY_CODE.ALI_WAP.equals(wayCode) && !aliWapPaywayListWithIntersectionDistinct.isEmpty() && !aliWapPaywayListWithIntersectionDistinct.get(0).equals(CS.PAY_WAY_CODE.ALI_WAP)){
MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfoV2(mchAppEntity.getMchNo(), mchAppEntity.getAppId(),payOrder.getMchExtNo());
QrCashierOrderRQ bizRQ = new QrCashierOrderRQ();
bizRQ.setSignType(appSignType.get(0)); // 任意一种签名方式
bizRQ.setMchNo(payOrder.getMchNo()); //
bizRQ.setAppId(payOrder.getAppId()); //
// 生活号的形式
if(aliWapPaywayListWithIntersectionDistinct.get(0).equals(CS.PAY_WAY_CODE.ALI_JSAPI)){
bizRQ.setEntryLiteType(QRCodeParams.ENTRY_LITE_ALIJSAPIH5);
}else{ // 支付宝小程序URL
bizRQ.setEntryLiteType(QRCodeParams.ENTRY_LITE_ALIH5);
}
ApiRes apiRes = this.qrCashierPayWay(bizRQ, mchAppConfigContext,payOrderId,null);
return commonAutoBuildRes(apiRes);
}
// 明确支付方式的: 下单:
UnifiedOrderRQ rq = new UnifiedOrderRQ();
if(StringUtils.isNotBlank(payDataType)){ // payDataType
rq.setChannelExtra(JsonKit.newJson("payDataType", payDataType).toString());
}
rq.setSignType(appSignType.get(0)); // 任意一种签名方式
rq.setWayCode(wayCode);
ApiRes apiRes = this.unifiedOrder(wayCode, rq.buildBizRQ(), payOrder,null);
return commonAutoBuildRes(apiRes);
}
}
/** 查单 **/
@PostMapping("orderState/{payOrderToken}")
@ResponseBody
public ApiRes orderState(@PathVariable("payOrderToken") String payOrderToken){
String payOrderId = JeepayKit.aesDecode(payOrderToken);
PayOrder payOrder = payOrderService.getById(payOrderId);
JSONObject result = new JSONObject();
result.put("state", payOrder.getState());
// 支付成功
if(payOrder.getState() == PayOrder.STATE_SUCCESS){
// 包含同步回调地址
if(StringUtils.isNotBlank(payOrder.getReturnUrl())){
MchAppEntity mchAppEntity = mchAppService.getById(payOrder.getAppId());
result.put("returnUrl", payMchNotifyService.createReturnUrl(payOrder, mchAppEntity.getAppSecret()));
}else{
result.put("returnUrl", "/api/webCashier/paySuccess");
}
}
return ApiRes.ok(result);
}
private PayOrder getPayOrder(String payOrderToken){
String payOrderId = JeepayKit.aesDecode(payOrderToken);
PayOrder payOrder = payOrderService.getById(payOrderId);
if(payOrder == null || payOrder.getState() != PayOrder.STATE_INIT){
throw new BizException("收银台订单不存在或状态不正确");
}
return payOrder;
}
/** 统一处理 buildPayData UnifiedOrderRS **/
private ApiRes commonAutoBuildRes(ApiRes apiRes){
UnifiedOrderRS bizRes = (UnifiedOrderRS)apiRes.getData();
if(bizRes == null){
return apiRes;
}
bizRes.setPayDataType(bizRes.buildPayDataType());
bizRes.setPayData(bizRes.buildPayData());
return ApiRes.ok(bizRes);
}
public void setPublicCdnHost(){
// 静态CDN地址
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
request.setAttribute("staticCdnHost", dbApplicationConfig.getStaticCdnHost());
}
}

View File

@@ -0,0 +1,63 @@
package com.jeequan.jeepay.pay.ctrl.wxmp;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.interfaces.wxmp.IWxMpTemplateMsgService;
import com.jeequan.jeepay.core.model.DBWxMpConfig;
import com.jeequan.jeepay.core.model.wx.wxmp.WxUserInfo;
import com.jeequan.jeepay.pay.ctrl.CommonController;
import com.jeequan.jeepay.service.impl.MchWxmpUserService;
import com.jeequan.jeepay.service.impl.SysConfigService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 接收微信服务器消息
*
* @author zx
* @site https://www.jeepay.vip
* @date 2021/10/15 9:37
*/
@Controller
@RequestMapping("/api/wxmpCallback/msg")
public class WxmpServerController extends CommonController {
@Autowired private SysConfigService sysConfigService;
@Autowired private MchWxmpUserService mchWxmpUserService;
@Autowired private IWxMpTemplateMsgService wxMpMessageService;
/** 微信授权回调回的页面(保存商户接收消息用户)*/
@RequestMapping("/wxAuthRedirect/{mchNo}/{sysUserId}")
public String wxAuthRedirect(@PathVariable("mchNo") String mchNo, @PathVariable("sysUserId") Long sysUserId){
try {
String code = getValStringRequired("code");
DBWxMpConfig wxMpConfig = sysConfigService.getDBWxMpConfig();
if (wxMpConfig == null) {
throw new BizException("未正确配置微信公众号消息配置项");
}
WxUserInfo wxUserInfo = wxMpMessageService.code2WxUserInfo(code, wxMpConfig);
// 初始化数据
mchWxmpUserService.saveOrUpdateRecord(mchNo, sysUserId, wxMpConfig.getWxAppId(), wxUserInfo.getOpenid(), wxUserInfo.getNickname());
request.setAttribute("wxmpQrUrl", wxMpConfig.getWxmpUrl());
}catch (BizException e) {
request.setAttribute("errMsg", e.getMessage());
logger.error("wxAuthRedirect系统异常", e);
}catch (Exception e) {
request.setAttribute("errMsg", StringUtils.defaultIfBlank(e.getMessage(), "系统异常"));
logger.error("wxAuthRedirect系统异常", e);
}
return "mchauth/index";
}
}

View File

@@ -0,0 +1,391 @@
/**
* cljpay.com
* Copyright (C) 2015-2023 All Rights Reserved.
*/package com.jeequan.jeepay.pay.ctrl.yly;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.device.YlyParams;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.AutoBarOrderRQ;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.Base64;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.model.DeviceInfo;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.ctrl.yly.sdk.RequestMethod;
import com.jeequan.jeepay.service.impl.DeviceProvideConfigService;
import com.jeequan.jeepay.service.impl.MchStoreDeviceService;
import com.jeequan.jeepay.service.impl.PayWayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
/**
* @author dingzhiwei
* @version
*/
@Slf4j
@RestController
@RequestMapping("/api/yly")
public class YlyController extends AbstractPayOrderController {
@Autowired private DeviceProvideConfigService deviceProvideConfigService;
@Autowired private MchStoreDeviceService mchStoreDeviceService;
@Autowired private PayWayService payWayService;
/**
* 易联云K8通知
*
* @param request
* @return
*/
@RequestMapping("/notify")
public JSONObject ylNotify(HttpServletRequest request) {
JSONObject object = new JSONObject();
object.put("message", "ok");
log.info("易联云K8消息{}", getReqParamJSON());
JSONObject reqObj = getReqParamJSON();
if(reqObj == null || reqObj.isEmpty()) {
return object;
}
String ciphertext = reqObj.getString("ciphertext");
String nonce = reqObj.getString("nonce");
String tag = reqObj.getString("tag");
String iv = reqObj.getString("iv");
String additional_data = reqObj.getString("additional_data");
String signature = reqObj.getString("signature");
// 解密key
String key = "tfa62db9e93353f12ad94988dc9322em";
try {
String plaintext = decrypte(Base64.decodeBase64String(ciphertext), key.getBytes(), iv.getBytes(), Base64.decodeBase64String(tag));
log.info("报文:{}", plaintext);
} catch (Exception e) {
log.error("解密异常,{}", e);
}
return object;
}
@RequestMapping("/oauth2")
public JSONObject oauth2(HttpServletRequest request) {
JSONObject object = new JSONObject();
object.put("message", "ok");
log.info("易联云K8消息{}", getReqParamJSON());
return object;
}
// TODO 验证签名暂未实现
/**
* AES-GCM-256对称解密
* @param encryptedBytes
* @param keyBytes
* @param ivBytes
* @param tagBytes
* @return
* @throws Exception
*/
private String decrypte(byte[] encryptedBytes, byte[] keyBytes, byte[] ivBytes, byte[] tagBytes) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, ivBytes);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
cipher.updateAAD("transaction".getBytes(StandardCharsets.UTF_8));
cipher.update(encryptedBytes);
byte[] decryptedBytes = cipher.doFinal(tagBytes);
String decryptedString = new String(decryptedBytes, StandardCharsets.UTF_8);
byte[] content = Base64.decodeBase64String(decryptedString);
return new String(content);
}
// 以下代码为临时测试
public static void main(String[] args) throws Exception {
String clientId = "1071822737";
String clientSecret = "9fa62db9e93353f09ad94988dc9322ee";
String machine_code = "4004849730";
String secret = "623550139267";
String result;
RequestMethod.getInstance().init(clientId, clientSecret);
//result = RequestMethod.getInstance().getCodeOpen(URLEncoder.encode("https://jeepay.natapp4.cc/api/yly/oauth2"));
//System.out.println("getCodeOpen = " + result);
//result = RequestMethod.getInstance().scanCodeModel(machine_code, secret);
//System.out.println("scancodemodel = " + result);
// String token = RequestMethod.getInstance().getAccessToken();
String accessToken = "40037cf4c0344b3f990a77eb9c116030"; // 自有 30天失效到2023-10-19
// String accessToken = "ca236d5254464d60b5b9832c3ef3fd92"; // 30天失效到2023-10-19
// 状态获取
result = RequestMethod.getInstance().printerGetPrintStatus(accessToken, machine_code);
System.out.println("/printer/getprintstatus = " + result);
// 设置打印Logo
//result = RequestMethod.getInstance().printSetIcon(accessToken, machine_code, "http://jeequan.oss-cn-beijing.aliyuncs.com/jeepay/img/jeepay_logo.png");
//System.out.println("/printer/printSetIcon = " + result);
// 指令
String content = "";
// 支付下发
// content = "<JSON>{\"order_payment\":{\"pay_amount\":\"0.03\",\"pay_amount_source\":3},\"processed_state\":0}</JSON><VI>040</VI><VI>043</VI><VD>0.03</VD><VI>032</VI>";
// 支付成功
// content = "<VI>033</VI><VI>041</VI><VI>031</VI><VD>0.03</VD><VI>032</VI>"; // 微信成功收款0.03元
content = "<VI>033</VI><VA>0,100</VA>"; // 微信收款100元
// 文本打印
result = RequestMethod.getInstance().printIndex(accessToken, machine_code, URLEncoder.encode(content), System.nanoTime()+"");
System.out.println("/printer/printIndex = " + result);
// 图片打印
// result = RequestMethod.getInstance().picturePrintIndex(accessToken, machine_code, "http://jeequan.oss-cn-beijing.aliyuncs.com/jeepay/img/jeepay_logo.png", SeqKit.genMhoRefundOrderId());
// System.out.println("/pictureprint/index = " + result);
// 取消全部打印
// result = RequestMethod.getInstance().printCancelAll(accessToken, machine_code);
// System.out.println("/printer/cancelall = " + result);
// K8关键词设置
JSONArray array = new JSONArray();
array.add("计全支付");
result = RequestMethod.getInstance().printerSetKeywords(accessToken, machine_code, "order_payment", "white_list", array.toJSONString());
// System.out.println("/printer/setkeywords = " + result);
}
/** 支付下单推送信息 **/
@RequestMapping("/pay/{configId}")
public JSONObject pay(@PathVariable("configId") String configId) {
String logPrefix = "易联云K8支付";
JSONObject result = new JSONObject();
result.put("message", "ok");
JSONObject reqParamJSON = getReqParamJSON();
log.info("{} 推送消息:{}", logPrefix, reqParamJSON);
if(reqParamJSON == null || reqParamJSON.isEmpty()) {
return result;
}
String ciphertext = reqParamJSON.getString("ciphertext");
String nonce = reqParamJSON.getString("nonce");
String tag = reqParamJSON.getString("tag");
String iv = reqParamJSON.getString("iv");
String additional_data = reqParamJSON.getString("additional_data");
String signature = reqParamJSON.getString("signature");
try {
// 查询对应打印设备厂商
DeviceProvideConfig provideConfig = deviceProvideConfigService.getOne(DeviceProvideConfig.gw()
.eq(DeviceProvideConfig::getConfigId, configId)
.eq(DeviceProvideConfig::getDeviceType, DeviceProvideConfig.DEVICE_TYPE_PRINTER)
.eq(DeviceProvideConfig::getState, CS.YES)
.eq(DeviceProvideConfig::getProvider, CS.DEVICE_PROVIDER.YLY)
);
if (provideConfig == null || StringUtils.isEmpty(provideConfig.getProviderParams())) {
log.info("{} 厂商信息不存在configId={}", logPrefix, configId);
return result;
}
// 厂商信息获取
YlyParams.ProviderParams providerParams = getParams(JSONObject.parseObject(provideConfig.getProviderParams()));
// 验签
boolean verify = verify(providerParams.getPlatPublicKey(), ciphertext, signature);
if (!verify) {
log.info("{} 验签失败configId={}", logPrefix, configId);
return result;
}
// 解密key
String key = providerParams.getPushPublicKey();
String plaintext = decrypte(Base64.decodeBase64String(ciphertext), key.getBytes(), iv.getBytes(), Base64.decodeBase64String(tag));
log.info("{} 解密文本:{}", logPrefix, plaintext);
// 获取参数
JSONObject payParams = JSONObject.parseObject(plaintext);
// 终端号
String machineCode = payParams.getString("machine_code");
// 设备验证是否存在
MchStoreDevice device = mchStoreDeviceService.getOne(MchStoreDevice.gw()
.eq(MchStoreDevice::getDeviceNo, machineCode)
.eq(MchStoreDevice::getDeviceType, MchStoreDevice.DEVICE_TYPE_PRINTER)
.eq(MchStoreDevice::getState, CS.YES)
);
if (device == null) {
log.info("{} 设备信息不存在deviceId={}", logPrefix, machineCode);
return result;
}
if (StringUtils.isEmpty(device.getMchNo()) || StringUtils.isEmpty(device.getAppId()) || device.getStoreId() == null) {
log.info("{} 设备未绑定请先绑定deviceId={}", logPrefix, machineCode);
return result;
}
ApiRes apiRes = doPayNew(payParams, device);
log.info("{} >> pay >> 下单返回参数.ApiRes={}", logPrefix, apiRes);
if (apiRes.getCode().equals(ApiCodeEnum.CUSTOM_FAIL.getCode())) {
log.info("{} 下单失败apiRes={}", logPrefix, apiRes);
return result;
}
UnifiedOrderRS bizRs = (UnifiedOrderRS) apiRes.getData();
log.info("{} >> pay >> 下单响应参数 bizRs = {}", logPrefix, JSON.toJSON(bizRs));
if (bizRs.getOrderState() == PayOrder.STATE_SUCCESS) {
return result;
}
} catch (BizException e) {
log.error("{} >> pos >> 打印机下单异常:", logPrefix, e);
return result;
} catch (Exception e) {
log.error("{} >> pos >> 打印机下单异常:", logPrefix, e);
return result;
}
return result;
}
private YlyParams.ProviderParams getParams(JSONObject deviceParams) {
YlyParams.ProviderParams providerParams = JSON.parseObject(deviceParams.toJSONString(), YlyParams.ProviderParams.class);
providerParams.setPlatPublicKey(deviceParams.getString("platPublicKey"));
return providerParams;
}
/** 构建下单参数 **/
private ApiRes doPayNew(JSONObject payParams, MchStoreDevice device) {
// 终端号
String machineCode = payParams.getString("machine_code");
// IP
String ip = payParams.getString("ip");
// 下单信息
JSONObject orderPayment = payParams.getJSONObject("order_payment");
// 付款码
String authCode = orderPayment.getString("scanned_code");
// 下单金额
String payAmount = orderPayment.getString("pay_amount");
// 来源参数 1USB键盘 2USB订单 3网络金额
String payAmountSource = orderPayment.getString("pay_amount_source");
// 下单参数
String amount = AmountUtil.convertDollar2Cent(payAmount);
String wayCode = JeepayKit.getPayWayCodeByBarCode(authCode);
UnifiedOrderRQ unifiedOrderRQ = new UnifiedOrderRQ();
// 商户号
unifiedOrderRQ.setMchNo(device.getMchNo());
unifiedOrderRQ.setAppId(device.getAppId());
unifiedOrderRQ.setStoreId(device.getStoreId());
// 商户单号特殊处理
unifiedOrderRQ.setMchOrderNo(SeqKit.genMhoOrderId());
unifiedOrderRQ.setWayCode(wayCode);
unifiedOrderRQ.setAmount(Long.valueOf(amount));
unifiedOrderRQ.setCurrency("CNY");
unifiedOrderRQ.setClientIp(getClientIp());
unifiedOrderRQ.setSubject("标题");
unifiedOrderRQ.setBody("商品信息");
unifiedOrderRQ.setSignType(MchApp.SIGN_TYPE_MD5);
// DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
// 回调地址
// unifiedOrderRQ.setNotifyUrl(dbApplicationConfig.getMchSiteUrl() + "/api/anon/paytestNotify/payOrder");
// 设置扩展参数
JSONObject extParams = new JSONObject();
extParams.put("authCode", authCode.trim());
unifiedOrderRQ.setChannelExtra(extParams.toString());
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setDeviceType(PayOrder.DEVICE_TYPE_PRINTER);
deviceInfo.setDeviceNo(device.getDeviceNo());
deviceInfo.setProvider(device.getProvider());
// 设备信息
unifiedOrderRQ.setDeviceInfo(JSON.toJSONString(deviceInfo));
try {
UnifiedOrderRQ bizRQ = buildBizRQ(unifiedOrderRQ);
return this.unifiedOrder(bizRQ.getWayCode(), bizRQ);
} catch (BizException e) {
throw e;
} catch (Exception e) {
throw new BizException(e.getMessage());
}
}
private UnifiedOrderRQ buildBizRQ(UnifiedOrderRQ rq) {
//支付方式 比如: ali_bar
String wayCode = rq.getWayCode();
//jsapi 收银台聚合支付场景 (不校验是否存在payWayCode)
if (CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)) {
return rq.buildBizRQ();
}
//web收银台聚合支付场景 (不校验是否存在payWayCode)
if (CS.PAY_WAY_CODE.WEB_CASHIER.equals(wayCode)) {
return rq.buildBizRQ();
}
//如果是自动分类条码
if (CS.PAY_WAY_CODE.AUTO_BAR.equals(wayCode)) {
AutoBarOrderRQ bizRQ = (AutoBarOrderRQ) rq.buildBizRQ();
wayCode = JeepayKit.getPayWayCodeByBarCode(bizRQ.getAuthCode());
rq.setWayCode(wayCode.trim());
}
if (payWayService.count(PayWay.gw().eq(PayWay::getWayCode, wayCode)) <= 0) {
throw new BizException("不支持的支付方式");
}
//转换为 bizRQ
return rq.buildBizRQ();
}
private static boolean verify(String publicKeyStr, String originStr, String encrypted) {
try {
byte[] decode = Base64.decodeBase64String(encrypted);
byte[] data = originStr.getBytes();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64String(publicKeyStr));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Signature sha256withRSA = Signature.getInstance("SHA256withRSA");
sha256withRSA.initVerify(publicKey);
sha256withRSA.update(data);
return sha256withRSA.verify(decode);
}catch (Exception e) {
log.error("设备验签失败:", e);
return false;
}
}
}

View File

@@ -0,0 +1,121 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.jeequan.jeepay.pay.ctrl.yly.sdk;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
class HttpRequest {
HttpRequest() {
}
public static String sendGet(String url, Map<String, String> paramMap) {
String param = forMap(paramMap);
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URLConnection connection = getUrlConnection(urlNameString);
connection.connect();
Map<String, List<String>> map = connection.getHeaderFields();
Iterator var8 = map.keySet().iterator();
while(var8.hasNext()) {
String key = (String)var8.next();
System.out.println(key + "--->" + map.get(key));
}
String line;
for(in = new BufferedReader(new InputStreamReader(connection.getInputStream())); (line = in.readLine()) != null; result = result + line) {
}
} catch (Exception var18) {
var18.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception var17) {
var17.printStackTrace();
}
}
return result;
}
public static String sendPost(String url, Map<String, String> paramMap) {
String param = forMap(paramMap);
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try {
URLConnection conn = getUrlConnection(url);
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception var16) {
var16.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException var15) {
var15.printStackTrace();
}
}
return result.toString();
}
private static URLConnection getUrlConnection(String url) throws IOException {
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setConnectTimeout(30000);
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
return conn;
}
private static String forMap(Map<String, String> paramMap) {
String reqStr = "";
if (null != paramMap && !paramMap.isEmpty()) {
Entry entry;
for(Iterator var2 = paramMap.entrySet().iterator(); var2.hasNext(); reqStr = (String)entry.getKey() + "=" + (String)entry.getValue() + "&" + reqStr) {
entry = (Entry)var2.next();
System.out.println("key = " + (String)entry.getKey() + ", value = " + (String)entry.getValue());
}
reqStr = reqStr.substring(0, reqStr.length() - 1);
}
return reqStr;
}
}

View File

@@ -0,0 +1,614 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.jeequan.jeepay.pay.ctrl.yly.sdk;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class RequestMethod {
private static String example = "{\"error\":\"20\",\"error_description\":\"success\",\"body\":\"未完成初始化\"}";
public String clientId;
public String clientSecret;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
private static final RequestMethod singleton = new RequestMethod();
private RequestMethod() {
}
public static RequestMethod getInstance() {
return singleton;
}
public void init(String client_id, String client_secret) {
clientId = client_id;
clientSecret = client_secret;
}
private boolean CCIsNull(String client_id, String client_secret) {
return clientId != null && clientSecret != null && !clientId.equals("") && !clientSecret.equals("");
}
public String getCodeOpen(String redirect_uri) {
return clientId != null && !clientId.equals("") ? UtilUrl.openType + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + redirect_uri + "&state=1" : example;
}
public String getOpenAccessToken(String code) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("client_id", clientId);
paramMap.put("grant_type", "authorization_code");
paramMap.put("sign", sign);
paramMap.put("code", code);
paramMap.put("scope", "all");
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
return HttpRequest.sendPost(UtilUrl.freeType, paramMap);
} else {
return example;
}
}
public String getAccessToken() throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("client_id", clientId);
paramMap.put("grant_type", "client_credentials");
paramMap.put("sign", sign);
paramMap.put("scope", "all");
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
return HttpRequest.sendPost(UtilUrl.freeType, paramMap);
} else {
return example;
}
}
public String getRefreshAccessToken(String refresh_token) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("refresh_token", refresh_token);
paramMap.put("grant_type", "refresh_token");
paramMap.put("scope", "all");
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.freeType, paramMap);
} else {
return example;
}
}
public String addPrinter(String machine_code, String msign, String access_token) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("machine_code", machine_code);
paramMap.put("msign", msign);
paramMap.put("access_token", access_token);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.addPrinter, paramMap);
} else {
return example;
}
}
public String addPrinter(String machine_code, String msign, String access_token, String phone, String print_name) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("machine_code", machine_code);
paramMap.put("msign", msign);
paramMap.put("access_token", access_token);
paramMap.put("phone", phone);
paramMap.put("print_name", print_name);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.addPrinter, paramMap);
} else {
return example;
}
}
public String scanCodeModel(String machine_code, String msign) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("machine_code", machine_code);
paramMap.put("msign", msign);
paramMap.put("scope", "all");
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.scanCodeModel, paramMap);
} else {
return example;
}
}
public String scanCodeModel_msign(String machine_code, String msign) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("machine_code", machine_code);
paramMap.put("msign", msign);
paramMap.put("scope", "all");
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.scanCodeModel, paramMap);
} else {
return example;
}
}
public String printIndex(String access_token, String machine_code, String content, String origin_id) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("content", content);
paramMap.put("origin_id", origin_id);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("idempotence", "0");
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printIndex, paramMap);
} else {
return example;
}
}
public String picturePrintIndex(String access_token, String machine_code, String picture_url, String origin_id) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("picture_url", URLEncoder.encode(picture_url, "UTF-8"));
paramMap.put("origin_id", origin_id);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.picturePrintIndex, paramMap);
} else {
return example;
}
}
public String expressPrintIndex(String access_token, String machine_code, String content, String origin_id) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("content", content);
paramMap.put("origin_id", origin_id);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.expressPrintIndex, paramMap);
} else {
return example;
}
}
public String printerSetVoice(String access_token, String machine_code, String content, String is_file, String aid, String origin_id) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("content", content);
paramMap.put("is_file", is_file);
paramMap.put("aid", aid);
paramMap.put("origin_id", origin_id);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printerSetVoice, paramMap);
} else {
return example;
}
}
public String printerDeleteVoice(String access_token, String machine_code, String aid, String origin_id) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("aid", aid);
paramMap.put("origin_id", origin_id);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printerDeleteVoice, paramMap);
} else {
return example;
}
}
public String printerDeletePrinter(String access_token, String machine_code) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printerDeletePrinter, paramMap);
} else {
return example;
}
}
public String printMenuAddPrintMenu(String access_token, String machine_code, String content) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("content", content);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printMenuAddPrintMenu, paramMap);
} else {
return example;
}
}
public String printShutdownRestart(String access_token, String machine_code, String response_type) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("response_type", response_type);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printShutdownRestart, paramMap);
} else {
return example;
}
}
public String printSetSound(String access_token, String machine_code, String response_type, String voice) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("response_type", response_type);
paramMap.put("voice", voice);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printSetSound, paramMap);
} else {
return example;
}
}
public String printPrintInfo(String access_token, String machine_code) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printPrintInfo, paramMap);
} else {
return example;
}
}
public String printGetVersion(String access_token, String machine_code) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printGetVersion, paramMap);
} else {
return example;
}
}
public String printCancelAll(String access_token, String machine_code) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printCancelAll, paramMap);
} else {
return example;
}
}
public String printCancelOne(String access_token, String machine_code, String order_id) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("order_id", order_id);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printCancelOne, paramMap);
} else {
return example;
}
}
public String printSetIcon(String access_token, String machine_code, String img_url) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("img_url", img_url);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printSetIcon, paramMap);
} else {
return example;
}
}
public String printDeleteIcon(String access_token, String machine_code) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printDeleteIcon, paramMap);
} else {
return example;
}
}
public String printBtnPrint(String access_token, String machine_code, String response_type) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("response_type", response_type);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printBtnPrint, paramMap);
} else {
return example;
}
}
public String printGetOrder(String access_token, String machine_code, String response_type) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("response_type", response_type);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printGetOrder, paramMap);
} else {
return example;
}
}
public String oauthSetPushUrl(String access_token, String machine_code, String cmd, String url, String status) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("cmd", cmd);
paramMap.put("url", url);
paramMap.put("status", status);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.oauthSetPushUrl, paramMap);
} else {
return example;
}
}
public String printerGetOrderStatus(String access_token, String machine_code, String order_id) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("order_id", order_id);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printerGetOrderStatus, paramMap);
} else {
return example;
}
}
public String printerGetOrderPagingList(String access_token, String machine_code, String page_index, String page_size) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("page_index", page_index);
paramMap.put("page_size", page_size);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printerGetOrderPagingList, paramMap);
} else {
return example;
}
}
public String printerGetPrintStatus(String access_token, String machine_code) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printerGetPrintStatus, paramMap);
} else {
return example;
}
}
public String printerSetKeywords(String access_token, String machine_code, String keys, String type, String content) throws Exception {
if (CCIsNull(clientId, clientSecret)) {
String timestamp = Utils.getTimestamp();
String signMD5 = clientId + timestamp + clientSecret;
String sign = Utils.getMD5Str(signMD5);
Map<String, String> paramMap = new HashMap();
paramMap.put("access_token", access_token);
paramMap.put("machine_code", machine_code);
paramMap.put("client_id", clientId);
paramMap.put("timestamp", timestamp);
paramMap.put("id", UUID.randomUUID().toString());
paramMap.put("keys", keys);
paramMap.put("type", type);
paramMap.put("content", content);
paramMap.put("sign", sign);
return HttpRequest.sendPost(UtilUrl.printerSetKeywords, paramMap);
} else {
return example;
}
}
}

View File

@@ -0,0 +1,67 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.jeequan.jeepay.pay.ctrl.yly.sdk;
class UtilUrl {
public static String baseUrl = "https://open-api.10ss.net/v2/";
public static String openType;
public static String freeType;
public static String addPrinter;
public static String scanCodeModel;
public static String printIndex;
public static String picturePrintIndex;
public static String expressPrintIndex;
public static String printerSetVoice;
public static String printerDeleteVoice;
public static String printerDeletePrinter;
public static String printMenuAddPrintMenu;
public static String printShutdownRestart;
public static String printSetSound;
public static String printPrintInfo;
public static String printGetVersion;
public static String printCancelAll;
public static String printCancelOne;
public static String printSetIcon;
public static String printDeleteIcon;
public static String printBtnPrint;
public static String printGetOrder;
public static String oauthSetPushUrl;
public static String printerGetOrderStatus;
public static String printerGetOrderPagingList;
public static String printerGetPrintStatus;
public static String printerSetKeywords;
UtilUrl() {
}
static {
openType = baseUrl + "oauth/authorize";
freeType = baseUrl + "oauth/oauth";
addPrinter = baseUrl + "printer/addprinter";
scanCodeModel = baseUrl + "oauth/scancodemodel";
printIndex = baseUrl + "print/index";
picturePrintIndex = baseUrl + "pictureprint/index";
expressPrintIndex = baseUrl + "expressprint/index";
printerSetVoice = baseUrl + "printer/setvoice";
printerDeleteVoice = baseUrl + "printer/deletevoice";
printerDeletePrinter = baseUrl + "printer/deleteprinter";
printMenuAddPrintMenu = baseUrl + "printmenu/addprintmenu";
printShutdownRestart = baseUrl + "printer/shutdownrestart";
printSetSound = baseUrl + "printer/setsound";
printPrintInfo = baseUrl + "printer/printinfo";
printGetVersion = baseUrl + "printer/getversion";
printCancelAll = baseUrl + "printer/cancelall";
printCancelOne = baseUrl + "printer/cancelone";
printSetIcon = baseUrl + "printer/seticon";
printDeleteIcon = baseUrl + "printer/deleteicon";
printBtnPrint = baseUrl + "printer/btnprint";
printGetOrder = baseUrl + "printer/getorder";
oauthSetPushUrl = baseUrl + "oauth/setpushurl";
printerGetOrderStatus = baseUrl + "printer/getorderstatus";
printerGetOrderPagingList = baseUrl + "printer/getorderpaginglist";
printerGetPrintStatus = baseUrl + "printer/getprintstatus";
printerSetKeywords = baseUrl + "printer/setkeywords";
}
}

View File

@@ -0,0 +1,52 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.jeequan.jeepay.pay.ctrl.yly.sdk;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Utils {
public Utils() {
}
public static String getMD5Str(String str) {
String re = null;
try {
byte[] tem = str.getBytes();
MessageDigest md5 = MessageDigest.getInstance("md5");
md5.reset();
md5.update(tem);
byte[] encrypt = md5.digest();
StringBuilder sb = new StringBuilder();
byte[] var6 = encrypt;
int var7 = encrypt.length;
for(int var8 = 0; var8 < var7; ++var8) {
byte t = var6[var8];
String s = Integer.toHexString(t & 255);
if (s.length() == 1) {
s = "0" + s;
}
sb.append(s);
}
re = sb.toString();
} catch (NoSuchAlgorithmException var11) {
var11.printStackTrace();
}
return re.length() == 31 ? "0" + re : re;
}
public static boolean isNull(String content) {
return content == null || content.equals("");
}
public static String getTimestamp() {
return String.valueOf(System.currentTimeMillis() / 1000L);
}
}

View File

@@ -0,0 +1,509 @@
/**
* cljpay.com
* Copyright (C) 2015-2023 All Rights Reserved.
*/package com.jeequan.jeepay.pay.ctrl.zw;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.ServiceException;
import com.jeequan.jeepay.core.constants.ApiCodeEnum;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApp;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.device.ZwParams;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.model.rqrs.payorder.QueryPayOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRS;
import com.jeequan.jeepay.core.model.rqrs.payorder.payway.AutoBarOrderRQ;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.db.entity.*;
import com.jeequan.jeepay.model.DeviceInfo;
import com.jeequan.jeepay.pay.ctrl.payorder.AbstractPayOrderController;
import com.jeequan.jeepay.pay.service.ChannelOrderReissueService;
import com.jeequan.jeepay.service.impl.*;
import com.jeequan.jeepay.thirdparty.service.zw.model.request.PrintPdRequest;
import com.jeequan.jeepay.thirdparty.service.zw.model.request.UpdatePayParamRequest;
import com.jeequan.jeepay.thirdparty.service.zw.model.request.ZwPayParam;
import com.jeequan.jeepay.thirdparty.service.zw.model.request.ZwPayQueryParam;
import com.jeequan.jeepay.thirdparty.service.zw.uils.SignatureUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* @author yangjun
* @version ZwController.java, v 0.1 2023-02-21 09:58 yangjun
*/
@Slf4j
@RestController
@RequestMapping("/api/zw")
public class ZwController extends AbstractPayOrderController {
@Autowired
private MchStoreDeviceService mchStoreDeviceService;
@Autowired
private MchInfoService mchInfoService;
@Autowired
private DeviceProvideConfigService deviceProvideConfigService;
@Autowired
private MchStoreService mchStoreService;
@Autowired
private MchAppService mchAppService;
@Autowired
private PayWayService payWayService;
@Autowired
private PayOrderService payOrderService;
@Autowired
private ChannelOrderReissueService channelOrderReissueService;
/**
* 支付 校验数据 + 验签
*
* @param reqString
* @param zwPayParam
* @return
*/
private Object checkParamsWithSign(String reqString, ZwPayParam zwPayParam) {
String deviceId = zwPayParam.getSn();
if (StringUtils.isBlank(deviceId)) {
return "设备sn不能为空";
}
String sign = zwPayParam.getMd5();
if (StringUtils.isBlank(sign)) {
return "签名参数不能为空";
}
JSONObject respJson = JSONObject.parseObject(reqString);
String termid = respJson.getString("termid");
// 打印机设备信息
MchStoreDevice mchStoreDevice = mchStoreDeviceService.getOne(MchStoreDevice.gw()
.eq(MchStoreDevice::getDeviceType, MchStoreDevice.DEVICE_TYPE_PRINTER)
.eq(MchStoreDevice::getDeviceNo, deviceId)
.eq(MchStoreDevice::getProvider, termid)
);
if (mchStoreDevice == null) {
return "该设备没有入库";
} else if (mchStoreDevice.getState() != CS.YES) {
return "该设备已停用";
} else if (mchStoreDevice.getBindState() != CS.YES) {
return "该设备没有绑定商户";
}
DeviceProvideConfig deviceProvideConfig = deviceProvideConfigService.getOne(DeviceProvideConfig.gw()
.eq(DeviceProvideConfig::getConfigId, mchStoreDevice.getConfigId())
.eq(DeviceProvideConfig::getDeviceType, MchStoreDevice.DEVICE_TYPE_PRINTER)
.eq(DeviceProvideConfig::getProvider, CS.DEVICE_PROVIDER.ZW));
if (deviceProvideConfig == null) {
return "设备厂商异常";
}
// 验签
ZwParams.ProviderParams zwProviderParams = JSONObject.parseObject(deviceProvideConfig.getProviderParams(), ZwParams.ProviderParams.class);
try {
Boolean checkSign = SignatureUtil.checkIsSignValidFromResponseString(reqString, zwProviderParams.getMd5Key());
if (!checkSign) {
return "验签失败";
}
} catch (Exception ex) {
log.error("ZwController >> checkParamsWithSign >> 智网打印机支付请求验签异常deviceProvideConfig ={}", deviceProvideConfig, ex);
return "验签异常";
}
String provider = deviceProvideConfig.getProvider();
String mchNo = mchStoreDevice.getMchNo();
MchInfo mchInfo = mchInfoService.getById(mchNo);
if (mchInfo == null) {
return "设备绑定商户不存在";
} else if (mchInfo.getState() != CS.YES) {
return "当前商户已停用";
}
String mchAppId = mchStoreDevice.getAppId();
MchAppEntity mchAppEntity = mchAppService.getById(mchAppId);
if (mchAppEntity == null) {
return "设备绑定应用不存在";
} else if (mchAppEntity.getState() != CS.YES) {
return "设备绑定应用已停用";
}
String storeId = mchStoreDevice.getStoreId();
MchStore mchStore = mchStoreService.getById(storeId);
if (mchStore == null) {
return "设备绑定门店不存在";
}
JSONObject deviceParamsJSON = new JSONObject();
deviceParamsJSON.put("mchInfo", mchInfo);
deviceParamsJSON.put("mchStore", mchStore);
deviceParamsJSON.put("mchApp", mchAppEntity);
deviceParamsJSON.put("provider", provider);
deviceParamsJSON.put("deviceNo", mchStoreDevice.getDeviceNo());
return deviceParamsJSON;
}
/**
* 构建小票
*
* @param bizRes
* @param deviceSn
* @return
*/
private List<PrintPdRequest> buildTicket(QueryPayOrderRS bizRes, String deviceSn) {
// 判断设备支付小票打印权限
MchStoreDevice dbRecord = mchStoreDeviceService.getOne(MchStoreDevice.gw()
.eq(MchStoreDevice::getDeviceType, MchStoreDevice.DEVICE_TYPE_PRINTER)
.eq(MchStoreDevice::getDeviceNo, deviceSn));
UpdatePayParamRequest updatePayParamRequest = JSONObject.parseObject(dbRecord.getDeviceParams(), UpdatePayParamRequest.class);
if (updatePayParamRequest != null && updatePayParamRequest.getBill_pay_print() != null && updatePayParamRequest.getBill_pay_print() == 0) {
return null;
}
List<PrintPdRequest> printPdRequests = new ArrayList<>();
PrintPdRequest printPdRequest = new PrintPdRequest();
printPdRequest.setTp("3");
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("支付凭证");
printPdRequest.setAl("1");
printPdRequest.setFs("3");
printPdRequest.setTb("1");
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("4");
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("门店名称:" + bizRes.getStoreId());
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("订 单 号:" + bizRes.getPayOrderId());
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("商户单号:" + bizRes.getMchOrderNo());
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("支付状态:" + "");
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("支付方式:" + CS.PAY_WAY_CODE_TYPE_MAP.get(bizRes.getWayCodeType()));
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("下单时间:" + "");
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("4");
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("完成时间:" + "");
printPdRequests.add(printPdRequest);
printPdRequest = new PrintPdRequest();
printPdRequest.setTp("1");
printPdRequest.setCv("交易金额:" + AmountUtil.convertCent2Dollar(bizRes.getAmount().toString()));
printPdRequests.add(printPdRequest);
return printPdRequests;
}
/**
* 支付
*
* @param request
* @return
*/
@RequestMapping("/pay")
public ZwPosRes pay(HttpServletRequest request) {
String resultStr = getBody(request);
log.info("ZwController >> pay >> 智网打印机支付请求:{}", resultStr);
ZwPayParam zwPayParam = JSONObject.parseObject(resultStr, ZwPayParam.class);
if (StringUtils.isBlank(zwPayParam.getAmount())) {
return ZwPosRes.error(zwPayParam.getUuid(), "支付金额不能为空");
}
if (StringUtils.isBlank(zwPayParam.getPaycode())) {
return ZwPosRes.error(zwPayParam.getUuid(), "条码不能为空");
}
// 验证通用请求字段、签名true
Object object = checkParamsWithSign(resultStr, zwPayParam);
if (object instanceof String) {
log.info("ZwController >> pay >> 智网打印机支付,验证不过.error={}", object);
return ZwPosRes.error(zwPayParam.getUuid(), (String) object);
}
JSONObject deviceJSON = (JSONObject) object;
try {
ApiRes apiRes = doPayNew(zwPayParam, deviceJSON);
log.info("ZwController >> pay >> 下单返回参数.ApiRes={}", apiRes);
if (apiRes.getCode().equals(ApiCodeEnum.CUSTOM_FAIL.getCode())) {
return ZwPosRes.error(zwPayParam.getUuid(), apiRes.getMsg());
}
UnifiedOrderRS bizRs = (UnifiedOrderRS) apiRes.getData();
log.info("ZwController >> pay >> 下单响应参数 bizRs = {}", JSON.toJSON(bizRs));
// 支付小票
if (bizRs.getOrderState() == PayOrder.STATE_SUCCESS) {
ZwPosRes zwPosRes = ZwPosRes.success(zwPayParam.getUuid(), "支付成功");
log.info("ZwController >> pay >> 智网打印机支付 zwPosRes = {}", JSON.toJSON(zwPosRes));
return zwPosRes;
} else if (bizRs.getOrderState() == PayOrder.STATE_FAIL
|| bizRs.getOrderState() == PayOrder.STATE_CANCEL
|| bizRs.getOrderState() == PayOrder.STATE_REFUND
|| bizRs.getOrderState() == PayOrder.STATE_CLOSED
) {
ZwPosRes zwPosRes = ZwPosRes.error(zwPayParam.getUuid(), bizRs.getErrCode() + bizRs.getErrMsg());
log.info("ZwController >> pay >> 智网打印机支付 zwPosRes = {}", JSON.toJSON(zwPosRes));
return zwPosRes;
}
ZwPosRes zwPosRes = ZwPosRes.ing(zwPayParam.getUuid(), "支付中");
log.info("ZwController >> pay >> 智网打印机支付 zwPosRes = {}", JSON.toJSON(zwPosRes));
return zwPosRes;
} catch (BizException e) {
log.error("PosController >> pos >> 打印机下单异常:", e);
return ZwPosRes.error("StringPool.EMPTY", e.getMessage());
}
}
/**
* 支付查单
**/
@RequestMapping("/query")
public ZwPosRes query(HttpServletRequest request) {
String resultStr = getBody(request);
ZwPayParam zwPayParam = JSONObject.parseObject(resultStr, ZwPayParam.class);
log.info("ZwController >> pay >> 智网打印机支付查单请求zwPayParam: {}", JSON.toJSON(zwPayParam));
ZwPayQueryParam zwPayQueryParam = JSONObject.parseObject(resultStr, ZwPayQueryParam.class);
if (StringUtils.isBlank(zwPayQueryParam.getUuid())) {
return ZwPosRes.ing(zwPayQueryParam.getUuid(), "订单号不能为空");
}
// 验证通用请求字段、签名true
Object object = checkParamsWithSign(resultStr, zwPayParam);
if (object instanceof String) {
return ZwPosRes.error(zwPayParam.getUuid(), (String) object);
}
JSONObject deviceJSON = (JSONObject) object;
String merchantId = deviceJSON.getJSONObject("mchInfo").getString("mchNo");
// 支持根据商户单号查询
PayOrder payOrder = payOrderService.queryMchOrder(merchantId, null, zwPayQueryParam.getUuid());
if (payOrder == null) {
return ZwPosRes.ing(zwPayQueryParam.getUuid(), "订单信息错误");
}
// 支付中主查
if (PayOrder.STATE_ING == payOrder.getState()) {
ChannelRetMsg channelRetMsg = channelOrderReissueService.processPayOrder(payOrder);
if (channelRetMsg != null && channelRetMsg.getChannelState() != null && channelRetMsg.getChannelState().equals(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS)) {
return ZwPosRes.success(zwPayQueryParam.getUuid(), null);
}
}
// 支付小票
if (payOrder.getState() == PayOrder.STATE_SUCCESS) {
List<PrintPdRequest> printPdRequests = buildTicket(QueryPayOrderRS.buildByPayOrder(payOrder), zwPayQueryParam.getSn());
ZwPosRes zwPosRes = ZwPosRes.success(zwPayQueryParam.getUuid(), printPdRequests.toString());
log.info("ZwController >> pay >> 智网打印机支付 zwPosRes = {}", JSON.toJSON(zwPosRes));
return zwPosRes;
} else if (payOrder.getState() == PayOrder.STATE_FAIL
|| payOrder.getState() == PayOrder.STATE_CANCEL
|| payOrder.getState() == PayOrder.STATE_REFUND
|| payOrder.getState() == PayOrder.STATE_CLOSED
) {
return ZwPosRes.error(zwPayQueryParam.getUuid(), payOrder.getErrMsg());
}
return ZwPosRes.ing(zwPayQueryParam.getUuid(), "支付中");
}
/**
* 智网打印机 支付优化
*
* @param zwPayParam
* @param deviceJSON
* @return
*/
private ApiRes doPayNew(ZwPayParam zwPayParam, JSONObject deviceJSON) {
// 下单参数
String amount = AmountUtil.convertDollar2Cent(zwPayParam.getAmount());
String wayCode = JeepayKit.getPayWayCodeByBarCode(zwPayParam.getPaycode());
// 付款码
String authCode = zwPayParam.getPaycode();
MchAppEntity mchAppEntity = deviceJSON.getObject("mchApp", MchAppEntity.class);
MchStore mchStore = deviceJSON.getObject("mchStore", MchStore.class);
UnifiedOrderRQ unifiedOrderRQ = new UnifiedOrderRQ();
// 商户号
unifiedOrderRQ.setMchNo(mchAppEntity.getMchNo());
unifiedOrderRQ.setAppId(mchAppEntity.getAppId());
unifiedOrderRQ.setStoreId(mchStore.getStoreId());
// 商户单号特殊处理
String deviceOrderNum = zwPayParam.getUuid();
if (StringUtils.isNotBlank(deviceOrderNum)) {
unifiedOrderRQ.setMchOrderNo(deviceOrderNum);
} else {
unifiedOrderRQ.setMchOrderNo(SeqKit.genMhoOrderId());
}
unifiedOrderRQ.setWayCode(wayCode);
unifiedOrderRQ.setAmount(Long.valueOf(amount));
unifiedOrderRQ.setCurrency("CNY");
unifiedOrderRQ.setClientIp(getClientIp());
unifiedOrderRQ.setSubject("标题");
unifiedOrderRQ.setBody("商品信息");
unifiedOrderRQ.setSignType(MchApp.SIGN_TYPE_MD5);
// DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
// 回调地址
// unifiedOrderRQ.setNotifyUrl(dbApplicationConfig.getMchSiteUrl() + "/api/anon/paytestNotify/payOrder");
// 设置扩展参数
JSONObject extParams = new JSONObject();
extParams.put("authCode", authCode.trim());
unifiedOrderRQ.setChannelExtra(extParams.toString());
// 设备信息
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setDeviceType(PayOrder.DEVICE_TYPE_PRINTER);
deviceInfo.setDeviceNo(deviceJSON.getString("deviceNo"));
deviceInfo.setProvider(deviceJSON.getString("provider"));
unifiedOrderRQ.setDeviceInfo(JSON.toJSONString(deviceInfo));
try {
UnifiedOrderRQ bizRQ = buildBizRQ(unifiedOrderRQ);
return this.unifiedOrder(bizRQ.getWayCode(), bizRQ);
} catch (BizException e) {
throw e;
} catch (Exception e) {
throw new BizException(e.getMessage());
}
}
/**
* 统一下单
*
* @param rq
* @return
*/
public ApiRes unifiedOrder(UnifiedOrderRQ rq) {
UnifiedOrderRQ bizRQ = buildBizRQ(rq);
//实现子类的res
ApiRes apiRes = unifiedOrder(bizRQ.getWayCode(), bizRQ);
if (apiRes.getData() == null) {
return apiRes;
}
UnifiedOrderRS bizRes = (UnifiedOrderRS) apiRes.getData();
//聚合接口,返回的参数
UnifiedOrderRS res = new UnifiedOrderRS();
BeanUtils.copyProperties(bizRes, res);
//只有 订单生成QR_CASHIER || 支付中 || 支付成功返回该数据
if (bizRes.getOrderState() != null
&& (bizRes.getOrderState() == PayOrder.STATE_INIT
|| bizRes.getOrderState() == PayOrder.STATE_ING
|| bizRes.getOrderState() == PayOrder.STATE_SUCCESS)) {
res.setPayDataType(bizRes.buildPayDataType());
res.setPayData(bizRes.buildPayData());
}
return ApiRes.ok(res);
}
/**
* 支付参数构建
*
* @param rq
* @return
*/
private UnifiedOrderRQ buildBizRQ(UnifiedOrderRQ rq) {
//支付方式 比如: ali_bar
String wayCode = rq.getWayCode();
//jsapi 收银台聚合支付场景 (不校验是否存在payWayCode)
if (CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)) {
return rq.buildBizRQ();
}
//web收银台聚合支付场景 (不校验是否存在payWayCode)
if (CS.PAY_WAY_CODE.WEB_CASHIER.equals(wayCode)) {
return rq.buildBizRQ();
}
//如果是自动分类条码
if (CS.PAY_WAY_CODE.AUTO_BAR.equals(wayCode)) {
AutoBarOrderRQ bizRQ = (AutoBarOrderRQ) rq.buildBizRQ();
wayCode = JeepayKit.getPayWayCodeByBarCode(bizRQ.getAuthCode());
rq.setWayCode(wayCode.trim());
}
if (payWayService.count(PayWay.gw().eq(PayWay::getWayCode, wayCode)) <= 0) {
throw new BizException("不支持的支付方式");
}
//转换为 bizRQ
return rq.buildBizRQ();
}
/**
* 获取请求数据
*
* @param request
* @return
*/
protected final String getBody(HttpServletRequest request) {
InputStreamReader in = null;
try {
in = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8);
StringBuffer bf = new StringBuffer();
int len;
char[] chs = new char[1024];
while ((len = in.read(chs)) != -1) {
bf.append(new String(chs, 0, len));
}
return bf.toString();
} catch (Exception e) {
log.error("请求头部取数据异常:{}", e.getMessage());
throw new ServiceException(e.getMessage());
} finally {
if (null != in) {
try {
in.close();
} catch (Exception e) {
log.error("流关闭异常:{}", e.getMessage());
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
package com.jeequan.jeepay.pay.ctrl.zw;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ZwPosRes {
/** 业务响应码 **/
private Integer code;
/** 业务响应信息 **/
private String message;
/** 数据对象 **/
private String uuid;
public static ZwPosRes ing(String uuid, String message) {
return new ZwPosRes(2, message, uuid);
}
public static ZwPosRes error(String uuid, String message) {
return new ZwPosRes(1, message, uuid);
}
public static ZwPosRes success(String uuid, String message) {
return new ZwPosRes(0, message, uuid);
}
}

View File

@@ -0,0 +1,22 @@
package com.jeequan.jeepay.pay.model;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.rqrs.payorder.UnifiedOrderRQ;
import com.jeequan.jeepay.db.entity.RouteManage;
import lombok.Data;
/**
* TODO
*
* @author crystal
* @date 2024/5/14 13:49
*/
@Data
public class OpenApiExt {
private MchAppConfigContext configContext;
private RouteManage routeManage;
private UnifiedOrderRQ urq;
}

View File

@@ -0,0 +1,26 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.CleanTnDayCacheMQ;
import com.jeequan.jeepay.core.cache.RedisUtil;
import com.jeequan.jeepay.core.constants.CS;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 清空所有T+N日期缓存
* @author zx
* @date 2023/9/22 9:23
*/
@Slf4j
@Component
public class CleanTnDayCacheMQReceiver implements CleanTnDayCacheMQ.IMQReceiver {
@Override
public void receive(CleanTnDayCacheMQ.MsgPayload payload) {
log.info("成功接收【清空所有T+N日期缓存】的订阅通知, msg={}", payload);
RedisUtil.delByPrefix(CS.CACHE_KEY_DATE_TN_PREFIX);
log.info("系统已清空所有T+N日期缓存");
}
}

View File

@@ -0,0 +1,98 @@
package com.jeequan.jeepay.pay.mq;
import cn.hutool.extra.spring.SpringUtil;
import com.jeequan.jeepay.components.mq.model.MchAuditMQ;
import com.jeequan.jeepay.components.mq.model.MchAuditThirdNotifyMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.converter.MchInfoConverter;
import com.jeequan.jeepay.core.cache.RedisUtil;
import com.jeequan.jeepay.db.entity.MchApplyment;
import com.jeequan.jeepay.pay.mq.channel.IsvAuditReceiver;
import com.jeequan.jeepay.service.impl.MchApplymentService;
import com.jeequan.jeepay.thirdparty.channel.yspay.YspayApplymentApiService;
import com.jeequan.jeepay.thirdparty.channel.yspay.YspayMchApplymentService;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
@Component
public class MchAuditMQReceiver implements MchAuditMQ.IMQReceiver {
@Autowired
private MchApplymentService mchApplymentService;
@Autowired
private YspayApplymentApiService yspayApplymentApiService;
@Autowired
private YspayMchApplymentService yspayMchApplymentService;
@Autowired
private ConfigContextQueryService configContextQueryService;
@Autowired
private IMQSender mqSender;
@Autowired
private MchInfoConverter infoConverter;
@Override
public void receive(MchAuditMQ.MsgPayload payload) {
String applyId = payload.getApplyId();
MchApplyment mchApplyment = mchApplymentService.getById(applyId);
if (mchApplyment.getState() != MchApplyment.STATE_AUDITING_WAIT) {
return;
}
if (mchApplyment.getRemainStep() <= 0) {
// 没有剩余处理步骤了,不作处理
return;
}
// 添加正在进件的一个键值,防止进件时间过长状态,队列重复处理
String redisKey = "auditApplyId_" + mchApplyment.getApplyId();
if (RedisUtil.hasKey(redisKey)) {
// 正在处理就将塞一条新的消息到rabbitmq中
mqSender.send(MchAuditMQ.build(mchApplyment.getApplyId()), 20);
return;
} else {
// 设置60秒超时若上传图片速度实在太慢还是需要升级服务器带宽
RedisUtil.set(redisKey, "1", 120);
}
IsvAuditReceiver isvAuditReceiver = SpringUtil.getBean(mchApplyment.getIfCode() + "AuditReceiver", IsvAuditReceiver.class);
mchApplyment.setLastApplyAt(new Date());
try {
isvAuditReceiver.consume(mchApplyment);
if (mchApplyment.getState() == MchApplyment.STATE_AUDITING_WAIT) {
return;
}
RedisUtil.del(redisKey);
mqSender.send(MchAuditThirdNotifyMQ.build(mchApplyment.getApplyId(), MchAuditThirdNotifyMQ.TYPE_AUDIT));
} catch (Exception e) {
log.info("进件失败, 进件单号{}", applyId, e);
MchApplyment result = new MchApplyment();
result.setApplyId(applyId);
result.setState(MchApplyment.STATE_REJECT_WAIT_MODIFY);
result.setApplyErrorInfo("[进件失败, 请联系客服处理]");
mchApplymentService.updateById(result);
}
}
}

View File

@@ -0,0 +1,70 @@
package com.jeequan.jeepay.pay.mq;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.components.mq.model.MchAuditNotifyMQ;
import com.jeequan.jeepay.components.mq.model.MchAuditThirdNotifyMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.converter.MchInfoConverter;
import com.jeequan.jeepay.core.interfaces.paychannel.IIsvmchApplymentNotifyService;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.db.entity.MchApplyment;
import com.jeequan.jeepay.service.impl.MchApplymentService;
import com.jeequan.jeepay.thirdparty.channel.yspay.YspayApplymentApiService;
import com.jeequan.jeepay.thirdparty.channel.yspay.YspayMchApplymentService;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
@Component
public class MchAuditNotifyMQReceiver implements MchAuditNotifyMQ.IMQReceiver {
@Autowired
private MchApplymentService mchApplymentService;
@Autowired
private YspayApplymentApiService yspayApplymentApiService;
@Autowired
private YspayMchApplymentService yspayMchApplymentService;
@Autowired
private ConfigContextQueryService configContextQueryService;
@Autowired
private IMQSender mqSender;
@Autowired
private MchInfoConverter infoConverter;
@Override
public void receive(MchAuditNotifyMQ.MsgPayload payload) {
JSONObject notifyJSON = payload.getNotifyData();
String ifCode = payload.getIfCode();
IIsvmchApplymentNotifyService notifyService = SpringBeansUtil.getBean(ifCode + "IsvmchApplymentNotifyService", IIsvmchApplymentNotifyService.class);
Assert.notNull(notifyService, "通知回调服务类缺失");
MutablePair<com.jeequan.jeepay.core.entity.MchApplyment, ResponseEntity> responseEntityMutablePair = notifyService.doNotify(null, notifyJSON, null);
com.jeequan.jeepay.core.entity.MchApplyment result = responseEntityMutablePair.getLeft();
mchApplymentService.updateById(infoConverter.toDbEntity(result));
if (result.getState() == MchApplyment.STATE_SUCCESS || result.getState() == MchApplyment.STATE_SUCCESS_NEED_SECOND_VERIFY) {
mchApplymentService.onApplymentSuccess(result.getApplyId());
mqSender.send(MchAuditThirdNotifyMQ.build(result.getApplyId(), MchAuditThirdNotifyMQ.TYPE_AUDIT));
}
log.info("进件通知完成。 applyId={}, parseState = {} =====", result.getApplyId(), result.getState());
}
}

View File

@@ -0,0 +1,190 @@
package com.jeequan.jeepay.pay.mq;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.jeequan.jeepay.components.mq.model.MchAuditThirdNotifyMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.converter.MchInfoConverter;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.model.openapi.AccessError;
import com.jeequan.jeepay.core.model.openapi.AccessReq;
import com.jeequan.jeepay.core.model.openapi.AccessResp;
import com.jeequan.jeepay.core.model.openapi.biz.AuditResultResp;
import com.jeequan.jeepay.db.entity.MchAppEntity;
import com.jeequan.jeepay.db.entity.MchApplyment;
import com.jeequan.jeepay.db.entity.MchNotifyRecord;
import com.jeequan.jeepay.db.entity.MchStore;
import com.jeequan.jeepay.pay.util.ApiResKit;
import com.jeequan.jeepay.service.impl.MchAppService;
import com.jeequan.jeepay.service.impl.MchApplymentService;
import com.jeequan.jeepay.service.impl.MchNotifyRecordService;
import com.jeequan.jeepay.service.impl.MchStoreService;
import com.jeequan.jeepay.thirdparty.channel.yspay.YspayApplymentApiService;
import com.jeequan.jeepay.thirdparty.channel.yspay.YspayMchApplymentService;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.AllArgsConstructor;
import lombok.Cleanup;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
@Component
public class MchAuditThirdNotifyMQReceiver implements MchAuditThirdNotifyMQ.IMQReceiver {
@Autowired
private MchApplymentService mchApplymentService;
@Autowired
private YspayApplymentApiService yspayApplymentApiService;
@Autowired
private YspayMchApplymentService yspayMchApplymentService;
@Autowired
private ConfigContextQueryService configContextQueryService;
@Autowired
private IMQSender mqSender;
@Autowired
private MchInfoConverter infoConverter;
@Autowired
private MchNotifyRecordService mchNotifyRecordService;
@Autowired
private MchAppService mchAppService;
@Autowired
private MchInfoConverter mchInfoConverter;
@Autowired
private MchStoreService mchStoreService;
@Override
public void receive(MchAuditThirdNotifyMQ.MsgPayload payload) {
try {
log.info("接收商户通知MQ, msg={}", payload.toString());
MchApplyment mchApplyment = mchApplymentService.getById(payload.getApplyId());
// 通知地址为空
if (ObjUtil.isEmpty(mchApplyment.getNotifyUrl())) {
return;
}
String orderId = mchApplyment.getApplyId() + "_" + mchApplyment.getUpdatedAt().getTime();
MchNotifyRecord auditOrder = mchNotifyRecordService.findAuditOrder(orderId);
MchStore mchStore = mchStoreService.getByMchApplyId(mchApplyment.getApplyId());
if (auditOrder != null) {
return;
}
AccessResp body = bulidNotifyData(mchApplyment, mchStore);
auditOrder = new MchNotifyRecord();
auditOrder.setOrderId(orderId);
auditOrder.setOrderType(MchNotifyRecord.TYPE_AUDIT);
auditOrder.setMchNo(mchApplyment.getMchNo());
auditOrder.setIsvNo(mchApplyment.getIsvNo());
auditOrder.setNotifyBody(JSON.toJSONString(body));
auditOrder.setAppId(mchApplyment.getAutoConfigMchAppId());
auditOrder.setNotifyUrl(mchApplyment.getNotifyUrl());
auditOrder.setNotifyPostType(CS.NOTIFY_POST_TYPE.POST_JSON);
auditOrder.setResResult("");
auditOrder.setNotifyCount(0);
auditOrder.setMchOrderNo(mchApplyment.getSubApplyId());
auditOrder.setState(MchNotifyRecord.STATE_ING);
try {
mchNotifyRecordService.save(auditOrder);
} catch (Exception e) {
log.info("回调通知数据插入失败", e);
log.info("数据库已存在[{}]消息,本次不再推送。", auditOrder.getOrderId());
return;
}
MchNotifyRecord record = mchNotifyRecordService.findAuditOrder(orderId);
if(record == null || record.getState() != MchNotifyRecord.STATE_ING){
log.info("查询通知记录不存在或状态不是通知中");
return;
}
if( record.getNotifyCount() >= record.getNotifyCountLimit() ){
log.info("已达到最大发送次数");
return;
}
Long notifyId = record.getNotifyId();
//1. (发送结果最多6次)
int currentCount = record.getNotifyCount() + 1;
String notifyUrl = record.getNotifyUrl();
String res = "";
try {
int timeout = 20000;
// body形式
@Cleanup HttpResponse httpResponse = HttpUtil.createPost(record.getNotifyUrl())
.body(record.getNotifyBody(), ContentType.JSON.getValue())
.timeout(timeout)
.execute();
res = httpResponse.body();
} catch (Exception e) {
log.error("http error", e);
res = "连接["+ UrlBuilder.of(notifyUrl).getHost() +"]异常:【" + e.getMessage() + "";
}
//通知成功
if("SUCCESS".equalsIgnoreCase(res)){
mchNotifyRecordService.updateNotifyResult(notifyId, MchNotifyRecord.STATE_SUCCESS, res);
return;
}
//通知次数 >= 最大通知次数时, 更新响应结果为异常, 不在继续延迟发送消息
if( currentCount >= record.getNotifyCountLimit() ){
mchNotifyRecordService.updateNotifyResult(notifyId, MchNotifyRecord.STATE_FAIL, res);
return;
}
// 继续发送MQ 延迟发送
mchNotifyRecordService.updateNotifyResult(notifyId, MchNotifyRecord.STATE_ING, res);
// 通知延时次数
// 1 2 3 4 5 6
// 0 30 60 90 120 150
mqSender.send(MchAuditThirdNotifyMQ.build(payload.getApplyId(), MchAuditThirdNotifyMQ.TYPE_AUDIT), currentCount * 30);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 通知参数
*
* @param payOrder
* @param mchStore
* @return
*/
private AccessResp bulidNotifyData(MchApplyment payOrder, MchStore mchStore) {
AccessReq req = new AccessReq();
req.setAppId(payOrder.getAutoConfigMchAppId());
MchAppEntity mchApp = mchAppService.getById(payOrder.getAutoConfigMchAppId());
req.setAppInfo(mchInfoConverter.toModel(mchApp));
req.setPlatPriKey(ApiResKit.getKey());
AuditResultResp bizResp = new AuditResultResp(mchInfoConverter.toModel(payOrder));
bizResp.setMchStore(mchStore);
// 生成通知
return new AccessResp(AccessError.SUCCESS, bizResp, req);
}
}

View File

@@ -0,0 +1,36 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.PayOrderCashoutMQ;
import com.jeequan.jeepay.pay.service.MchChannelCashoutService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 订单支付成功自动提现通知
* @author zx
* @date 2021/7/27 9:23
*/
@Slf4j
@Component
public class PayOrderCashoutMQReceiver implements PayOrderCashoutMQ.IMQReceiver {
@Autowired private MchChannelCashoutService mchChannelCashoutService;
@Override
public void receive(PayOrderCashoutMQ.MsgPayload payload) {
try {
log.info("接收商户通知MQ, msg={}", payload.toString());
String payOrderId = payload.getPayOrderId();
// 发起提现
mchChannelCashoutService.cashout(payOrderId);
}catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,33 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
import com.jeequan.jeepay.pay.service.PayOrderDivisionProcessService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 支付订单分账处理逻辑
* @author terrfly
* @date 2021/8/22 8:23
*/
@Slf4j
@Component
public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceiver {
@Autowired private PayOrderDivisionProcessService payOrderDivisionProcessService;
@Override
public void receive(PayOrderDivisionMQ.MsgPayload payload) {
try {
log.info("接收订单分账通知MQ, msg={}", payload.toString());
payOrderDivisionProcessService.processPayOrderDivision(payload.getPayOrderId(), payload.getUseSysAutoDivisionReceivers(), payload.getReceiverList(), payload.getIsResend(), payload.getIsResendAndRecalAmount());
}catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,32 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.PayOrderMarketMQ;
import com.jeequan.jeepay.pay.service.PayOrderMarketReissueService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* TODO
* 订单营销活动消费类
* @author crystal
* @date 2024/1/29 16:10
*/
@Slf4j
@Component
public class PayOrderMarketMQReceiver implements PayOrderMarketMQ.IMQReceiver {
@Autowired
private PayOrderMarketReissueService payOrderMarketReissueService;
@Override
public void receive(PayOrderMarketMQ.MsgPayload payload) {
try {
log.info("接收订单营销转账通知MQ, msg={}", payload.toString());
payOrderMarketReissueService.transferReissue(payload.getTransferId(), payload.getType());
}catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,99 @@
package com.jeequan.jeepay.pay.mq;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.jeequan.jeepay.components.mq.model.PayOrderMchNotifyMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.db.entity.MchNotifyRecord;
import com.jeequan.jeepay.service.impl.MchNotifyRecordService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 接收MQ消息
* 业务: 支付订单商户通知
* @author terrfly
* @date 2021/7/27 9:23
*/
@Slf4j
@Component
public class PayOrderMchNotifyMQReceiver implements PayOrderMchNotifyMQ.IMQReceiver {
@Autowired
private PayOrderService payOrderService;
@Autowired
private MchNotifyRecordService mchNotifyRecordService;
@Autowired
private IMQSender mqSender;
@Override
public void receive(PayOrderMchNotifyMQ.MsgPayload payload) {
try {
log.info("接收商户通知MQ, msg={}", payload.toString());
Long notifyId = payload.getNotifyId();
MchNotifyRecord record = mchNotifyRecordService.getById(notifyId);
if(record == null || record.getState() != MchNotifyRecord.STATE_ING){
log.info("查询通知记录不存在或状态不是通知中");
return;
}
if( record.getNotifyCount() >= record.getNotifyCountLimit() ){
log.info("已达到最大发送次数");
return;
}
//1. (发送结果最多6次)
Integer currentCount = record.getNotifyCount() + 1;
String notifyUrl = record.getNotifyUrl();
String res = "";
try {
int timeout = 20000;
// body形式
res = HttpUtil.createPost(record.getNotifyUrl()).body(record.getNotifyBody(), CharsetUtil.CHARSET_UTF_8.name())
.contentType(MediaType.APPLICATION_JSON_VALUE).timeout(timeout).execute().body();
} catch (Exception e) {
log.error("http error", e);
res = "连接["+ UrlBuilder.of(notifyUrl).getHost() +"]异常:【" + e.getMessage() + "";
}
//支付订单 & 第一次通知: 更新为已通知
if(currentCount == 1 && MchNotifyRecord.TYPE_PAY_ORDER == record.getOrderType()){
payOrderService.updateNotifySent(record.getOrderId());
}
//通知成功
if("SUCCESS".equalsIgnoreCase(res)){
mchNotifyRecordService.updateNotifyResult(notifyId, MchNotifyRecord.STATE_SUCCESS, res);
return;
}
//通知次数 >= 最大通知次数时, 更新响应结果为异常, 不在继续延迟发送消息
if( currentCount >= record.getNotifyCountLimit() ){
mchNotifyRecordService.updateNotifyResult(notifyId, MchNotifyRecord.STATE_FAIL, res);
return;
}
// 继续发送MQ 延迟发送
mchNotifyRecordService.updateNotifyResult(notifyId, MchNotifyRecord.STATE_ING, res);
// 通知延时次数
// 1 2 3 4 5 6
// 0 30 60 90 120 150
mqSender.send(PayOrderMchNotifyMQ.build(notifyId), currentCount * 30);
return;
}catch (Exception e) {
log.error(e.getMessage(), e);
return;
}
}
}

View File

@@ -0,0 +1,76 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.PayOrderReissueMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.db.entity.PayOrder;
import com.jeequan.jeepay.pay.service.ChannelOrderReissueService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 支付订单补单(一般用于没有回调的接口,比如微信的条码支付)
* @author terrfly
* @date 2021/7/27 9:23
*/
@Slf4j
@Component
public class PayOrderReissueMQReceiver implements PayOrderReissueMQ.IMQReceiver {
@Autowired
private IMQSender mqSender;
@Autowired
private PayOrderService payOrderService;
@Autowired
private ChannelOrderReissueService channelOrderReissueService;
@Override
public void receive(PayOrderReissueMQ.MsgPayload payload) {
try {
String payOrderId = payload.getPayOrderId();
int currentCount = payload.getCount();
log.info("接收轮询查单通知MQ, payOrderId={}, count={}", payOrderId, currentCount);
currentCount++ ;
PayOrder payOrder = payOrderService.getById(payOrderId);
if(payOrder == null) {
log.warn("查询支付订单为空,payOrderId={}", payOrderId);
return;
}
if(payOrder.getState() != PayOrder.STATE_ING) {
log.warn("订单状态不是支付中,不需查询渠道.payOrderId={}", payOrderId);
return;
}
// 是否调用渠道独立的轮询查单接口
payOrder.addExt("isReissueByPoll", true);
// 轮询次数
payOrder.addExt("pollCount", currentCount);
ChannelRetMsg channelRetMsg = channelOrderReissueService.processPayOrder(payOrder);
//返回null 可能为接口报错等, 需要再次轮询
if(channelRetMsg == null || channelRetMsg.getChannelState() == null || channelRetMsg.getChannelState().equals(ChannelRetMsg.ChannelState.WAITING)){
//最多查询6次
if(currentCount <= 6){
mqSender.send(PayOrderReissueMQ.build(payOrderId, currentCount), 5); //延迟5s再次查询
}else{
//TODO 调用【撤销订单】接口
}
}else{ //其他状态, 不需要再次轮询。
}
}catch (Exception e) {
log.error(e.getMessage());
return;
}
}
}

View File

@@ -0,0 +1,37 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.PushPrinterMQ;
import com.jeequan.jeepay.pay.service.payorderpush.impl.PushPrinterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 新订单提醒 通知
* @author zx
* @date 2021/01/04 15:25
*/
@Slf4j
@Component
public class PushPrinterMQReceiver implements PushPrinterMQ.IMQReceiver {
@Autowired private PushPrinterService pushPrinterService;
@Override
public void receive(PushPrinterMQ.MsgPayload payload) {
log.info("接收支付成功云打印MQ, msg={}", payload.toString());
String orderId = payload.getOrderId();
Byte orderType = payload.getOrderType();
// 云打印
pushPrinterService.send(orderId, orderType);
log.info("云打印业务结束。");
}
}

View File

@@ -0,0 +1,37 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.PushSpeakerMQ;
import com.jeequan.jeepay.pay.service.payorderpush.impl.PushSpeakerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 新订单提醒 通知
* @author zx
* @date 2021/01/04 15:25
*/
@Slf4j
@Component
public class PushSpeakerMQReceiver implements PushSpeakerMQ.IMQReceiver {
@Autowired private PushSpeakerService pushSpeakerService;
@Override
public void receive(PushSpeakerMQ.MsgPayload payload) {
log.info("接收支付成功云喇叭播报MQ, msg={}", payload.toString());
String orderId = payload.getOrderId();
Byte orderType = payload.getOrderType();
// 云喇叭播报
pushSpeakerService.send(orderId, orderType);
log.info("云喇叭播报业务结束。");
}
}

View File

@@ -0,0 +1,38 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.PushWxMpMsgMQ;
import com.jeequan.jeepay.pay.service.payorderpush.impl.PushWxMpTemplateMsgService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 新订单提醒 通知
* @author zx
* @date 2021/01/04 15:25
*/
@Slf4j
@Component
public class PushWxMpMsgMQReceiver implements PushWxMpMsgMQ.IMQReceiver {
@Autowired private PushWxMpTemplateMsgService pushWxMpMessageService;
@Override
public void receive(PushWxMpMsgMQ.MsgPayload payload) {
log.info("接收支付成功微信公众号提醒通知MQ, msg={}", payload.toString());
try {
// 微信公众号消息提醒
pushWxMpMessageService.send(payload.getPayOrderId(),payload.getNoticeType());
log.info("微信公众号提醒业务结束。");
}catch (Exception e) {
log.error("微信公众号提醒异常" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,37 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.ResetAppConfigMQ;
import com.jeequan.jeepay.service.impl.SysConfigService;
import com.jeequan.jeepay.thirdparty.model.WxmpServerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 更新系统配置参数
* @author terrfly
* @date 2021/7/27 9:23
*/
@Slf4j
@Component
public class ResetAppConfigMQReceiver implements ResetAppConfigMQ.IMQReceiver {
@Autowired
private SysConfigService sysConfigService;
@Autowired
private WxmpServerContext wxmpServerContext;
@Override
public void receive(ResetAppConfigMQ.MsgPayload payload) {
log.info("成功接收更新系统配置的订阅通知, msg={}", payload);
sysConfigService.initDBConfig(payload.getGroupKey());
if (SysConfigService.WXMP_CONFIG.getLeft().equals(payload.getGroupKey())) {
wxmpServerContext.clearWxService();
}
log.info("系统配置静态属性已重置");
}
}

View File

@@ -0,0 +1,61 @@
package com.jeequan.jeepay.pay.mq;
import com.jeequan.jeepay.components.mq.model.ResetIsvMchAppInfoConfigMQ;
import com.jeequan.jeepay.core.interfaces.IConfigContextService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接收MQ消息
* 业务: 更新服务商/商户/商户应用配置信息;
*
* @author terrfly
* @date 2021/7/27 9:23
*/
@Slf4j
@Component
public class ResetIsvMchAppInfoMQReceiver implements ResetIsvMchAppInfoConfigMQ.IMQReceiver {
@Autowired(required = false)
private IConfigContextService configContextService;
@Override
public void receive(ResetIsvMchAppInfoConfigMQ.MsgPayload payload) {
if (payload.getResetType() == ResetIsvMchAppInfoConfigMQ.RESET_TYPE_ISV_INFO) {
this.modifyIsvInfo(payload.getIsvNo());
} else if (payload.getResetType() == ResetIsvMchAppInfoConfigMQ.RESET_TYPE_MCH_INFO) {
this.modifyMchInfo(payload.getMchNo());
}
}
/**
* 接收 [商户配置信息] 的消息
*/
private void modifyMchInfo(String mchNo) {
log.info("成功接收 [商户配置信息] 的消息, msg={}", mchNo);
if (configContextService != null) configContextService.initMchInfoConfigContext(mchNo);
log.info(" [商户配置信息] 已重置");
}
/**
* 接收 [商户应用支付参数配置信息] 的消息
*/
private void modifyMchApp(String mchNo, String appId) {
log.info("成功接收 [商户应用支付参数配置信息] 的消息, mchNo={}, appId={}", mchNo, appId);
if (configContextService != null) configContextService.initMchAppConfigContext(mchNo, appId);
log.info(" [商户应用支付参数配置信息] 已重置");
}
/**
* 重置ISV信息
*/
private void modifyIsvInfo(String isvNo) {
log.info("成功接收 [ISV信息] 重置, msg={}", isvNo);
if (configContextService != null) configContextService.initIsvConfigContext(isvNo);
log.info("[ISV信息] 已重置");
}
}

Some files were not shown because too many files have changed in this diff Show More