Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro
Conflicts: yudao-ui-admin/yarn.lock
This commit is contained in:
commit
b761a7b462
|
@ -21,7 +21,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x]
|
||||
node_version: [14.x, 16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
|
59
README.md
59
README.md
|
@ -21,7 +21,7 @@
|
|||
>
|
||||
> 😜 给项目点点 Star 吧,这对我们真的很重要!
|
||||
|
||||

|
||||

|
||||
|
||||
* 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
* 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
|
||||
|
@ -29,18 +29,37 @@
|
|||
* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
|
||||
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
|
||||
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
|
||||
* 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装
|
||||
* 支持 SaaS 多租户,可自定义每个租户的权限,提供透明化的多租户底层封装
|
||||
* 工作流使用 Flowable,支持动态表单、在线设计流程、会签 / 或签、多种任务分配方式
|
||||
* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验
|
||||
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款
|
||||
* 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务
|
||||
* 集成报表设计器、大屏设计器,通过拖拽即可生成酷炫的报表与大屏
|
||||
|
||||
| 项目名 | 说明 | 传送门 |
|
||||
|--------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ruoyi-vue-pro` | Spring Boot 多模块 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)** [Github](https://github.com/YunaiV/ruoyi-vue-pro) |
|
||||
| `yudao-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)** [Github](https://github.com/YunaiV/yudao-cloud) |
|
||||
| `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/yudaocode/SpringBoot-Labs)** [Github](https://github.com/yudaocode/SpringBoot-Labs) |
|
||||
## 🐳 项目关系
|
||||
|
||||

|
||||
|
||||
三个项目的功能对比,可见社区共同整理的 [国产开源项目对比](https://www.yuque.com/xiatian-bsgny/lm0ec1/wqf8mn) 表格。
|
||||
|
||||
### 后端项目
|
||||
|
||||
|
||||
| 项目 | Star | 简介 |
|
||||
|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
|
||||
| [ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [](https://gitee.com/zhijiantianya/ruoyi-vue-pro) [](https://github.com/YunaiV/ruoyi-vue-pro) | 基于 Spring Boot 多模块架构 |
|
||||
| [yudao-cloud](https://gitee.com/zhijiantianya/yudao-cloud) | [](https://gitee.com/zhijiantianya/yudao-cloud) [](https://github.com/YunaiV/yudao-cloud) | 基于 Spring Cloud 微服务架构 |
|
||||
| [Spring-Boot-Labs](https://gitee.com/yudaocode/SpringBoot-Labs) | [](https://gitee.com/zhijiantianya/yudao-cloud) [](https://github.com/yudaocode/SpringBoot-Labs) | 系统学习 Spring Boot & Cloud 专栏 |
|
||||
|
||||
### 前端项目
|
||||
|
||||
| 项目 | Star | 简介 |
|
||||
|----------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|
|
||||
| [yudao-ui-admin-vue3](https://gitee.com/yudaocode/yudao-ui-admin-vue3) | [](https://gitee.com/yudaocode/yudao-ui-admin-vue3) [](https://github.com/yudaocode/yudao-ui-admin-vue3) | 基于 Vue3 + element-plus 实现的管理后台 |
|
||||
| [yudao-ui-admin](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) | [](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) [](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-admin) | 基于 Vue2 + element-ui 实现的管理后台 |
|
||||
| [yudao-ui-admin-uniapp](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) | [](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) [](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) | 基于 uni-app + uni-ui 实现的管理后台的小程序 |
|
||||
| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [](https://gitee.com/yudaocode/yudao-ui-go-view) [](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 |
|
||||
| [yudao-ui-app](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-app) | [](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-app) [](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-app) | 基于 uni-app + uview 实现的用户 App |
|
||||
|
||||
## 🐰 分支说明
|
||||
|
||||
|
@ -207,6 +226,8 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
|
||||
## 🐨 技术栈
|
||||
|
||||
### 模块
|
||||
|
||||
| 项目 | 说明 |
|
||||
|--------------------------------------------------------------------------|--------------------|
|
||||
| `yudao-dependencies` | Maven 依赖版本管理 |
|
||||
|
@ -221,15 +242,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
| `yudao-module-mp` | 微信公众号的 Module 模块 |
|
||||
| `yudao-module-report` | 大屏报表 Module 模块 |
|
||||
|
||||
> 前端项目的地址:
|
||||
>
|
||||
> * [`yudao-ui-admin-vue3`](https://gitee.com/yudaocode/yudao-ui-admin-vue3):基于 Vue3 + element-plus 实现的管理后台
|
||||
> * `yudao-ui-admin`:基于 Vue2 + element-ui 实现的管理后台
|
||||
> * [`yudao-ui-go-view`](https://gitee.com/yudaocode/yudao-ui-go-view):基于 Vue3 + naive-ui 实现的大屏报表
|
||||
> * `yudao-ui-admin-uniapp`:基于 uni-app + uni-ui 实现的管理后台的小程序
|
||||
> * `yudao-ui-app`:基于 uni-app + uview 实现的用户 App
|
||||
|
||||
### 后端
|
||||
### 框架
|
||||
|
||||
| 框架 | 说明 | 版本 | 学习指南 |
|
||||
|---------------------------------------------------------------------------------------------|------------------|-------------|----------------------------------------------------------------|
|
||||
|
@ -255,20 +268,6 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
|
||||
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - |
|
||||
|
||||
### [管理后台 Vue2 前端](./yudao-ui-admin)
|
||||
|
||||
| 框架 | 说明 | 版本 |
|
||||
|------------------------------------------------------------------------------|---------------|--------|
|
||||
| [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.7.14 |
|
||||
| [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案 | - |
|
||||
|
||||
### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
|
||||
|
||||
| 框架 | 说明 | 版本 |
|
||||
|-------------------------------------------------|--------------------|--------|
|
||||
| [uni-app](hhttps://github.com/dcloudio/uni-app) | 跨平台框架 | 2.0.0 |
|
||||
| [uni-ui](https://github.com/dcloudio/uni-ui) | 基于 uni-app 的 UI 框架 | 1.4.20 |
|
||||
|
||||
## 🐷 演示图
|
||||
|
||||
### 系统功能
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -30,7 +30,7 @@
|
|||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>1.7.0-snapshot</revision>
|
||||
<revision>1.7.1-snapshot</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
|
|
|
@ -2493,7 +2493,7 @@ CREATE TABLE "system_login_log" (
|
|||
"id" int8 NOT NULL,
|
||||
"log_type" int8 NOT NULL,
|
||||
"trace_id" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"user_id" int8 NOT NULL,
|
||||
"user_id" int8 NOT NULL DEFAULT 0,
|
||||
"user_type" int2 NOT NULL,
|
||||
"username" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"result" int2 NOT NULL,
|
||||
|
@ -2866,7 +2866,7 @@ CREATE TABLE "system_oauth2_access_token" (
|
|||
"updater" varchar(64) COLLATE "pg_catalog"."default",
|
||||
"update_time" timestamp(6) NOT NULL,
|
||||
"deleted" int2 NOT NULL DEFAULT 0,
|
||||
"tenant_id" int8 NOT NULL,
|
||||
"tenant_id" int8 NOT NULL DEFAULT 0,
|
||||
"scopes" varchar(255) COLLATE "pg_catalog"."default" DEFAULT ''::character varying
|
||||
)
|
||||
;
|
||||
|
@ -2909,7 +2909,7 @@ CREATE TABLE "system_oauth2_approve" (
|
|||
"updater" varchar(64) COLLATE "pg_catalog"."default",
|
||||
"update_time" timestamp(6) NOT NULL,
|
||||
"deleted" int2 NOT NULL DEFAULT 0,
|
||||
"tenant_id" int8 NOT NULL
|
||||
"tenant_id" int8 NOT NULL DEFAULT 0
|
||||
)
|
||||
;
|
||||
COMMENT ON COLUMN "system_oauth2_approve"."id" IS '编号';
|
||||
|
@ -3011,7 +3011,7 @@ CREATE TABLE "system_oauth2_code" (
|
|||
"updater" varchar(64) COLLATE "pg_catalog"."default",
|
||||
"update_time" timestamp(6) NOT NULL,
|
||||
"deleted" int2 NOT NULL DEFAULT 0,
|
||||
"tenant_id" int8 NOT NULL
|
||||
"tenant_id" int8 NOT NULL DEFAULT 0
|
||||
)
|
||||
;
|
||||
COMMENT ON COLUMN "system_oauth2_code"."id" IS '编号';
|
||||
|
@ -3053,7 +3053,7 @@ CREATE TABLE "system_oauth2_refresh_token" (
|
|||
"updater" varchar(64) COLLATE "pg_catalog"."default",
|
||||
"update_time" timestamp(6) NOT NULL,
|
||||
"deleted" int2 NOT NULL DEFAULT 0,
|
||||
"tenant_id" int8 NOT NULL,
|
||||
"tenant_id" int8 NOT NULL DEFAULT 0,
|
||||
"scopes" varchar(255) COLLATE "pg_catalog"."default" DEFAULT ''::character varying
|
||||
)
|
||||
;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>1.7.0-snapshot</revision>
|
||||
<revision>1.7.1-snapshot</revision>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>2.7.8</spring.boot.version>
|
||||
<!-- Web 相关 -->
|
||||
|
|
|
@ -39,5 +39,12 @@
|
|||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package cn.iocoder.yudao.framework.dict.core.util;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
|
||||
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link DictFrameworkUtils} 的单元测试
|
||||
*/
|
||||
public class DictFrameworkUtilsTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Mock
|
||||
private DictDataApi dictDataApi;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
DictFrameworkUtils.init(dictDataApi);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictDataLabel() {
|
||||
// mock 数据
|
||||
DictDataRespDTO dataRespDTO = randomPojo(DictDataRespDTO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// mock 方法
|
||||
when(dictDataApi.getDictData(dataRespDTO.getDictType(), dataRespDTO.getValue())).thenReturn(dataRespDTO);
|
||||
// 断言返回值
|
||||
assertEquals(dataRespDTO.getLabel(), DictFrameworkUtils.getDictDataLabel(dataRespDTO.getDictType(), dataRespDTO.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDictDataValue() {
|
||||
// mock 数据
|
||||
DictDataRespDTO resp = randomPojo(DictDataRespDTO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// mock 方法
|
||||
when(dictDataApi.parseDictData(resp.getDictType(), resp.getLabel())).thenReturn(resp);
|
||||
// 断言返回值
|
||||
assertEquals(resp.getValue(), DictFrameworkUtils.parseDictDataValue(resp.getDictType(), resp.getLabel()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.annotations;
|
||||
|
||||
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
|
@ -23,13 +23,13 @@ public @interface OperateLog {
|
|||
/**
|
||||
* 操作模块
|
||||
*
|
||||
* 为空时,会尝试读取 {@link Api#value()} 属性
|
||||
* 为空时,会尝试读取 {@link Tag#name()} 属性
|
||||
*/
|
||||
String module() default "";
|
||||
/**
|
||||
* 操作名
|
||||
*
|
||||
* 为空时,会尝试读取 {@link ApiOperation#value()} 属性
|
||||
* 为空时,会尝试读取 {@link Operation#summary()} 属性
|
||||
*/
|
||||
String name() default "";
|
||||
/**
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 将 API 的错误码,转换为通用的错误码
|
||||
*
|
||||
* @see PayCommonResult
|
||||
* @see PayFrameworkErrorCodeConstants
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractPayCodeMapping {
|
||||
|
||||
public final ErrorCode apply(String apiCode, String apiMsg) {
|
||||
if (apiCode == null) {
|
||||
log.error("[apply][API 错误码为空,请排查]");
|
||||
return PayFrameworkErrorCodeConstants.EXCEPTION;
|
||||
}
|
||||
ErrorCode errorCode = this.apply0(apiCode, apiMsg);
|
||||
if (errorCode == null) {
|
||||
log.error("[apply][API 错误码({}) 错误提示({}) 无法匹配]", apiCode, apiMsg);
|
||||
return PayFrameworkErrorCodeConstants.PAY_UNKNOWN;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
protected abstract ErrorCode apply0(String apiCode, String apiMsg);
|
||||
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
|
||||
/**
|
||||
* 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能
|
||||
|
@ -23,51 +28,25 @@ public interface PayClient {
|
|||
* @param reqDTO 下单信息
|
||||
* @return 各支付渠道的返回结果
|
||||
*/
|
||||
PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 解析支付单的通知结果
|
||||
*
|
||||
* @param data 通知结果
|
||||
* @return 解析结果
|
||||
* @throws Exception 解析失败,抛出异常
|
||||
*/
|
||||
PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception;
|
||||
PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 调用支付渠道,进行退款
|
||||
* @param reqDTO 统一退款请求信息
|
||||
* @return 各支付渠道的统一返回结果
|
||||
*/
|
||||
PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
||||
PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 解析支付退款通知数据
|
||||
* @param notifyData 支付退款通知请求数据
|
||||
* @return 支付退款通知的Notify DTO
|
||||
*/
|
||||
PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData);
|
||||
|
||||
// TODO @芋艿:后续改成非 default,避免不知道去实现
|
||||
/**
|
||||
* 验证是否渠道通知
|
||||
* 解析回调数据
|
||||
*
|
||||
* @param notifyData 通知数据
|
||||
* @return 默认是 true
|
||||
* @param rawNotify 通知内容
|
||||
* @return 回调对象
|
||||
* 1. {@link PayRefundNotifyRespDTO} 退款通知
|
||||
* 2. {@link PayOrderNotifyRespDTO} 支付通知
|
||||
*/
|
||||
default boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO @芋艿:后续改成非 default,避免不知道去实现
|
||||
/**
|
||||
* 判断是否为退款通知
|
||||
*
|
||||
* @param notifyData 通知数据
|
||||
* @return 默认是 false
|
||||
*/
|
||||
default boolean isRefundNotify(PayNotifyDataDTO notifyData){
|
||||
return false;
|
||||
default Object parseNotify(PayNotifyReqDTO rawNotify) {
|
||||
throw new UnsupportedOperationException("未实现 parseNotify 方法!");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 支付的 CommonResult 拓展类
|
||||
*
|
||||
* 考虑到不同的平台,返回的 code 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class PayCommonResult<T> extends CommonResult<T> {
|
||||
|
||||
/**
|
||||
* API 返回错误码
|
||||
*
|
||||
* 由于第三方的错误码可能是字符串,所以使用 String 类型
|
||||
*/
|
||||
private String apiCode;
|
||||
/**
|
||||
* API 返回提示
|
||||
*/
|
||||
private String apiMsg;
|
||||
|
||||
private PayCommonResult() {
|
||||
}
|
||||
|
||||
public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, AbstractPayCodeMapping codeMapping) {
|
||||
Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
|
||||
PayCommonResult<T> result = new PayCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg);
|
||||
result.setData(data);
|
||||
// 翻译错误码
|
||||
if (codeMapping != null) {
|
||||
ErrorCode errorCode = codeMapping.apply(apiCode, apiMsg);
|
||||
result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> PayCommonResult<T> error(Throwable ex) {
|
||||
PayCommonResult<T> result = new PayCommonResult<>();
|
||||
result.setCode(PayFrameworkErrorCodeConstants.EXCEPTION.getCode());
|
||||
result.setMsg(ExceptionUtil.getRootCauseMessage(ex));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -13,7 +13,7 @@ import java.util.Map;
|
|||
@Data
|
||||
@ToString
|
||||
@Builder
|
||||
public class PayNotifyDataDTO {
|
||||
public class PayNotifyReqDTO {
|
||||
|
||||
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
|
@ -35,15 +35,9 @@ public class PayOrderNotifyRespDTO {
|
|||
*/
|
||||
private LocalDateTime successTime;
|
||||
|
||||
/**
|
||||
* 通知的原始数据
|
||||
*
|
||||
* 主要用于持久化,方便后续修复数据,或者排错
|
||||
*/
|
||||
private String data;
|
||||
|
||||
/**
|
||||
* TODO @jason 结合其他的渠道定义成枚举,
|
||||
*
|
||||
* alipay
|
||||
* TRADE_CLOSED,未付款交易超时关闭,或支付完成后全额退款。
|
||||
* TRADE_SUCCESS, 交易支付成功
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
import lombok.Builder;
|
||||
|
@ -15,14 +15,13 @@ import java.time.LocalDateTime;
|
|||
@Data
|
||||
@ToString
|
||||
@Builder
|
||||
public class PayRefundNotifyDTO {
|
||||
public class PayRefundNotifyRespDTO {
|
||||
|
||||
/**
|
||||
* 支付渠道编号
|
||||
*/
|
||||
private String channelOrderNo;
|
||||
|
||||
|
||||
/**
|
||||
* 交易订单号,根据规则生成
|
||||
* 调用支付渠道时,使用该字段作为对接的订单号。
|
||||
|
@ -46,18 +45,14 @@ public class PayRefundNotifyDTO {
|
|||
*/
|
||||
private String reqNo;
|
||||
|
||||
|
||||
/**
|
||||
* 退款是否成功
|
||||
*/
|
||||
private PayNotifyRefundStatusEnum status;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
private LocalDateTime refundSuccessTime;
|
||||
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
@ -7,6 +8,7 @@ import org.hibernate.validator.constraints.URL;
|
|||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.awt.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -78,4 +80,13 @@ public class PayOrderUnifiedReqDTO {
|
|||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
/**
|
||||
* 展示模式
|
||||
*
|
||||
* 如果不传递,则每个支付渠道使用默认的方式
|
||||
*
|
||||
* 枚举 {@link PayDisplayModeEnum}
|
||||
*/
|
||||
private String displayMode;
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 统一下单 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderUnifiedRespDTO {
|
||||
|
||||
/**
|
||||
* 展示模式
|
||||
*/
|
||||
private String displayMode;
|
||||
/**
|
||||
* 展示内容
|
||||
*/
|
||||
private String displayContent;
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
|
||||
import lombok.AllArgsConstructor;
|
|
@ -1,17 +1,23 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import com.alipay.api.AlipayResponse;import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.validation.Validation;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.PAY_EXCEPTION;
|
||||
|
||||
/**
|
||||
* 支付客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||
|
@ -29,19 +35,14 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||
* 渠道编码
|
||||
*/
|
||||
private final String channelCode;
|
||||
/**
|
||||
* 错误码枚举类
|
||||
*/
|
||||
protected AbstractPayCodeMapping codeMapping;
|
||||
/**
|
||||
* 支付配置
|
||||
*/
|
||||
protected Config config;
|
||||
|
||||
public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
|
||||
public AbstractPayClient(Long channelId, String channelCode, Config config) {
|
||||
this.channelId = channelId;
|
||||
this.channelCode = channelCode;
|
||||
this.codeMapping = codeMapping;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
@ -69,47 +70,72 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||
this.init();
|
||||
}
|
||||
|
||||
protected Double calculateAmount(Integer amount) {
|
||||
return amount / 100.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
|
||||
// 执行短信发送
|
||||
PayCommonResult<?> result;
|
||||
PayOrderUnifiedRespDTO result;
|
||||
try {
|
||||
result = doUnifiedOrder(reqDTO);
|
||||
} catch (Throwable ex) {
|
||||
// 打印异常日志
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex);
|
||||
// 封装返回
|
||||
return PayCommonResult.error(ex);
|
||||
throw buildException(ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
|
||||
protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
|
||||
throws Throwable;
|
||||
|
||||
@Override
|
||||
public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
PayCommonResult<PayRefundUnifiedRespDTO> resp;
|
||||
public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
PayRefundUnifiedRespDTO resp;
|
||||
try {
|
||||
resp = doUnifiedRefund(reqDTO);
|
||||
} catch (Throwable ex) {
|
||||
// 记录异常日志
|
||||
log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
|
||||
resp = PayCommonResult.error(ex);
|
||||
throw buildException(ex);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
||||
protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
||||
|
||||
// ========== 各种工具方法 ==========
|
||||
|
||||
private RuntimeException buildException(Throwable ex) {
|
||||
if (ex instanceof RuntimeException) {
|
||||
return (RuntimeException) ex;
|
||||
}
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
protected void validateSuccess(AlipayResponse response) {
|
||||
if (response.isSuccess()) {
|
||||
return;
|
||||
}
|
||||
throw exception0(PAY_EXCEPTION.getCode(), response.getSubMsg());
|
||||
}
|
||||
|
||||
protected String formatAmount(Integer amount) {
|
||||
return String.valueOf(amount / 100.0);
|
||||
}
|
||||
|
||||
protected String formatTime(LocalDateTime time) {
|
||||
// "yyyy-MM-dd HH:mm:ss"
|
||||
return LocalDateTimeUtil.format(time, NORM_DATETIME_FORMATTER);
|
||||
}
|
||||
|
||||
protected LocalDateTime parseTime(String str) {
|
||||
// "yyyy-MM-dd HH:mm:ss"
|
||||
return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,7 @@ import cn.hutool.core.lang.Assert;
|
|||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPcPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXLitePayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXNativePayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
|
||||
|
@ -69,8 +66,9 @@ public class PayClientFactoryImpl implements PayClientFactory {
|
|||
case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
|
||||
case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
}
|
||||
// 创建失败,错误日志 + 抛出异常
|
||||
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
|
@ -18,7 +20,6 @@ import lombok.SneakyThrows;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
@ -33,9 +34,8 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
|
||||
protected DefaultAlipayClient client;
|
||||
|
||||
public AbstractAlipayClient(Long channelId, String channelCode,
|
||||
AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) {
|
||||
super(channelId, channelCode, config, codeMapping);
|
||||
public AbstractAlipayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
|
||||
super(channelId, channelCode, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,71 +46,25 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
this.client = new DefaultAlipayClient(alipayConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考
|
||||
* //https://opendocs.alipay.com/open/203/105286
|
||||
* @param data 通知结果
|
||||
* @return 解析结果 PayOrderNotifyRespDTO
|
||||
* @throws Exception 解析失败,抛出异常
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
|
||||
Map<String, String> params = strToMap(data.getBody());
|
||||
|
||||
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
|
||||
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
|
||||
.tradeStatus(params.get("trade_status"))
|
||||
.successTime(LocalDateTimeUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.data(data.getBody()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
Map<String, String> params = strToMap(notifyData.getBody());
|
||||
PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
|
||||
.tradeNo(params.get("out_trade_no"))
|
||||
.reqNo(params.get("out_biz_no"))
|
||||
.status(PayNotifyRefundStatusEnum.SUCCESS)
|
||||
.refundSuccessTime(LocalDateTimeUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.build();
|
||||
return notifyDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
if (notifyData.getParams().containsKey("refund_fee")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
|
||||
boolean verifyResult = false;
|
||||
try {
|
||||
verifyResult = AlipaySignature.rsaCheckV1(notifyData.getParams(), config.getAlipayPublicKey(), StandardCharsets.UTF_8.name(), "RSA2");
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[AlipayClient verifyNotifyData][(notify param is :{}) 验证失败]", toJsonString(notifyData.getParams()), e);
|
||||
}
|
||||
return verifyResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝统一的退款接口 alipay.trade.refund
|
||||
* @param reqDTO 退款请求 request DTO
|
||||
* @return 退款请求 Response
|
||||
*/
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
AlipayTradeRefundModel model=new AlipayTradeRefundModel();
|
||||
model.setTradeNo(reqDTO.getChannelOrderNo());
|
||||
model.setOutTradeNo(reqDTO.getPayTradeNo());
|
||||
|
||||
model.setOutRequestNo(reqDTO.getMerchantRefundId());
|
||||
model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
|
||||
model.setRefundAmount(formatAmount(reqDTO.getAmount()).toString());
|
||||
model.setRefundReason(reqDTO.getReason());
|
||||
|
||||
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
|
||||
refundRequest.setBizModel(model);
|
||||
refundRequest.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
refundRequest.setReturnUrl(reqDTO.getNotifyUrl());
|
||||
try {
|
||||
AlipayTradeRefundResponse response = client.execute(refundRequest);
|
||||
log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
|
||||
|
@ -119,38 +73,43 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
//支付宝不返回退款单号,设置为空
|
||||
PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
|
||||
respDTO.setChannelRefundId("");
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping);
|
||||
// return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping); TODO
|
||||
return null;
|
||||
}
|
||||
// 失败。需要抛出异常
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping);
|
||||
// return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping); TODO
|
||||
return null;
|
||||
} catch (AlipayApiException e) {
|
||||
// TODO 记录异常日志
|
||||
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
// return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Object parseNotify(PayNotifyReqDTO rawNotify) {
|
||||
// 1. 校验回调数据
|
||||
String body = rawNotify.getBody();
|
||||
Map<String, String> params = rawNotify.getParams();
|
||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
|
||||
StandardCharsets.UTF_8.name(), "RSA2");
|
||||
|
||||
|
||||
/**
|
||||
* 支付宝统一回调参数 str 转 map
|
||||
*
|
||||
* @param s 支付宝支付通知回调参数
|
||||
* @return map 支付宝集合
|
||||
*/
|
||||
public static Map<String, String> strToMap(String s) {
|
||||
// TODO @zxy:这个可以使用 hutool 的 HttpUtil decodeParams 方法么?
|
||||
Map<String, String> stringStringMap = new HashMap<>();
|
||||
// 调整时间格式
|
||||
String s3 = s.replaceAll("%3A", ":");
|
||||
// 获取 map
|
||||
String s4 = s3.replace("+", " ");
|
||||
String[] split = s4.split("&");
|
||||
for (String s1 : split) {
|
||||
String[] split1 = s1.split("=");
|
||||
stringStringMap.put(split1[0], split1[1]);
|
||||
// 2.1 退款的情况
|
||||
if (bodyObj.containsKey("refund_fee")) {
|
||||
return PayRefundNotifyRespDTO.builder().channelOrderNo(bodyObj.get("trade_no"))
|
||||
.tradeNo(bodyObj.get("out_trade_no")).reqNo(bodyObj.get("out_biz_no"))
|
||||
.status(PayNotifyRefundStatusEnum.SUCCESS)
|
||||
.refundSuccessTime(parseTime(params.get("gmt_refund")))
|
||||
.build();
|
||||
}
|
||||
return stringStringMap;
|
||||
// 2.2 支付的情况
|
||||
return PayOrderNotifyRespDTO.builder().orderExtensionNo(bodyObj.get("out_trade_no"))
|
||||
.channelOrderNo(bodyObj.get("trade_no")).channelUserId(bodyObj.get("seller_id"))
|
||||
.tradeStatus(bodyObj.get("trade_status")).successTime(parseTime(params.get("notify_time")))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.domain.AlipayTradeAppPayModel;
|
||||
import com.alipay.api.request.AlipayTradeAppPayRequest;
|
||||
import com.alipay.api.response.AlipayTradeAppPayResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 支付宝【App 支付】的 PayClient 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/open/02e7gq">App 支付</a>
|
||||
*
|
||||
* // TODO 芋艿:未详细测试,因为手头没 App
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlipayAppPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradeAppPayModel 请求
|
||||
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setProductCode(" QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品
|
||||
// ② 个性化的参数【无】
|
||||
// ③ 支付宝扫码支付只有一种展示
|
||||
String displayMode = PayDisplayModeEnum.APP.getMode();
|
||||
|
||||
// 1.2 构建 AlipayTradePrecreateRequest 请求
|
||||
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
|
||||
request.setBizModel(model);
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
|
||||
// 2.1 执行请求
|
||||
AlipayTradeAppPayResponse response = client.execute(request);
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent("");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.domain.AlipayTradePayModel;
|
||||
import com.alipay.api.request.AlipayTradePayRequest;
|
||||
import com.alipay.api.response.AlipayTradePayResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||
|
||||
/**
|
||||
* 支付宝【条码支付】的 PayClient 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/open/194/105072">当面付</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlipayBarPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "auth_code");
|
||||
if (StrUtil.isEmpty(authCode)) {
|
||||
throw exception0(BAD_REQUEST.getCode(), "条形码不能为空");
|
||||
}
|
||||
|
||||
// 1.1 构建 AlipayTradePayModel 请求
|
||||
AlipayTradePayModel model = new AlipayTradePayModel();
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setScene("bar_code"); // 当面付条码支付场景
|
||||
// ② 个性化的参数
|
||||
model.setAuthCode(authCode);
|
||||
// ③ 支付宝条码支付只有一种展示
|
||||
String displayMode = PayDisplayModeEnum.BAR_CODE.getMode();
|
||||
|
||||
// 1.2 构建 AlipayTradePayRequest 请求
|
||||
AlipayTradePayRequest request = new AlipayTradePayRequest();
|
||||
request.setBizModel(model);
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
|
||||
// 2.1 执行请求
|
||||
AlipayTradePayResponse response = client.execute(request);
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent("");
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 支付宝的 PayCodeMapping 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AlipayPayCodeMapping extends AbstractPayCodeMapping {
|
||||
|
||||
@Override
|
||||
protected ErrorCode apply0(String apiCode, String apiMsg) {
|
||||
if (Objects.equals(apiCode, "10000")) {
|
||||
return GlobalErrorCodeConstants.SUCCESS;
|
||||
}
|
||||
// alipay wap api code 返回为null, 暂时定为-9999
|
||||
if (Objects.equals(apiCode, "-9999")) {
|
||||
return GlobalErrorCodeConstants.SUCCESS;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +1,24 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.domain.AlipayTradePagePayModel;
|
||||
import com.alipay.api.request.AlipayTradePagePayRequest;
|
||||
import com.alipay.api.response.AlipayTradePagePayResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 支付宝【PC网站支付】的 PayClient 实现类
|
||||
* 文档:https://opendocs.alipay.com/open/270/105898
|
||||
* 支付宝【PC 网站】的 PayClient 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/open/270/105898">电脑网站支付</a>
|
||||
*
|
||||
* @author XGD
|
||||
*/
|
||||
|
@ -23,38 +26,66 @@ import lombok.extern.slf4j.Slf4j;
|
|||
public class AlipayPcPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config, new AlipayPayCodeMapping());
|
||||
super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradePagePayModel 请求
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradePagePayModel 请求
|
||||
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
|
||||
// 构建 AlipayTradePagePayRequest
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setTimeExpire(formatTime(reqDTO.getExpireTime()));
|
||||
model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY
|
||||
// ② 个性化的参数
|
||||
// 参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分
|
||||
model.setQrPayMode(MapUtil.getStr(reqDTO.getChannelExtras(), "qr_pay_mode"));
|
||||
model.setQrcodeWidth(MapUtil.getLong(reqDTO.getChannelExtras(), "qr_code_width"));
|
||||
// ③ 支付宝 PC 支付有多种展示模式,因此这里需要计算
|
||||
String displayMode = getDisplayMode(reqDTO.getDisplayMode(), model.getQrPayMode());
|
||||
|
||||
// 1.2 构建 AlipayTradePagePayRequest 请求
|
||||
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
|
||||
request.setBizModel(model);
|
||||
JSONObject bizContent = new JSONObject();
|
||||
// 参数说明可查看: https://opendocs.alipay.com/open/028r8t?scene=22
|
||||
bizContent.put("out_trade_no", reqDTO.getMerchantOrderId());
|
||||
bizContent.put("total_amount", calculateAmount(reqDTO.getAmount()));
|
||||
bizContent.put("subject", reqDTO.getSubject());
|
||||
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
|
||||
// PC扫码支付的方式:支持前置模式和跳转模式。4: 订单码-可定义宽度的嵌入式二维码
|
||||
bizContent.put("qr_pay_mode", "4");
|
||||
// 自定义二维码宽度
|
||||
bizContent.put("qrcode_width", "150");
|
||||
request.setBizContent(bizContent.toJSONString());
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl("");
|
||||
// 执行请求
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
|
||||
// 2.1 执行请求
|
||||
AlipayTradePagePayResponse response;
|
||||
try {
|
||||
response = client.pageExecute(request);
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
if (Objects.equals(displayMode, PayDisplayModeEnum.FORM.getMode())) {
|
||||
response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求
|
||||
} else {
|
||||
response = client.pageExecute(request, Method.GET.name());
|
||||
}
|
||||
// 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
|
||||
return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000") ,response.getMsg(), response, codeMapping);
|
||||
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO().setDisplayMode(displayMode)
|
||||
.setDisplayContent(response.getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得最终的支付 UI 展示模式
|
||||
*
|
||||
* @param displayMode 前端传递的 UI 展示模式
|
||||
* @param qrPayMode 前端传递的二维码模式
|
||||
* @return 最终的支付 UI 展示模式
|
||||
*/
|
||||
private String getDisplayMode(String displayMode, String qrPayMode) {
|
||||
// 1.1 支付宝二维码的前置模式
|
||||
if (StrUtil.equalsAny(qrPayMode, "0", "1", "3", "4")) {
|
||||
return PayDisplayModeEnum.IFRAME.getMode();
|
||||
}
|
||||
// 1.2 支付宝二维码的跳转模式
|
||||
if (StrUtil.equals(qrPayMode, "2")) {
|
||||
return PayDisplayModeEnum.URL.getMode();
|
||||
}
|
||||
// 2. 前端传递了 UI 展示模式
|
||||
return displayMode != null ? displayMode :
|
||||
PayDisplayModeEnum.URL.getMode(); // 模式使用 URL 跳转
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.domain.AlipayTradePrecreateModel;
|
||||
import com.alipay.api.request.AlipayTradePrecreateRequest;
|
||||
import com.alipay.api.response.AlipayTradePrecreateResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
* 支付宝【扫码支付】的 PayClient 实现类
|
||||
* 文档:https://opendocs.alipay.com/apis/02890k
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/apis/02890k">扫码支付</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -21,32 +21,34 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
|
|||
public class AlipayQrPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping());
|
||||
super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradePrecreateModel 请求
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradePrecreateModel 请求
|
||||
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位:元
|
||||
// TODO 芋艿:userIp + expireTime
|
||||
// 构建 AlipayTradePrecreateRequest
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT
|
||||
// ② 个性化的参数【无】
|
||||
// ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
|
||||
String displayMode = PayDisplayModeEnum.QR_CODE.getMode();
|
||||
|
||||
// 1.2 构建 AlipayTradePrecreateRequest 请求
|
||||
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
|
||||
request.setBizModel(model);
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
// 执行请求
|
||||
AlipayTradePrecreateResponse response;
|
||||
try {
|
||||
response = client.execute(request);
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
}
|
||||
// TODO 芋艿:sub Code 需要测试下各种失败的情况
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
|
||||
|
||||
// 2.1 执行请求
|
||||
AlipayTradePrecreateResponse response = client.execute(request);
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent(response.getQrCode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +1,60 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.domain.AlipayTradeWapPayModel;
|
||||
import com.alipay.api.request.AlipayTradeWapPayRequest;
|
||||
import com.alipay.api.response.AlipayTradeWapPayResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 支付宝【手机网站】的 PayClient 实现类
|
||||
* 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
|
||||
* 支付宝【Wap 网站】的 PayClient 实现类
|
||||
*
|
||||
* 文档:<a href="https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay">手机网站支付接口</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlipayWapPayClient extends AbstractAlipayClient {
|
||||
|
||||
|
||||
public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping());
|
||||
super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradeWapPayModel 请求
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
|
||||
// 1.1 构建 AlipayTradeWapPayModel 请求
|
||||
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
||||
// ① 通用的参数
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString());
|
||||
model.setProductCode("QUICK_WAP_PAY"); // TODO 芋艿:这里咋整
|
||||
//TODO 芋艿:这里咋整 jason @芋艿 可以去掉吧,
|
||||
// TODO 芋艿 似乎这里不用传sellerId
|
||||
// https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
|
||||
//model.setSellerId("2088102147948060");
|
||||
model.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(),"yyyy-MM-dd HH:mm:ss"));
|
||||
// TODO 芋艿:userIp
|
||||
// 构建 AlipayTradeWapPayRequest
|
||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
||||
model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY
|
||||
// ② 个性化的参数【无】
|
||||
// ③ 支付宝 Wap 支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
|
||||
String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(),
|
||||
PayDisplayModeEnum.URL.getMode());
|
||||
|
||||
// 1.2 构建 AlipayTradeWapPayRequest 请求
|
||||
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
|
||||
request.setBizModel(model);
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
request.setReturnUrl(reqDTO.getReturnUrl());
|
||||
model.setQuitUrl(reqDTO.getReturnUrl());
|
||||
|
||||
// 执行请求
|
||||
AlipayTradeWapPayResponse response;
|
||||
try {
|
||||
response = client.pageExecute(request);
|
||||
} catch (AlipayApiException e) {
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
// 2.1 执行请求
|
||||
AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name());
|
||||
|
||||
// 2.2 处理结果
|
||||
validateSuccess(response);
|
||||
return new PayOrderUnifiedRespDTO()
|
||||
.setDisplayMode(displayMode).setDisplayContent(response.getBody());
|
||||
}
|
||||
|
||||
// TODO 芋艿:sub Code
|
||||
if(response.isSuccess() && Objects.isNull(response.getCode()) && Objects.nonNull(response.getBody())){
|
||||
//成功alipay wap 成功 code 为 null , body 为form 表单
|
||||
return PayCommonResult.build("-9999", "Success", response, codeMapping);
|
||||
}else {
|
||||
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 微信支付 PayCodeMapping 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class WXCodeMapping extends AbstractPayCodeMapping {
|
||||
|
||||
/**
|
||||
* 错误码 - 成功
|
||||
* 由于 weixin-java-pay 封装的 Result 未返回 code,所以自己定义下
|
||||
*/
|
||||
public static final String CODE_SUCCESS = "SUCCESS";
|
||||
/**
|
||||
* 错误提示 - 成功
|
||||
*/
|
||||
public static final String MESSAGE_SUCCESS = "成功";
|
||||
|
||||
@Override
|
||||
protected ErrorCode apply0(String apiCode, String apiMsg) {
|
||||
if (Objects.equals(apiCode, CODE_SUCCESS)) {
|
||||
return GlobalErrorCodeConstants.SUCCESS;
|
||||
}
|
||||
if (Objects.equals(apiCode, "FAIL")) {
|
||||
if (Objects.equals(apiMsg, "AppID不存在,请检查后再试")) {
|
||||
return PAY_CONFIG_APP_ID_ERROR;
|
||||
}
|
||||
if (Objects.equals(apiMsg, "签名错误,请检查后再试")
|
||||
|| Objects.equals(apiMsg, "签名错误")) {
|
||||
return PAY_CONFIG_SIGN_ERROR;
|
||||
}
|
||||
}
|
||||
if (Objects.equals(apiCode, "PARAM_ERROR")) {
|
||||
if (Objects.equals(apiMsg, "无效的openid")) {
|
||||
return PAY_OPENID_ERROR;
|
||||
}
|
||||
}
|
||||
if (Objects.equals(apiCode, "CustomErrorCode")) {
|
||||
if (StrUtil.contains(apiMsg, "必填字段")) {
|
||||
return PAY_PARAM_MISSING;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,9 +7,13 @@ import cn.hutool.core.lang.Assert;
|
|||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
|
@ -30,11 +34,6 @@ import java.time.ZoneId;
|
|||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
|
||||
/**
|
||||
* 微信小程序下支付
|
||||
*
|
||||
|
@ -46,7 +45,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
private WxPayService client;
|
||||
|
||||
public WXLitePayClient(Long channelId, WXPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_LITE.getCode(), config, new WXCodeMapping());
|
||||
super(channelId, PayChannelEnum.WX_LITE.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,28 +70,29 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
WxPayMpOrderResult response;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
response = this.unifiedOrderV2(reqDTO);
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
response = new WxPayMpOrderResult();
|
||||
BeanUtil.copyProperties(responseV3, response, true);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException();
|
||||
// WxPayMpOrderResult response;
|
||||
// try {
|
||||
// switch (config.getApiVersion()) {
|
||||
// case WXPayClientConfig.API_VERSION_V2:
|
||||
// response = this.unifiedOrderV2(reqDTO);
|
||||
// break;
|
||||
// case WXPayClientConfig.API_VERSION_V3:
|
||||
// WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
// response = new WxPayMpOrderResult();
|
||||
// BeanUtil.copyProperties(responseV3, response, true);
|
||||
// break;
|
||||
// default:
|
||||
// throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
// }
|
||||
// } catch (WxPayException e) {
|
||||
// log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
// return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
// ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
// }
|
||||
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
}
|
||||
|
||||
private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
|
@ -145,8 +145,8 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
* @return 支付回调对象
|
||||
* @throws WxPayException 微信异常类
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
|
||||
// @Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
|
||||
log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody());
|
||||
// 微信支付 v2 回调结果处理
|
||||
switch (config.getApiVersion()) {
|
||||
|
@ -159,7 +159,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
|
||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
|
||||
// 转换结果
|
||||
|
@ -172,11 +172,10 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(result.getTransactionId())
|
||||
.channelUserId(result.getPayer().getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||
// 转换结果
|
||||
|
@ -186,20 +185,12 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(notifyResult.getTransactionId())
|
||||
.channelUserId(notifyResult.getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException("需要实现");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -6,9 +6,13 @@ import cn.hutool.core.date.LocalDateTimeUtil;
|
|||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
|
@ -28,10 +32,6 @@ import java.time.ZoneId;
|
|||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
/**
|
||||
* 微信 App 支付
|
||||
*
|
||||
|
@ -43,7 +43,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
private WxPayService client;
|
||||
|
||||
public WXNativePayClient(Long channelId, WXPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config, new WXCodeMapping());
|
||||
super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,27 +68,28 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 这里原生的返回的是支付的 url 所以直接使用string接收
|
||||
// "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
|
||||
String responseV3;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException();
|
||||
// // 这里原生的返回的是支付的 url 所以直接使用string接收
|
||||
// // "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
|
||||
// String responseV3;
|
||||
// try {
|
||||
// switch (config.getApiVersion()) {
|
||||
// case WXPayClientConfig.API_VERSION_V2:
|
||||
// responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
|
||||
// break;
|
||||
// case WXPayClientConfig.API_VERSION_V3:
|
||||
// responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// break;
|
||||
// default:
|
||||
// throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
// }
|
||||
// } catch (WxPayException e) {
|
||||
// log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
// return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
// ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
// }
|
||||
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
|
||||
}
|
||||
|
||||
private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
|
@ -129,8 +130,8 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
* @return 支付回调对象
|
||||
* @throws WxPayException 微信异常类
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
|
||||
// @Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
|
||||
log.info("微信支付回调data数据:{}", data.getBody());
|
||||
// 微信支付 v2 回调结果处理
|
||||
switch (config.getApiVersion()) {
|
||||
|
@ -143,7 +144,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
|
||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
|
||||
// 转换结果
|
||||
|
@ -154,11 +155,10 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.orderExtensionNo(result.getOutTradeNo())
|
||||
.channelOrderNo(result.getTradeState())
|
||||
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||
// 转换结果
|
||||
|
@ -168,20 +168,12 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(notifyResult.getTransactionId())
|
||||
.channelUserId(notifyResult.getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException("需要实现");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -7,9 +7,12 @@ import cn.hutool.core.lang.Assert;
|
|||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
|
@ -30,10 +33,6 @@ import java.time.LocalDateTime;
|
|||
import java.time.ZoneId;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
/**
|
||||
* 微信支付(公众号)的 PayClient 实现类
|
||||
*
|
||||
|
@ -45,7 +44,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
private WxPayService client;
|
||||
|
||||
public WXPubPayClient(Long channelId, WXPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_PUB.getCode(), config, new WXCodeMapping());
|
||||
super(channelId, PayChannelEnum.WX_PUB.getCode(), config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -70,28 +69,30 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
WxPayMpOrderResult response;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
response = this.unifiedOrderV2(reqDTO);
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
response = new WxPayMpOrderResult();
|
||||
BeanUtil.copyProperties(responseV3, response, true);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
throw new UnsupportedOperationException();
|
||||
//
|
||||
// WxPayMpOrderResult response;
|
||||
// try {
|
||||
// switch (config.getApiVersion()) {
|
||||
// case WXPayClientConfig.API_VERSION_V2:
|
||||
// response = this.unifiedOrderV2(reqDTO);
|
||||
// break;
|
||||
// case WXPayClientConfig.API_VERSION_V3:
|
||||
// WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
// response = new WxPayMpOrderResult();
|
||||
// BeanUtil.copyProperties(responseV3, response, true);
|
||||
// break;
|
||||
// default:
|
||||
// throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
// }
|
||||
// } catch (WxPayException e) {
|
||||
// log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
// return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
// ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
|
||||
// }
|
||||
// return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
}
|
||||
|
||||
|
||||
|
@ -140,8 +141,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
* @return 支付回调对象
|
||||
* @throws WxPayException 微信异常类
|
||||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
|
||||
// @Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
|
||||
log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody());
|
||||
// 微信支付 v2 回调结果处理
|
||||
switch (config.getApiVersion()) {
|
||||
|
@ -154,7 +155,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
|
||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
|
||||
// 转换结果
|
||||
|
@ -165,11 +166,10 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.orderExtensionNo(result.getOutTradeNo())
|
||||
.channelOrderNo(result.getTradeState())
|
||||
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
}
|
||||
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
|
||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||
// 转换结果
|
||||
|
@ -179,19 +179,12 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.channelOrderNo(notifyResult.getTransactionId())
|
||||
.channelUserId(notifyResult.getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException("需要实现");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
// TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ public enum PayChannelEnum {
|
|||
ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class);
|
||||
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class);
|
||||
|
||||
/**
|
||||
* 编码
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付 UI 展示模式
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayDisplayModeEnum {
|
||||
|
||||
URL("url"), // Redirect 跳转链接的方式
|
||||
IFRAME("iframe"), // IFrame 内嵌链接的方式
|
||||
FORM("form"), // HTML 表单提交
|
||||
QR_CODE("qr_code"), // 二维码的文字内容
|
||||
QR_CODE_URL("qr_code_url"), // 二维码的图片链接
|
||||
BAR_CODE("bar_code"), // 条形码
|
||||
APP("app"), // 应用
|
||||
;
|
||||
|
||||
/**
|
||||
* 展示模式
|
||||
*/
|
||||
private final String mode;
|
||||
|
||||
}
|
|
@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
|||
/**
|
||||
* 支付框架的错误码枚举
|
||||
*
|
||||
* 短信框架,使用 2-002-000-000 段
|
||||
* 支付框架,使用 2-002-000-000 段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -14,14 +14,16 @@ public interface PayFrameworkErrorCodeConstants {
|
|||
ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析");
|
||||
|
||||
// ========== 配置相关相关 2002000100 ==========
|
||||
// todo 芋艿:如下的错误码,怎么处理掉
|
||||
ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确");
|
||||
ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说,微信支付,配置错了 mchId 或者 mchKey
|
||||
|
||||
|
||||
// ========== 其它相关 2002000900 开头 ==========
|
||||
// todo 芋艿:如下的错误码,怎么处理掉
|
||||
ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说,微信 openid 未授权过
|
||||
ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说,支付少传了金额
|
||||
|
||||
ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");
|
||||
ErrorCode PAY_EXCEPTION = new ErrorCode(2002000999, "调用异常");
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package cn.iocoder.yudao.framework.pay.core.enums;
|
|||
* @author jason
|
||||
*/
|
||||
public enum PayNotifyRefundStatusEnum {
|
||||
|
||||
/**
|
||||
* 支付宝 中 全额退款 trade_status=TRADE_CLOSED, 部分退款 trade_status=TRADE_SUCCESS
|
||||
* 退款成功
|
||||
|
@ -17,4 +18,5 @@ public enum PayNotifyRefundStatusEnum {
|
|||
* 退款异常
|
||||
*/
|
||||
ABNORMAL;
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import cn.hutool.core.util.RandomUtil;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
|
||||
|
@ -46,8 +46,8 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
System.out.println(result);
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
// System.out.println(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,8 +69,8 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
System.out.println(result);
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
// System.out.println(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,9 +93,9 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址
|
||||
CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
|
||||
System.out.println(JsonUtils.toJsonString(result));
|
||||
System.out.println(result.getData().getQrCode());
|
||||
// CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
|
||||
// System.out.println(JsonUtils.toJsonString(result));
|
||||
// System.out.println(result.getData().getQrCode());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,8 +116,8 @@ public class PayClientFactoryImplIntegrationTest {
|
|||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
System.out.println(JsonUtils.toJsonString(result));
|
||||
// CommonResult<?> result = client.unifiedOrder(reqDTO);
|
||||
// System.out.println(JsonUtils.toJsonString(result));
|
||||
}
|
||||
|
||||
private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
|
@ -87,13 +87,13 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
|
|||
}))).thenReturn(response);
|
||||
|
||||
|
||||
PayCommonResult<AlipayTradePrecreateResponse> result = client.doUnifiedOrder(reqDTO);
|
||||
// 断言
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMsg(), result.getApiMsg());
|
||||
// TODO @tina:这个断言木有过?
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
// PayCommonResult<PayOrderUnifiedRespDTO> result = client.doUnifiedOrder(reqDTO);
|
||||
// // 断言
|
||||
// assertEquals(response.getCode(), result.getApiCode());
|
||||
// assertEquals(response.getMsg(), result.getApiMsg());
|
||||
// // TODO @tina:这个断言木有过?
|
||||
// assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
// assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class YudaoMQAutoConfiguration {
|
|||
/**
|
||||
* 创建 Redis Pub/Sub 广播消费的容器
|
||||
*/
|
||||
@Bean
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
public RedisMessageListenerContainer redisMessageListenerContainer(
|
||||
RedisMQTemplate redisMQTemplate, List<AbstractChannelMessageListener<?>> listeners) {
|
||||
// 创建 RedisMessageListenerContainer 对象
|
||||
|
|
|
@ -52,8 +52,7 @@ public class YudaoSwaggerAutoConfiguration {
|
|||
// 接口信息
|
||||
.info(buildInfo(properties))
|
||||
// 接口安全配置
|
||||
.components(new Components().securitySchemes(securitySchemas))
|
||||
.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION));
|
||||
.components(new Components().securitySchemes(securitySchemas));
|
||||
securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
|
||||
return openAPI;
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ public class CodegenServiceImpl implements CodegenService {
|
|||
|
||||
//计算需要修改的字段,插入时重新插入,删除时将原来的删除
|
||||
BiPredicate<TableField, CodegenColumnDO> pr =
|
||||
(tableField, codegenColumn) -> tableField.getType().equals(codegenColumn.getDataType())
|
||||
(tableField, codegenColumn) -> tableField.getMetaInfo().getJdbcType().name().equals(codegenColumn.getDataType())
|
||||
&& tableField.getMetaInfo().isNullable() == codegenColumn.getNullable()
|
||||
&& tableField.isKeyFlag() == codegenColumn.getPrimaryKey()
|
||||
&& tableField.getComment().equals(codegenColumn.getColumnComment());
|
||||
|
|
|
@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@Import(SeckillActivityServiceImpl.class)
|
||||
@Disabled // TODO 芋艿:未来开启
|
||||
public class SeckillActivityServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
|
|
|
@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@Import(SeckillTimeServiceImpl.class)
|
||||
@Disabled // TODO 芋艿:未来开启
|
||||
public class SeckillTimeServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
|
|
|
@ -37,7 +37,6 @@ public interface TradeAfterSaleConvert {
|
|||
TradeAfterSaleDO convert(AppTradeAfterSaleCreateReqVO createReqVO, TradeOrderItemDO tradeOrderItem);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "afterSale.orderId", target = "merchantOrderId"),
|
||||
@Mapping(source = "afterSale.applyReason", target = "reason"),
|
||||
@Mapping(source = "afterSale.refundPrice", target = "amount")
|
||||
})
|
||||
|
|
|
@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
|
|||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.member.api.address.AddressApi;
|
||||
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
|
@ -63,6 +64,8 @@ public class TradeOrderServiceTest extends BaseDbUnitTest {
|
|||
@Resource
|
||||
private TradeOrderItemMapper tradeOrderItemMapper;
|
||||
|
||||
@MockBean
|
||||
private MemberUserApi memberUserApi;
|
||||
@MockBean
|
||||
private ProductSpuApi productSpuApi;
|
||||
@MockBean
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.pay.api.notify.dto;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -31,12 +32,4 @@ public class PayRefundNotifyReqDTO {
|
|||
@NotNull(message = "支付退款编号不能为空")
|
||||
private Long payRefundId;
|
||||
|
||||
/**
|
||||
* 退款状态
|
||||
*
|
||||
* (成功,失败) TODO 芋艿:枚举
|
||||
*/
|
||||
@NotNull(message = "退款状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
|
|
|
@ -28,12 +28,6 @@ public class PayRefundCreateReqDTO {
|
|||
|
||||
// ========== 商户相关字段 ==========
|
||||
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
@NotEmpty(message = "商户订单编号不能为空")
|
||||
private String merchantOrderId;
|
||||
|
||||
/**
|
||||
* 退款描述
|
||||
*/
|
||||
|
@ -43,6 +37,12 @@ public class PayRefundCreateReqDTO {
|
|||
|
||||
// ========== 订单相关字段 ==========
|
||||
|
||||
/**
|
||||
* 支付单号
|
||||
*/
|
||||
@NotNull(message = "支付单号不能为空")
|
||||
private Long payOrderId;
|
||||
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
|
|
|
@ -27,8 +27,16 @@ public class PayRefundRespDTO {
|
|||
* 枚举 {@link PayRefundStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
private Integer refundAmount;
|
||||
|
||||
// ========== 渠道相关字段 ==========
|
||||
// ========== 商户相关字段 ==========
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.pay.enums;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
|
@ -50,11 +51,23 @@ public interface ErrorCodeConstants {
|
|||
ErrorCode PAY_REFUND_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
|
||||
ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
|
||||
|
||||
|
||||
/**
|
||||
* ========== 支付商户信息 1-007-004-000 ==========
|
||||
*/
|
||||
ErrorCode PAY_MERCHANT_NOT_EXISTS = new ErrorCode(1007004000, "支付商户信息不存在");
|
||||
ErrorCode PAY_MERCHANT_EXIST_APP_CANT_DELETE = new ErrorCode(1007004001, "支付商户存在支付应用,无法删除");
|
||||
|
||||
// ========== 示例订单 1-007-900-000 ==========
|
||||
ErrorCode PAY_DEMO_ORDER_NOT_FOUND = new ErrorCode(100790000, "示例订单不存在");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(100790001, "示例订单更新支付状态失败,订单不是【未支付】状态");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(100790002, "示例订单更新支付状态失败,支付单编号不匹配");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(100790003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态");
|
||||
ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(100790004, "示例订单更新支付状态失败,支付单金额不匹配");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(100790005, "发起退款失败,示例订单未支付");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(100790006, "发起退款失败,示例订单已退款");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(100790007, "发起退款失败,退款订单不存在");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(100790008, "发起退款失败,退款订单未退款成功");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(100790008, "发起退款失败,退款单编号不匹配");
|
||||
ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(100790004, "发起退款失败,退款单金额不匹配");
|
||||
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.pay.api.refund;
|
|||
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 退款单 API 实现类
|
||||
*
|
||||
|
@ -14,10 +17,12 @@ import org.springframework.validation.annotation.Validated;
|
|||
@Validated
|
||||
public class PayRefundApiImpl implements PayRefundApi {
|
||||
|
||||
@Resource
|
||||
private PayRefundService payRefundService;
|
||||
|
||||
@Override
|
||||
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
// TODO 芋艿:暂未实现
|
||||
return null;
|
||||
return payRefundService.createPayRefund(reqDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,20 +3,26 @@ package cn.iocoder.yudao.module.pay.controller.admin.demo;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.demo.PayDemoOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - 示例订单")
|
||||
|
@ -41,4 +47,32 @@ public class PayDemoOrderController {
|
|||
return success(PayDemoOrderConvert.INSTANCE.convertPage(pageResult));
|
||||
}
|
||||
|
||||
@PostMapping("/update-paid")
|
||||
@Operation(summary = "更新示例订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
|
||||
@OperateLog(enable = false) // 禁用操作日志,因为没有操作人
|
||||
public CommonResult<Boolean> updateDemoOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
|
||||
payDemoOrderService.updateDemoOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||
notifyReqDTO.getPayOrderId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/refund")
|
||||
@Operation(summary = "发起示例订单的退款")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> refundDemoOrder(@RequestParam("id") Long id) {
|
||||
payDemoOrderService.refundDemoOrder(id, getClientIP());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/update-refunded")
|
||||
@Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
|
||||
@OperateLog(enable = false) // 禁用操作日志,因为没有操作人
|
||||
public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
|
||||
payDemoOrderService.updateDemoOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||
notifyReqDTO.getPayRefundId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import java.time.LocalDateTime;
|
|||
@Data
|
||||
public class PayDemoOrderRespVO {
|
||||
|
||||
@Schema(description = "订单编号", required = true, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", required = true, example = "23199")
|
||||
private Long userId;
|
||||
|
||||
|
@ -33,10 +36,19 @@ public class PayDemoOrderRespVO {
|
|||
@Schema(description = "订单支付时间")
|
||||
private LocalDateTime payTime;
|
||||
|
||||
@Schema(description = "支付渠道", example = "alipay_qr")
|
||||
private String payChannelCode;
|
||||
|
||||
@Schema(description = "支付退款编号", example = "23366")
|
||||
private Long payRefundId;
|
||||
|
||||
@Schema(description = "退款金额,单位:分", required = true, example = "14039")
|
||||
private Integer refundPrice;
|
||||
|
||||
@Schema(description = "退款时间")
|
||||
private LocalDateTime refundTime;
|
||||
|
||||
@Schema(description = "创建时间", required = true)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,11 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
|
|||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
@ -17,6 +21,7 @@ import javax.annotation.security.PermitAll;
|
|||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
|
||||
|
||||
@Tag(name = "管理后台 - 支付通知")
|
||||
|
@ -64,28 +69,32 @@ public class PayNotifyController {
|
|||
@PermitAll
|
||||
@OperateLog(enable = false) // 回调地址,无需记录操作日志
|
||||
public String notifyCallback(@PathVariable("channelId") Long channelId,
|
||||
@RequestParam Map<String, String> params,
|
||||
@RequestBody String body) throws Exception {
|
||||
// 校验支付渠道是否存在
|
||||
@RequestParam(required = false) Map<String, String> params,
|
||||
@RequestBody(required = false) String body) {
|
||||
log.info("[notifyCallback][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
||||
// 1. 校验支付渠道是否存在
|
||||
PayClient payClient = payClientFactory.getPayClient(channelId);
|
||||
if (payClient == null) {
|
||||
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
// 校验通知数据是否合法
|
||||
PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(body).build();
|
||||
payClient.verifyNotifyData(notifyData);
|
||||
|
||||
// 情况一:如果是退款,则发起退款通知
|
||||
if (payClient.isRefundNotify(notifyData)) {
|
||||
refundService.notifyPayRefund(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
|
||||
// 2. 解析通知数据
|
||||
PayNotifyReqDTO rawNotify = PayNotifyReqDTO.builder().params(params).body(body).build();
|
||||
Object notify = payClient.parseNotify(rawNotify);
|
||||
|
||||
// 3. 处理通知
|
||||
// 3.1:退款通知
|
||||
if (notify instanceof PayRefundNotifyRespDTO) {
|
||||
refundService.notifyPayRefund(channelId, (PayRefundNotifyRespDTO) notify, rawNotify);
|
||||
return "success";
|
||||
}
|
||||
|
||||
// 情况二:如果非退款,则发起支付通知
|
||||
orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
|
||||
// 3.2:支付通知
|
||||
if (notify instanceof PayOrderNotifyRespDTO) {
|
||||
orderService.notifyPayOrder(channelId, (PayOrderNotifyRespDTO) notify, rawNotify);
|
||||
return "success";
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,12 @@ package cn.iocoder.yudao.module.pay.controller.admin.order;
|
|||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
|
||||
|
@ -12,21 +18,12 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
|
|||
import cn.iocoder.yudao.module.pay.service.merchant.PayMerchantService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -37,6 +34,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
@Tag(name = "管理后台 - 支付订单")
|
||||
|
@ -46,7 +44,7 @@ import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.E
|
|||
public class PayOrderController {
|
||||
|
||||
@Resource
|
||||
private PayOrderService orderService;
|
||||
private PayOrderService payOrderService;
|
||||
@Resource
|
||||
private PayOrderExtensionService orderExtensionService;
|
||||
@Resource
|
||||
|
@ -58,8 +56,17 @@ public class PayOrderController {
|
|||
@Operation(summary = "获得支付订单")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:query')")
|
||||
public CommonResult<PayOrderDetailsRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
PayOrderDO order = orderService.getOrder(id);
|
||||
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
return success(PayOrderConvert.INSTANCE.convert(payOrderService.getOrder(id)));
|
||||
}
|
||||
|
||||
// TODO 芋艿:看看怎么优化下;
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得支付订单详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:query')")
|
||||
public CommonResult<PayOrderDetailsRespVO> getOrderDetail(@RequestParam("id") Long id) {
|
||||
PayOrderDO order = payOrderService.getOrder(id);
|
||||
if (ObjectUtil.isNull(order)) {
|
||||
return success(new PayOrderDetailsRespVO());
|
||||
}
|
||||
|
@ -82,11 +89,18 @@ public class PayOrderController {
|
|||
return success(respVO);
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交支付订单")
|
||||
public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
|
||||
PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得支付订单分页")
|
||||
@PreAuthorize("@ss.hasPermission('pay:order:query')")
|
||||
public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) {
|
||||
PageResult<PayOrderDO> pageResult = orderService.getOrderPage(pageVO);
|
||||
PageResult<PayOrderDO> pageResult = payOrderService.getOrderPage(pageVO);
|
||||
if (CollectionUtil.isEmpty(pageResult.getList())) {
|
||||
return success(new PageResult<>(pageResult.getTotal()));
|
||||
}
|
||||
|
@ -120,7 +134,7 @@ public class PayOrderController {
|
|||
public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
|
||||
List<PayOrderDO> list = orderService.getOrderList(exportReqVO);
|
||||
List<PayOrderDO> list = payOrderService.getOrderList(exportReqVO);
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
ExcelUtils.write(response, "支付订单.xls", "数据",
|
||||
PayOrderExcelVO.class, new ArrayList<>());
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.admin.order.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.awt.*;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "管理后台 - 支付订单提交 Request VO")
|
||||
@Data
|
||||
public class PayOrderSubmitReqVO {
|
||||
|
||||
@Schema(description = "支付单编号", required = true, example = "1024")
|
||||
@NotNull(message = "支付单编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "支付渠道", required = true, example = "wx_pub")
|
||||
@NotEmpty(message = "支付渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
@Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
@Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式
|
||||
private String displayMode;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.admin.order.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Schema(description = "管理后台 - 支付订单提交 Response VO")
|
||||
@Data
|
||||
public class PayOrderSubmitRespVO {
|
||||
|
||||
@Schema(description = "展示模式", required = true, example = "url") // 参见 PayDisplayModeEnum 枚举
|
||||
private String displayMode;
|
||||
|
||||
@Schema(description = "展示内容", required = true)
|
||||
private String displayContent;
|
||||
|
||||
}
|
|
@ -1,15 +1,13 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.order;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
@ -34,20 +32,9 @@ public class AppPayOrderController {
|
|||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交支付订单")
|
||||
// @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
|
||||
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
|
||||
// 获得订单
|
||||
PayOrderDO payOrder = orderService.getOrder(reqVO.getId());
|
||||
|
||||
// 提交支付
|
||||
PayOrderSubmitReqDTO reqDTO = new PayOrderSubmitReqDTO();
|
||||
BeanUtil.copyProperties(reqVO, reqDTO, false);
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setAppId(payOrder.getAppId());
|
||||
PayOrderSubmitRespDTO respDTO = orderService.submitPayOrder(reqDTO);
|
||||
|
||||
// 拼接返回
|
||||
return success(AppPayOrderSubmitRespVO.builder().invokeResponse(respDTO.getInvokeResponse()).build());
|
||||
PayOrderSubmitRespVO respVO = orderService.submitPayOrder(reqVO, getClientIP());
|
||||
return success(PayOrderConvert.INSTANCE.convert3(respVO));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.order.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
@ -10,18 +11,5 @@ import java.util.Map;
|
|||
|
||||
@Schema(description = "用户 APP - 支付订单提交 Request VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AppPayOrderSubmitReqVO {
|
||||
|
||||
@Schema(description = "支付单编号", required = true, example = "1024")
|
||||
@NotNull(message = "支付单编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "支付渠道", required = true, example = "wx_pub")
|
||||
@NotEmpty(message = "支付渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
@Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO {
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.order.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
|
@ -9,15 +10,6 @@ import lombok.experimental.Accessors;
|
|||
|
||||
@Schema(description = "用户 APP - 支付订单提交 Response VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppPayOrderSubmitRespVO {
|
||||
|
||||
/**
|
||||
* 调用支付渠道的响应结果
|
||||
*/
|
||||
private Object invokeResponse;
|
||||
public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.refund;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||
import cn.iocoder.yudao.module.pay.util.PaySeqUtils;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
|
||||
@Tag(name = "用户 APP - 退款订单")
|
||||
@RestController
|
||||
@RequestMapping("/pay/refund")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppPayRefundController {
|
||||
|
||||
@Resource
|
||||
private PayRefundService refundService;
|
||||
|
||||
@PostMapping("/refund")
|
||||
@Operation(summary = "提交退款订单")
|
||||
public CommonResult<AppPayRefundRespVO> submitRefundOrder(@RequestBody AppPayRefundReqVO reqVO){
|
||||
PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO);
|
||||
req.setUserIp(getClientIP());
|
||||
// TODO 测试暂时模拟生成商户退款订单
|
||||
if(StrUtil.isEmpty(reqVO.getMerchantRefundId())) {
|
||||
req.setMerchantRefundId(PaySeqUtils.genMerchantRefundNo());
|
||||
}
|
||||
return success(PayRefundConvert.INSTANCE.convert(refundService.submitRefundOrder(req)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* TODO 芋艿:占个位置,没啥用
|
||||
*/
|
||||
package cn.iocoder.yudao.module.pay.controller.app.refund;
|
|
@ -1,34 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.refund.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "用户 APP - 退款订单 Req VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppPayRefundReqVO {
|
||||
|
||||
@Schema(description = "支付订单编号自增", required = true, example = "10")
|
||||
@NotNull(message = "支付订单编号自增")
|
||||
private Long payOrderId;
|
||||
|
||||
@Schema(description = "退款金额", required = true, example = "1")
|
||||
@NotNull(message = "退款金额")
|
||||
private Long amount;
|
||||
|
||||
@Schema(description = "退款原因", required = true, example = "不喜欢")
|
||||
@NotEmpty(message = "退款原因")
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "商户退款订单号", required = true, example = "MR202111180000000001")
|
||||
//TODO 测试暂时模拟生成
|
||||
//@NotEmpty(message = "商户退款订单号")
|
||||
private String merchantRefundId;
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.controller.app.refund.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Schema(description = "用户 APP - 提交退款订单 Response VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppPayRefundRespVO {
|
||||
|
||||
@Schema(description = "退款订单编号", required = true, example = "10")
|
||||
private Long refundId;
|
||||
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
package cn.iocoder.yudao.module.pay.convert.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderDetailsRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExcelVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
@ -31,6 +30,8 @@ public interface PayOrderConvert {
|
|||
|
||||
PayOrderRespVO convert(PayOrderDO bean);
|
||||
|
||||
PayOrderRespDTO convert2(PayOrderDO order);
|
||||
|
||||
PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
|
||||
|
||||
PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
|
||||
|
@ -88,14 +89,15 @@ public interface PayOrderConvert {
|
|||
return payOrderExcelVO;
|
||||
}
|
||||
|
||||
|
||||
PayOrderDO convert(PayOrderCreateReqDTO bean);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
PayOrderExtensionDO convert(PayOrderSubmitReqDTO bean);
|
||||
PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp);
|
||||
|
||||
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqDTO bean);
|
||||
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO);
|
||||
|
||||
PayOrderRespDTO convert2(PayOrderDO bean);
|
||||
PayOrderSubmitRespVO convert(PayOrderUnifiedRespDTO bean);
|
||||
|
||||
AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);
|
||||
|
||||
}
|
||||
|
|
|
@ -2,12 +2,8 @@ package cn.iocoder.yudao.module.pay.convert.refund;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
|
@ -17,11 +13,6 @@ import java.math.BigDecimal;
|
|||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 退款订单 Convert
|
||||
*
|
||||
* @author aquan
|
||||
*/
|
||||
@Mapper
|
||||
public interface PayRefundConvert {
|
||||
|
||||
|
@ -102,8 +93,4 @@ public interface PayRefundConvert {
|
|||
})
|
||||
PayRefundDO convert(PayOrderDO orderDO);
|
||||
|
||||
PayRefundReqDTO convert(AppPayRefundReqVO bean);
|
||||
|
||||
AppPayRefundRespVO convert(PayRefundRespDTO bean);
|
||||
|
||||
}
|
||||
|
|
|
@ -73,14 +73,16 @@ public class PayDemoOrderDO extends BaseDO {
|
|||
|
||||
// ========== 退款相关字段 ==========
|
||||
/**
|
||||
* 退款金额
|
||||
* 支付退款单号
|
||||
*/
|
||||
private Long payRefundId;
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
private Integer refundPrice;
|
||||
/**
|
||||
* 退款时间
|
||||
*
|
||||
* 由于可以多次退款,记录最后一次退款的时间
|
||||
* 退款完成时间
|
||||
*/
|
||||
private Date refundTime;
|
||||
private LocalDateTime refundTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -80,7 +80,6 @@ public class PayRefundDO extends BaseDO {
|
|||
*/
|
||||
private String tradeNo;
|
||||
|
||||
|
||||
// ========== 商户相关字段 ==========
|
||||
/**
|
||||
* 商户订单编号
|
||||
|
@ -171,14 +170,12 @@ public class PayRefundDO extends BaseDO {
|
|||
*/
|
||||
private String channelErrorMsg;
|
||||
|
||||
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
* 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
|
||||
*/
|
||||
private String channelExtras;
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 退款失效时间
|
||||
|
@ -193,5 +190,4 @@ public class PayRefundDO extends BaseDO {
|
|||
*/
|
||||
private LocalDateTime notifyTime;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -20,4 +20,9 @@ public interface PayDemoOrderMapper extends BaseMapperX<PayDemoOrderDO> {
|
|||
.orderByDesc(PayDemoOrderDO::getId));
|
||||
}
|
||||
|
||||
default int updateByIdAndPayed(Long id, boolean wherePayed, PayDemoOrderDO updateObj) {
|
||||
return update(updateObj, new LambdaQueryWrapperX<PayDemoOrderDO>()
|
||||
.eq(PayDemoOrderDO::getId, id).eq(PayDemoOrderDO::getPayed, wherePayed));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
|||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayNotifyLogCoreMapper extends BaseMapperX<PayNotifyLogDO> {
|
||||
public interface PayNotifyLogMapper extends BaseMapperX<PayNotifyLogDO> {
|
||||
}
|
|
@ -10,7 +10,7 @@ import java.time.LocalDateTime;
|
|||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> {
|
||||
public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
|
||||
|
||||
/**
|
||||
* 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件:
|
|
@ -39,4 +39,28 @@ public interface PayDemoOrderService {
|
|||
*/
|
||||
PageResult<PayDemoOrderDO> getDemoOrderPage(PageParam pageReqVO);
|
||||
|
||||
/**
|
||||
* 更新示例订单为已支付
|
||||
*
|
||||
* @param id 编号
|
||||
* @param payOrderId 支付订单号
|
||||
*/
|
||||
void updateDemoOrderPaid(Long id, Long payOrderId);
|
||||
|
||||
/**
|
||||
* 发起示例订单的退款
|
||||
*
|
||||
* @param id 编号
|
||||
* @param userIp 用户编号
|
||||
*/
|
||||
void refundDemoOrder(Long id, String userIp);
|
||||
|
||||
/**
|
||||
* 更新示例订单为已退款
|
||||
*
|
||||
* @param id 编号
|
||||
* @param payRefundId 退款订单号
|
||||
*/
|
||||
void updateDemoOrderRefunded(Long id, Long payRefundId);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,23 +1,38 @@
|
|||
package cn.iocoder.yudao.module.pay.service.demo;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.hutool.core.util.ObjectUtil.*;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 示例订单 Service 实现类
|
||||
|
@ -26,6 +41,7 @@ import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getCli
|
|||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
||||
|
||||
/**
|
||||
|
@ -45,6 +61,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
|
||||
@Resource
|
||||
private PayOrderApi payOrderApi;
|
||||
@Resource
|
||||
private PayRefundApi payRefundApi;
|
||||
|
||||
@Resource
|
||||
private PayDemoOrderMapper payDemoOrderMapper;
|
||||
|
@ -53,8 +71,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
spuNames.put(1L, new Object[]{"华为手机", 1});
|
||||
spuNames.put(2L, new Object[]{"小米电视", 10});
|
||||
spuNames.put(3L, new Object[]{"苹果手表", 100});
|
||||
spuNames.put(4L, new Object[]{"华硕笔记本", 200});
|
||||
spuNames.put(5L, new Object[]{"蔚来汽车", 300});
|
||||
spuNames.put(4L, new Object[]{"华硕笔记本", 1000});
|
||||
spuNames.put(5L, new Object[]{"蔚来汽车", 200000});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -67,7 +85,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
// 1.2 插入 demo 订单
|
||||
PayDemoOrderDO demoOrder = new PayDemoOrderDO().setUserId(userId)
|
||||
.setSpuId(createReqVO.getSpuId()).setSpuName(spuName)
|
||||
.setPayed(false).setRefundPrice(0);
|
||||
.setPrice(price).setPayed(false).setRefundPrice(0);
|
||||
payDemoOrderMapper.insert(demoOrder);
|
||||
|
||||
// 2.1 创建支付单
|
||||
|
@ -99,4 +117,152 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
|
|||
return payDemoOrderMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDemoOrderPaid(Long id, Long payOrderId) {
|
||||
// 校验并获得支付订单(可支付)
|
||||
PayOrderRespDTO payOrder = validateDemoOrderCanPaid(id, payOrderId);
|
||||
|
||||
// 更新 PayDemoOrderDO 状态为已支付
|
||||
int updateCount = payDemoOrderMapper.updateByIdAndPayed(id, false,
|
||||
new PayDemoOrderDO().setPayed(true).setPayTime(LocalDateTime.now())
|
||||
.setPayChannelCode(payOrder.getChannelCode()));
|
||||
if (updateCount == 0) {
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验交易订单满足被支付的条件
|
||||
*
|
||||
* 1. 交易订单未支付
|
||||
* 2. 支付单已支付
|
||||
*
|
||||
* @param id 交易订单编号
|
||||
* @param payOrderId 支付订单编号
|
||||
* @return 交易订单
|
||||
*/
|
||||
private PayOrderRespDTO validateDemoOrderCanPaid(Long id, Long payOrderId) {
|
||||
// 1.1 校验订单是否存在
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(PAY_DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验订单未支付
|
||||
if (order.getPayed()) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
|
||||
id, toJsonString(order));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
// 1.3 校验支付订单匹配
|
||||
if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验支付单是否存在
|
||||
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
|
||||
throw exception(PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 2.2 校验支付单已支付
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验支付金额一致
|
||||
if (notEqual(payOrder.getAmount(), order.getPrice())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(order), toJsonString(payOrder));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验支付订单匹配(二次)
|
||||
if (notEqual(payOrder.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, toJsonString(payOrder));
|
||||
throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
return payOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refundDemoOrder(Long id, String userIp) {
|
||||
// 1. 校验订单是否可以退款
|
||||
PayDemoOrderDO order = validateDemoOrderCanRefund(id);
|
||||
|
||||
// 2.1 创建退款单
|
||||
Long payRefundId = payRefundApi.createPayRefund(new PayRefundCreateReqDTO()
|
||||
.setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
|
||||
.setPayOrderId(order.getPayOrderId()) // 支付单号
|
||||
.setReason("想退钱").setAmount(order.getPrice()));// 价格信息
|
||||
// 2.2 更新退款单到 demo 订单
|
||||
payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
|
||||
.setPayRefundId(payRefundId).setRefundPrice(order.getPrice()));
|
||||
}
|
||||
|
||||
private PayDemoOrderDO validateDemoOrderCanRefund(Long id) {
|
||||
// 校验订单是否存在
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(PAY_DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 校验订单是否支付
|
||||
if (!order.getPayed()) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID);
|
||||
}
|
||||
// 校验订单是否已退款
|
||||
if (order.getPayRefundId() != null) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDemoOrderRefunded(Long id, Long payRefundId) {
|
||||
// 1. 校验并获得退款订单(可退款)
|
||||
PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, payRefundId);
|
||||
// 2.2 更新退款单到 demo 订单
|
||||
payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
|
||||
.setRefundTime(payRefund.getSuccessTime()));
|
||||
}
|
||||
|
||||
private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, Long payRefundId) {
|
||||
// 1.1 校验示例订单
|
||||
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(PAY_DEMO_ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验退款订单匹配
|
||||
if (Objects.equals(order.getPayOrderId(), payRefundId)) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payRefundId, toJsonString(order));
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2.1 校验退款订单
|
||||
PayRefundRespDTO payRefund = payRefundApi.getPayRefund(payRefundId);
|
||||
if (payRefund == null) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
|
||||
}
|
||||
// 2.2
|
||||
if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
|
||||
}
|
||||
// 2.3 校验退款金额一致
|
||||
if (notEqual(payRefund.getRefundAmount(), order.getPrice())) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配,请进行处理!order 数据是:{},payRefund 数据是:{}]",
|
||||
id, payRefundId, toJsonString(order), toJsonString(payRefund));
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 2.4 校验退款订单匹配(二次)
|
||||
if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
|
||||
id, payRefundId, toJsonString(payRefund));
|
||||
throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
||||
}
|
||||
return payRefund;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
|
|||
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogCoreMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskCoreMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
|
@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -40,6 +41,7 @@ import java.util.Objects;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration.NOTIFY_THREAD_POOL_TASK_EXECUTOR;
|
||||
|
||||
/**
|
||||
|
@ -69,9 +71,9 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
private PayRefundService refundService;
|
||||
|
||||
@Resource
|
||||
private PayNotifyTaskCoreMapper payNotifyTaskCoreMapper;
|
||||
private PayNotifyTaskMapper payNotifyTaskMapper;
|
||||
@Resource
|
||||
private PayNotifyLogCoreMapper payNotifyLogCoreMapper;
|
||||
private PayNotifyLogMapper payNotifyLogMapper;
|
||||
|
||||
@Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR)
|
||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
|
@ -101,7 +103,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
}
|
||||
|
||||
// 执行插入
|
||||
payNotifyTaskCoreMapper.insert(task);
|
||||
payNotifyTaskMapper.insert(task);
|
||||
|
||||
// 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟
|
||||
self.executeNotifyAsync(task);
|
||||
|
@ -110,7 +112,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
@Override
|
||||
public int executeNotify() throws InterruptedException {
|
||||
// 获得需要通知的任务
|
||||
List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify();
|
||||
List<PayNotifyTaskDO> tasks = payNotifyTaskMapper.selectListByNotify();
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -168,8 +170,8 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
|
||||
// 校验,当前任务是否已经被通知过
|
||||
// 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
|
||||
PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
|
||||
if (LocalDateTimeUtils.afterNow(dbTask.getNextNotifyTime())) {
|
||||
PayNotifyTaskDO dbTask = payNotifyTaskMapper.selectById(task.getId());
|
||||
if (afterNow(dbTask.getNextNotifyTime())) {
|
||||
log.info("[executeNotifySync][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]",
|
||||
JsonUtils.toJsonString(dbTask));
|
||||
return;
|
||||
|
@ -197,7 +199,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
// 记录 PayNotifyLog 日志
|
||||
String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
|
||||
JsonUtils.toJsonString(invokeResult);
|
||||
payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
|
||||
payNotifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
|
||||
.notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
|
||||
}
|
||||
|
||||
|
@ -250,23 +252,22 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
|||
// 情况一:调用成功
|
||||
if (invokeResult != null && invokeResult.isSuccess()) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
// 情况二:调用失败、调用异常
|
||||
// 2.1 超过最大回调次数
|
||||
if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
// 2.2 未超过最大回调次数
|
||||
updateTask.setNextNotifyTime(LocalDateTime.now().plusSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
||||
updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()])));
|
||||
updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
|
||||
: PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
|
||||
private void processNotifySuccess(PayNotifyTaskDO task, PayNotifyTaskDO updateTask) {
|
||||
payNotifyTaskCoreMapper.updateById(updateTask);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -81,17 +83,20 @@ public interface PayOrderService {
|
|||
* 提交支付
|
||||
* 此时,会发起支付渠道的调用
|
||||
*
|
||||
* @param reqDTO 提交请求
|
||||
* @param reqVO 提交请求
|
||||
* @param userIp 提交 IP
|
||||
* @return 提交结果
|
||||
*/
|
||||
PayOrderSubmitRespDTO submitPayOrder(@Valid PayOrderSubmitReqDTO reqDTO);
|
||||
PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO,
|
||||
@NotEmpty(message = "提交 IP 不能为空") String userIp);
|
||||
|
||||
/**
|
||||
* 通知支付单成功
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notifyData 通知数据
|
||||
* @param notify 通知
|
||||
* @param rawNotify 通知数据
|
||||
*/
|
||||
void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
|
||||
void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
|
||||
|
||||
}
|
||||
|
|
|
@ -3,19 +3,21 @@ package cn.iocoder.yudao.module.pay.service.order;
|
|||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
|
@ -31,8 +33,6 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
|
|||
import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -42,9 +42,9 @@ import javax.annotation.Resource;
|
|||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.*;
|
||||
|
||||
/**
|
||||
* 支付订单 Service 实现类
|
||||
|
@ -105,7 +105,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
reqDTO.getAppId(), reqDTO.getMerchantOrderId());
|
||||
if (order != null) {
|
||||
log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
|
||||
order.getMerchantOrderId(), JsonUtils.toJsonString(order)); // 理论来说,不会出现这个情况
|
||||
order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况
|
||||
return order.getId();
|
||||
}
|
||||
|
||||
|
@ -127,51 +127,60 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) {
|
||||
// 校验 App
|
||||
appService.validPayApp(reqDTO.getAppId());
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
|
||||
// 校验支付客户端是否正确初始化
|
||||
public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
|
||||
// 1. 获得 PayOrderDO ,并校验其是否存在
|
||||
PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
|
||||
// 1.2 校验支付渠道是否有效
|
||||
PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[submitPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 获得 PayOrderDO ,并校验其是否存在
|
||||
PayOrderDO order = orderMapper.selectById(reqDTO.getId());
|
||||
if (order == null || !Objects.equals(order.getAppId(), reqDTO.getAppId())) { // 是否存在
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 插入 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqDTO)
|
||||
// 2. 插入 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
|
||||
.setOrderId(order.getId()).setNo(generateOrderExtensionNo())
|
||||
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
|
||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus());
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
|
||||
// 调用三方接口
|
||||
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqDTO);
|
||||
// 商户相关字段
|
||||
//TODO jason @芋艿 是否加一个属性 如tradeNo 支付订单号, 用这个merchantOrderId让人迷糊
|
||||
unifiedOrderReqDTO.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
|
||||
// 3. 调用三方接口
|
||||
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
|
||||
// 商户相关的字段
|
||||
.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
|
||||
.setSubject(order.getSubject()).setBody(order.getBody())
|
||||
.setNotifyUrl(genChannelPayNotifyUrl(channel))
|
||||
.setReturnUrl(genChannelReturnUrl(channel));
|
||||
.setReturnUrl(genChannelReturnUrl(channel))
|
||||
// 订单相关字段
|
||||
unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
|
||||
CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
|
||||
unifiedOrderResult.checkError();
|
||||
.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
|
||||
PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO);
|
||||
|
||||
// TODO 轮询三方接口,是否已经支付的任务
|
||||
// 返回成功
|
||||
return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId())
|
||||
.setInvokeResponse(unifiedOrderResult.getData());
|
||||
return PayOrderConvert.INSTANCE.convert(unifiedOrderRespDTO);
|
||||
}
|
||||
|
||||
private PayOrderDO validatePayOrderCanSubmit(Long id) {
|
||||
PayOrderDO order = orderMapper.selectById(id);
|
||||
if (order == null) { // 是否存在
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private PayChannelDO validatePayChannelCanSubmit(Long appId, String channelCode) {
|
||||
// 校验 App
|
||||
appService.validPayApp(appId);
|
||||
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,49 +222,30 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) {
|
||||
// TODO 芋艿,记录回调日志
|
||||
log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
|
||||
public void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||
TenantUtils.execute(channel.getTenantId(), () -> {
|
||||
try {
|
||||
notifyPayOrder(channel, notifyData);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyPayOrder(PayChannelDO channel, PayNotifyDataDTO notifyData) throws Exception {
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 0. 解析支付结果
|
||||
PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData);
|
||||
// 1. 更新 PayOrderExtensionDO 支付成功
|
||||
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notifyRespDTO.getOrderExtensionNo(), notifyData.getBody());
|
||||
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify.getOrderExtensionNo(),
|
||||
rawNotify);
|
||||
// 2. 更新 PayOrderDO 支付成功
|
||||
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notifyRespDTO);
|
||||
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notify);
|
||||
|
||||
// 3. 插入支付通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 PayOrderExtensionDO 支付成功
|
||||
*
|
||||
* @param no 支付订单号(支付模块)
|
||||
* @param body 回调内容
|
||||
* @param rawNotify 通知数据
|
||||
* @return PayOrderExtensionDO 对象
|
||||
*/
|
||||
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, String body) {
|
||||
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
|
||||
// 1.1 查询 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
|
||||
if (orderExtension == null) {
|
||||
|
@ -267,7 +257,8 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
// 1.2 更新 PayOrderExtensionDO
|
||||
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
|
||||
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
|
||||
.status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(body).build());
|
||||
.status(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.channelNotifyData(toJsonString(rawNotify)).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
@ -280,11 +271,11 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
*
|
||||
* @param channel 支付渠道
|
||||
* @param orderExtension 支付拓展单
|
||||
* @param notifyRespDTO 通知回调
|
||||
* @param notify 通知回调
|
||||
* @return PayOrderDO 对象
|
||||
*/
|
||||
private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
|
||||
PayOrderNotifyRespDTO notifyRespDTO) {
|
||||
PayOrderNotifyRespDTO notify) {
|
||||
// 2.1 判断 PayOrderDO 是否处于待支付
|
||||
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
|
||||
if (order == null) {
|
||||
|
@ -297,8 +288,8 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||
int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.channelId(channel.getId()).channelCode(channel.getCode())
|
||||
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
|
||||
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
|
||||
.successTime(notify.getSuccessTime()).successExtensionId(orderExtension.getId())
|
||||
.channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
|
||||
.notifyTime(LocalDateTime.now()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付单提交 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayOrderSubmitReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotNull(message = "应用编号不能为空")
|
||||
private Long appId;
|
||||
|
||||
/**
|
||||
* 支付单编号
|
||||
*/
|
||||
@NotNull(message = "支付单编号不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 支付渠道
|
||||
*/
|
||||
@NotEmpty(message = "支付渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotEmpty(message = "用户 IP 不能为空")
|
||||
private String userIp;
|
||||
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付单提交 Response DTO
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderSubmitRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 支付拓展单的编号
|
||||
*/
|
||||
private Long extensionId;
|
||||
|
||||
/**
|
||||
* 调用支付渠道的响应结果
|
||||
*/
|
||||
private Object invokeResponse;
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 退款申请单 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundReqDTO {
|
||||
|
||||
/**
|
||||
* 支付订单编号
|
||||
*/
|
||||
@NotNull(message = "支付订单编号不能为空")
|
||||
private Long payOrderId;
|
||||
|
||||
/**
|
||||
* 退款金额
|
||||
*/
|
||||
@NotNull(message = "退款金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "退款金额必须大于零")
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 商户退款订单号
|
||||
*/
|
||||
@NotEmpty(message = "商户退款订单号不能为空")
|
||||
private String merchantRefundId;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
private String userIp;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package cn.iocoder.yudao.module.pay.service.order.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 退款申请单 Response DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundRespDTO {
|
||||
|
||||
/**
|
||||
* 支付退款单编号,自增
|
||||
*/
|
||||
private Long refundId;
|
||||
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package cn.iocoder.yudao.module.pay.service.refund;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -42,20 +42,20 @@ public interface PayRefundService {
|
|||
List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO);
|
||||
|
||||
/**
|
||||
* 提交退款申请
|
||||
* 创建退款申请
|
||||
*
|
||||
* @param reqDTO 退款申请信息
|
||||
* @return 退款申请返回信息
|
||||
* @return 退款单号
|
||||
*/
|
||||
PayRefundRespDTO submitRefundOrder(PayRefundReqDTO reqDTO);
|
||||
Long createPayRefund(PayRefundCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 渠道的退款通知
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notifyData 通知数据
|
||||
* @throws Exception 退款通知异常
|
||||
* @param notify 通知
|
||||
* @param rawNotify 通知数据
|
||||
*/
|
||||
void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
|
||||
void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
package cn.iocoder.yudao.module.pay.service.refund;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
|
||||
|
@ -19,7 +21,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
|||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
|
||||
|
@ -32,8 +33,6 @@ import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
|||
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -54,6 +53,9 @@ import java.util.Objects;
|
|||
@Validated
|
||||
public class PayRefundServiceImpl implements PayRefundService {
|
||||
|
||||
@Resource
|
||||
private PayProperties payProperties;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
|
@ -90,9 +92,9 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) {
|
||||
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
|
||||
// 获得 PayOrderDO
|
||||
PayOrderDO order = orderService.getOrder(req.getPayOrderId());
|
||||
PayOrderDO order = orderService.getOrder(reqDTO.getPayOrderId());
|
||||
// 校验订单是否存在
|
||||
if (Objects.isNull(order) ) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
|
||||
|
@ -108,15 +110,19 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// TODO 芋艿:待实现
|
||||
String merchantRefundId = RandomUtil.randomNumbers(16);
|
||||
|
||||
// 校验退款的条件
|
||||
validatePayRefund(req, order);
|
||||
validatePayRefund(reqDTO, order);
|
||||
// 退款类型
|
||||
PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
|
||||
if (Objects.equals(req.getAmount(), order.getAmount())) {
|
||||
if (Objects.equals(reqDTO.getAmount(), order.getAmount())) {
|
||||
refundType = PayRefundTypeEnum.ALL;
|
||||
}
|
||||
PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
|
||||
PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundId());
|
||||
PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
|
||||
merchantRefundId); // TODO 芋艿:需要优化
|
||||
if(Objects.nonNull(payRefundDO)){
|
||||
// 退款订单已经提交过。
|
||||
//TODO 校验相同退款单的金额
|
||||
|
@ -137,15 +143,15 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
.channelId(order.getChannelId())
|
||||
.merchantId(order.getMerchantId())
|
||||
.orderId(order.getId())
|
||||
.merchantRefundNo(req.getMerchantRefundId())
|
||||
.merchantRefundNo(merchantRefundId) // TODO 芋艿:需要优化
|
||||
.notifyUrl(app.getRefundNotifyUrl())
|
||||
.payAmount(order.getAmount())
|
||||
.refundAmount(req.getAmount())
|
||||
.userIp(req.getUserIp())
|
||||
.refundAmount(reqDTO.getAmount())
|
||||
.userIp(reqDTO.getUserIp())
|
||||
.merchantOrderId(order.getMerchantOrderId())
|
||||
.tradeNo(orderExtensionDO.getNo())
|
||||
.status(PayRefundStatusEnum.CREATE.getStatus())
|
||||
.reason(req.getReason())
|
||||
.reason(reqDTO.getReason())
|
||||
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
||||
.type(refundType.getStatus())
|
||||
.build();
|
||||
|
@ -153,47 +159,50 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
}
|
||||
// TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
|
||||
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
|
||||
unifiedReqDTO.setUserIp(req.getUserIp())
|
||||
.setAmount(req.getAmount())
|
||||
unifiedReqDTO.setUserIp(reqDTO.getUserIp())
|
||||
.setAmount(reqDTO.getAmount())
|
||||
.setChannelOrderNo(order.getChannelOrderNo())
|
||||
.setPayTradeNo(orderExtensionDO.getNo())
|
||||
.setMerchantRefundId(req.getMerchantRefundId())
|
||||
.setReason(req.getReason());
|
||||
.setMerchantRefundId(merchantRefundId) // TODO 芋艿:需要优化
|
||||
.setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl
|
||||
.setReason(reqDTO.getReason());
|
||||
// 向渠道发起退款申请
|
||||
PayCommonResult<PayRefundUnifiedRespDTO> refundUnifiedResult = client.unifiedRefund(unifiedReqDTO);
|
||||
client.unifiedRefund(unifiedReqDTO);
|
||||
// 检查是否失败,失败抛出业务异常。
|
||||
// TODO 渠道的异常记录。
|
||||
// TODO @jason:可以先打个 warn log 哈;
|
||||
refundUnifiedResult.checkError();
|
||||
// 成功在 退款回调中处理
|
||||
return PayRefundRespDTO.builder().refundId(payRefundDO.getId()).build();
|
||||
return payRefundDO.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付渠道的编码,生成支付渠道的回调地址
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
||||
return payProperties.getCallbackUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
|
||||
log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
public void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
|
||||
// 校验支付渠道是否有效
|
||||
// TODO 芋艿:需要重构下这块的逻辑
|
||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
// 解析渠道退款通知数据, 统一处理
|
||||
PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
|
||||
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
|
||||
payRefundSuccess(refundNotify);
|
||||
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())){
|
||||
payRefundSuccess(notify);
|
||||
} else {
|
||||
//TODO 支付异常, 支付宝似乎没有支付异常的通知。
|
||||
// TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
|
||||
}
|
||||
}
|
||||
|
||||
private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
|
||||
private void payRefundSuccess(PayRefundNotifyRespDTO refundNotify) {
|
||||
// 校验退款单存在
|
||||
PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
|
||||
PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
|
||||
refundNotify.getReqNo());
|
||||
if (refundDO == null) {
|
||||
log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
|
||||
|
@ -235,10 +244,11 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
|
||||
/**
|
||||
* 校验是否进行退款
|
||||
* @param req 退款申请信息
|
||||
*
|
||||
* @param reqDTO 退款申请信息
|
||||
* @param order 原始支付订单信息
|
||||
*/
|
||||
private void validatePayRefund(PayRefundReqDTO req, PayOrderDO order) {
|
||||
private void validatePayRefund(PayRefundCreateReqDTO reqDTO, PayOrderDO order) {
|
||||
// 校验状态,必须是支付状态
|
||||
if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS);
|
||||
|
@ -248,7 +258,7 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED);
|
||||
}
|
||||
// 校验金额 退款金额不能大于 原定的金额
|
||||
if (req.getAmount() + order.getRefundAmount() > order.getAmount()){
|
||||
if (reqDTO.getAmount() + order.getRefundAmount() > order.getAmount()){
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_AMOUNT_EXCEED);
|
||||
}
|
||||
// 校验渠道订单号
|
||||
|
|
|
@ -18,6 +18,7 @@ public class PaySeqUtils {
|
|||
|
||||
private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L);
|
||||
|
||||
// TODO 芋艿:需要看看
|
||||
/**
|
||||
* 生成商户退款单号,用于测试,应该由商户系统生成
|
||||
* @return 商户退款单
|
||||
|
@ -28,6 +29,8 @@ public class PaySeqUtils {
|
|||
(int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000);
|
||||
}
|
||||
|
||||
// TODO 芋艿:需要看看
|
||||
|
||||
/**
|
||||
* 生成退款请求号
|
||||
* @return 退款请求号
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.module.pay.service.refund;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
@ -38,6 +39,8 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
|
|||
@Resource
|
||||
private PayRefundMapper refundMapper;
|
||||
|
||||
@MockBean
|
||||
private PayProperties payProperties;
|
||||
@MockBean
|
||||
private PayClientFactory payClientFactory;
|
||||
@MockBean
|
||||
|
|
|
@ -123,7 +123,10 @@ public class UserController {
|
|||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('system:user:query')")
|
||||
public CommonResult<UserRespVO> getUser(@RequestParam("id") Long id) {
|
||||
return success(UserConvert.INSTANCE.convert(userService.getUser(id)));
|
||||
AdminUserDO user = userService.getUser(id);
|
||||
// 获得部门数据
|
||||
DeptDO dept = deptService.getDept(user.getDeptId());
|
||||
return success(UserConvert.INSTANCE.convert(user).setDept(UserConvert.INSTANCE.convert(dept)));
|
||||
}
|
||||
|
||||
@GetMapping("/export")
|
||||
|
|
|
@ -76,9 +76,9 @@ public class DeptServiceImpl implements DeptService {
|
|||
// 第二步:构建缓存
|
||||
ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
|
||||
ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
|
||||
depts.forEach(sysRoleDO -> {
|
||||
builder.put(sysRoleDO.getId(), sysRoleDO);
|
||||
parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
|
||||
depts.forEach(deptDO -> {
|
||||
builder.put(deptDO.getId(), deptDO);
|
||||
parentBuilder.put(deptDO.getParentId(), deptDO);
|
||||
});
|
||||
deptCache = builder.build();
|
||||
parentDeptCache = parentBuilder.build();
|
||||
|
|
|
@ -54,11 +54,11 @@
|
|||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- 支付服务 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-pay-biz</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.iocoder.boot</groupId>-->
|
||||
<!-- <artifactId>yudao-module-pay-biz</artifactId>-->
|
||||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- 微信公众号模块。默认注释,保证编译速度 -->
|
||||
<!-- <dependency>-->
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.shop.controller.admin;
|
|
@ -1,73 +0,0 @@
|
|||
package cn.iocoder.yudao.module.shop.controller.app;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.util.PaySeqUtils;
|
||||
import cn.iocoder.yudao.module.shop.controller.app.vo.AppShopOrderCreateRespVO;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
|
||||
@Tag(name = "用户 APP - 商城订单")
|
||||
@RestController
|
||||
@RequestMapping("/shop/order")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppShopOrderController {
|
||||
|
||||
@Resource
|
||||
private PayOrderService payOrderService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建商城订单")
|
||||
// @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
|
||||
public CommonResult<AppShopOrderCreateRespVO> create() {
|
||||
// 假装创建商城订单
|
||||
Long shopOrderId = System.currentTimeMillis();
|
||||
|
||||
// 创建对应的支付订单
|
||||
PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO();
|
||||
reqDTO.setAppId(6L);
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setMerchantOrderId(PaySeqUtils.genMerchantOrderNo());
|
||||
reqDTO.setSubject("标题:" + shopOrderId);
|
||||
reqDTO.setBody("内容:" + shopOrderId);
|
||||
reqDTO.setAmount(200); // 单位:分
|
||||
reqDTO.setExpireTime(LocalDateTime.now().plusDays(1));
|
||||
Long payOrderId = payOrderService.createPayOrder(reqDTO);
|
||||
|
||||
// 拼接返回
|
||||
return success(AppShopOrderCreateRespVO.builder().id(shopOrderId)
|
||||
.payOrderId(payOrderId).build());
|
||||
}
|
||||
|
||||
@PostMapping("/pay-notify")
|
||||
@Operation(summary = "支付回调")
|
||||
public CommonResult<Boolean> payNotify(@RequestBody @Valid PayOrderNotifyReqDTO reqVO) {
|
||||
log.info("[payNotify][回调成功]");
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/refund-notify")
|
||||
@Operation(summary = "退款回调")
|
||||
public CommonResult<Boolean> refundNotify(@RequestBody @Valid PayRefundNotifyReqDTO reqVO) {
|
||||
log.info("[refundNotify][回调成功]");
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package cn.iocoder.yudao.module.shop.controller.app.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 APP - 商城订单创建 Response VO")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class AppShopOrderCreateRespVO {
|
||||
|
||||
@Schema(description = "商城订单编号", required = true, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "支付订单编号", required = true, example = "2048")
|
||||
private Long payOrderId;
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* shop 包下,我们放商城业务
|
||||
* 例如说:商品、订单等等
|
||||
* 注意,目前仅仅作为 demo 演示,对接 pay 支付系统
|
||||
*
|
||||
* 缩写:shop
|
||||
*/
|
||||
// TODO 芋艿:后续会迁移到 yudao-module-mall-trade 下
|
||||
package cn.iocoder.yudao.module.shop;
|
|
@ -1,6 +0,0 @@
|
|||
/**
|
||||
* 属于整个 yudao-server 的 framework 封装
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package cn.iocoder.yudao.server.framework;
|
|
@ -1,35 +0,0 @@
|
|||
package cn.iocoder.yudao.server.framework.ui.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* yudao-admin-ui 的配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class AdminUiConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/admin-ui/**", "/admin-ui/", "/admin-ui")
|
||||
.addResourceLocations("classpath:/admin-ui/")
|
||||
// 自定义 ClassPathResource 实现类,在前端请求的地址匹配不到对应的路径时,强制使用 /admin-ui/index.html 资源
|
||||
// 本质上,等价于 nginx 在处理不到 Vue 的请求地址时,try_files 到 index.html 地址
|
||||
// 想要彻底理解,可以调试 ResourceHttpRequestHandler 的 resolveResourceLocations 方法,前端请求 /admin-ui/system/tenant 地址
|
||||
.addResourceLocations(new ClassPathResource("/admin-ui/index.html") {
|
||||
|
||||
@Override
|
||||
public Resource createRelative(String relativePath) {
|
||||
return this;
|
||||
}
|
||||
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package cn.iocoder.yudao.server.framework.ui.core;
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
|
||||
//@Controller
|
||||
//@RequestMapping("/admin-ui/")
|
||||
public class AdminUiController implements ErrorController {
|
||||
|
||||
// public String
|
||||
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/**
|
||||
* 目的:解决后端开发,不太擅长 node 环境的配置,导致启动 yudao-ui-admin 项目一直失败
|
||||
* 所以,本项目将 yudao-ui-admin 项目通过 npm run build:demo1024 的方式,将它构建成静态资源,
|
||||
* 然后,使用 Spring Boot 作为静态资源服务器,进行启动访问。
|
||||
* 注意,这个项目仅仅作为后端开发的快速体验,并不要部署到生产环境!!!
|
||||
*/
|
||||
package cn.iocoder.yudao.server.framework.ui;
|
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB |
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -1,208 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="/admin-ui/favicon.ico">
|
||||
<title>芋道管理系统</title>
|
||||
<!--[if lt IE 11]><script>window.location.href='html/ie.html';</script><![endif]-->
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.chromeframe {
|
||||
margin: 0.2em 0;
|
||||
background: #ccc;
|
||||
color: #000;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
||||
#loader-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
#loader {
|
||||
display: block;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
-ms-animation: spin 2s linear infinite;
|
||||
-moz-animation: spin 2s linear infinite;
|
||||
-o-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
#loader:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-webkit-animation: spin 3s linear infinite;
|
||||
-moz-animation: spin 3s linear infinite;
|
||||
-o-animation: spin 3s linear infinite;
|
||||
-ms-animation: spin 3s linear infinite;
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
#loader:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-moz-animation: spin 1.5s linear infinite;
|
||||
-o-animation: spin 1.5s linear infinite;
|
||||
-ms-animation: spin 1.5s linear infinite;
|
||||
-webkit-animation: spin 1.5s linear infinite;
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#loader-wrapper .loader-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 51%;
|
||||
height: 100%;
|
||||
background: #7171C6;
|
||||
z-index: 1000;
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-left {
|
||||
-webkit-transform: translateX(-100%);
|
||||
-ms-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
|
||||
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-right {
|
||||
-webkit-transform: translateX(100%);
|
||||
-ms-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
|
||||
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
|
||||
}
|
||||
|
||||
.loaded #loader {
|
||||
opacity: 0;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper {
|
||||
visibility: hidden;
|
||||
-webkit-transform: translateY(-100%);
|
||||
-ms-transform: translateY(-100%);
|
||||
transform: translateY(-100%);
|
||||
-webkit-transition: all 0.3s 1s ease-out;
|
||||
transition: all 0.3s 1s ease-out;
|
||||
}
|
||||
|
||||
.no-js #loader-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-js h1 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title {
|
||||
font-family: 'Open Sans';
|
||||
color: #FFF;
|
||||
font-size: 19px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
z-index: 9999999999999;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
opacity: 1;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title span {
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
color: #FFF;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="loader-wrapper">
|
||||
<div id="loader"></div>
|
||||
<div class="loader-section section-left"></div>
|
||||
<div class="loader-section section-right"></div>
|
||||
<div class="load_title">正在加载系统资源,请耐心等待</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/admin-ui/static/js/chunk-vendors.js"></script><script type="text/javascript" src="/admin-ui/static/js/app.js"></script></body>
|
||||
</html>
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue