diff --git a/.gitignore b/.gitignore index a58be0137..c33d9f683 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ nbdist/ !*/build/*.java !*/build/*.html !*/build/*.xml + diff --git a/sql/change_db.sql b/sql/change_db.sql new file mode 100644 index 000000000..810183f3e --- /dev/null +++ b/sql/change_db.sql @@ -0,0 +1,8 @@ +ALTER TABLE `ruoyi-vue-pro`.`pay_order_extension` +CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTER SET 'utf8mb4' NULL DEFAULT NULL COMMENT '支付渠道异步通知的内容' ; + +ALTER TABLE `ruoyi-vue-pro`.`pay_refund` +CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ; + +ALTER TABLE `ruoyi-vue-pro`.`pay_refund` +DROP COLUMN `req_no`; diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/PayAppController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/PayAppController.java new file mode 100644 index 000000000..56d8ffb19 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/PayAppController.java @@ -0,0 +1,163 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.*; +import cn.iocoder.yudao.adminserver.modules.pay.convert.app.PayAppConvert; +import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService; +import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +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.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Slf4j +@Api(tags = "支付应用信息") +@RestController +@RequestMapping("/pay/app") +@Validated +public class PayAppController { + + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + @Resource + private PayMerchantService merchantService; + + @PostMapping("/create") + @ApiOperation("创建支付应用信息") + @PreAuthorize("@ss.hasPermission('pay:app:create')") + public CommonResult createApp(@Valid @RequestBody PayAppCreateReqVO createReqVO) { + return success(appService.createApp(createReqVO)); + } + + @PutMapping("/update") + @ApiOperation("更新支付应用信息") + @PreAuthorize("@ss.hasPermission('pay:app:update')") + public CommonResult updateApp(@Valid @RequestBody PayAppUpdateReqVO updateReqVO) { + appService.updateApp(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @ApiOperation("更新支付应用状态") + @PreAuthorize("@ss.hasPermission('pay:app:update')") + public CommonResult updateAppStatus(@Valid @RequestBody PayAppUpdateStatusReqVO updateReqVO) { + appService.updateAppStatus(updateReqVO.getId(), updateReqVO.getStatus()); + return success(true); + } + + @DeleteMapping("/delete") + @ApiOperation("删除支付应用信息") + @ApiImplicitParam(name = "id", value = "编号", required = true) + @PreAuthorize("@ss.hasPermission('pay:app:delete')") + public CommonResult deleteApp(@RequestParam("id") Long id) { + appService.deleteApp(id); + return success(true); + } + + @GetMapping("/get") + @ApiOperation("获得支付应用信息") + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('pay:app:query')") + public CommonResult getApp(@RequestParam("id") Long id) { + PayAppDO app = appService.getApp(id); + return success(PayAppConvert.INSTANCE.convert(app)); + } + + @GetMapping("/list") + @ApiOperation("获得支付应用信息列表") + @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class) + @PreAuthorize("@ss.hasPermission('pay:app:query')") + public CommonResult> getAppList(@RequestParam("ids") Collection ids) { + List list = appService.getAppList(ids); + return success(PayAppConvert.INSTANCE.convertList(list)); + } + + @GetMapping("/page") + @ApiOperation("获得支付应用信息分页") + @PreAuthorize("@ss.hasPermission('pay:app:query')") + public CommonResult> getAppPage(@Valid PayAppPageReqVO pageVO) { + // 得到应用分页列表 + PageResult pageResult = appService.getAppPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 得到所有的应用编号,查出所有的通道 + Collection payAppIds = CollectionUtils.convertList(pageResult.getList(), PayAppDO::getId); + List channels = channelService.getChannelListByAppIds(payAppIds); + // TODO @aquan:可以基于 appId 简历一个 multiMap。这样下面,直接 get 到之后,CollUtil buildSet 即可 + Iterator iterator = channels.iterator(); + + // 得到所有的商户信息 + Collection merchantIds = CollectionUtils.convertList(pageResult.getList(), PayAppDO::getMerchantId); + Map deptMap = merchantService.getMerchantMap(merchantIds); + + // 利用反射将通道数据复制到返回的数据结构中去 + List appList = new ArrayList<>(pageResult.getList().size()); + pageResult.getList().forEach(app -> { + // 写入应用信息的数据 + PayAppPageItemRespVO respVO = PayAppConvert.INSTANCE.pageConvert(app); + // 写入商户的数据 + respVO.setPayMerchant(PayAppConvert.INSTANCE.convert(deptMap.get(app.getMerchantId()))); + // 写入支付渠道信息的数据 + Set channelCodes = new HashSet<>(PayChannelEnum.values().length); + while (iterator.hasNext()) { + PayChannelDO channelDO = iterator.next(); + if (channelDO.getAppId().equals(app.getId())) { + channelCodes.add(channelDO.getCode()); + iterator.remove(); + } + } + respVO.setChannelCodes(channelCodes); + appList.add(respVO); + }); + + return success(new PageResult<>(appList, pageResult.getTotal())); + } + + @GetMapping("/export-excel") + @ApiOperation("导出支付应用信息 Excel") + @PreAuthorize("@ss.hasPermission('pay:app:export')") + @OperateLog(type = EXPORT) + public void exportAppExcel(@Valid PayAppExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = appService.getAppList(exportReqVO); + // 导出 Excel + List datas = PayAppConvert.INSTANCE.convertList02(list); + ExcelUtils.write(response, "支付应用信息.xls", "数据", PayAppExcelVO.class, datas); + } + + @GetMapping("/list-merchant-id") + @ApiOperation("根据商户 ID 查询支付应用信息") + @ApiImplicitParam(name = "merchantId", value = "商户ID", required = true, example = "1", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('pay:merchant:query')") + public CommonResult> getMerchantListByName(@RequestParam String merchantId) { + List appListDO = appService.getListByMerchantId(merchantId); + return success(PayAppConvert.INSTANCE.convertList(appListDO)); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppBaseVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppBaseVO.java new file mode 100644 index 000000000..6ffe565ec --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppBaseVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import lombok.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +/** +* 支付应用信息 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayAppBaseVO { + + @ApiModelProperty(value = "应用名", required = true) + @NotNull(message = "应用名不能为空") + private String name; + + @ApiModelProperty(value = "开启状态", required = true) + @NotNull(message = "开启状态不能为空") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "支付结果的回调地址", required = true) + @NotNull(message = "支付结果的回调地址不能为空") + private String payNotifyUrl; + + @ApiModelProperty(value = "退款结果的回调地址", required = true) + @NotNull(message = "退款结果的回调地址不能为空") + private String refundNotifyUrl; + + @ApiModelProperty(value = "商户编号", required = true) + @NotNull(message = "商户编号不能为空") + private Long merchantId; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppCreateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppCreateReqVO.java new file mode 100644 index 000000000..3b0e69b99 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +@ApiModel("支付应用信息创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppCreateReqVO extends PayAppBaseVO { + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExcelVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExcelVO.java new file mode 100644 index 000000000..f81b53bd0 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExcelVO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; + +import com.alibaba.excel.annotation.ExcelProperty; + +/** + * 支付应用信息 Excel VO + * + * @author 芋艿 + */ +@Data +public class PayAppExcelVO { + + @ExcelProperty("应用编号") + private Long id; + + @ExcelProperty("应用名") + private String name; + + @ExcelProperty("开启状态") + private Integer status; + + @ExcelProperty("备注") + private String remark; + + @ExcelProperty("支付结果的回调地址") + private String payNotifyUrl; + + @ExcelProperty("退款结果的回调地址") + private String refundNotifyUrl; + + @ExcelProperty("商户编号") + private Long merchantId; + + @ExcelProperty("创建时间") + private Date createTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExportReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExportReqVO.java new file mode 100644 index 000000000..b5d284447 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExportReqVO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel(value = "支付应用信息 Excel 导出 Request VO", description = "参数和 PayAppPageReqVO 是一致的") +@Data +public class PayAppExportReqVO { + + @ApiModelProperty(value = "应用名") + private String name; + + @ApiModelProperty(value = "开启状态") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "支付结果的回调地址") + private String payNotifyUrl; + + @ApiModelProperty(value = "退款结果的回调地址") + private String refundNotifyUrl; + + @ApiModelProperty(value = "商户名称") + private String merchantName; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageItemRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageItemRespVO.java new file mode 100644 index 000000000..6d8144afe --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageItemRespVO.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; +import java.util.Set; + +/** + * 支付应用信息分页查询 Response VO + * + * @author aquan + */ +@ApiModel(value = "支付应用信息分页查询 Response VO", description = "相比于支付信息,还会多出应用渠道的开关信息") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppPageItemRespVO extends PayAppBaseVO { + + @ApiModelProperty(value = "应用编号", required = true) + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + + /** + * 所属商户 + */ + private PayMerchant payMerchant; + + @ApiModel("商户") + @Data + public static class PayMerchant { + + @ApiModelProperty(value = "商户编号", required = true, example = "1") + private Long id; + + @ApiModelProperty(value = "商户名称", required = true, example = "研发部") + private String name; + + } + + @ApiModelProperty(value = "渠道编码集合", required = true, example = "alipay_pc,alipay_wap...") + private Set channelCodes; + + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageReqVO.java new file mode 100644 index 000000000..62156e993 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageReqVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel("支付应用信息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppPageReqVO extends PageParam { + + @ApiModelProperty(value = "应用名") + private String name; + + @ApiModelProperty(value = "开启状态") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "支付结果的回调地址") + private String payNotifyUrl; + + @ApiModelProperty(value = "退款结果的回调地址") + private String refundNotifyUrl; + + @ApiModelProperty(value = "商户名称") + private String merchantName; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppRespVO.java new file mode 100644 index 000000000..565491f92 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; + +@ApiModel("支付应用信息 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppRespVO extends PayAppBaseVO { + + @ApiModelProperty(value = "应用编号", required = true) + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateReqVO.java new file mode 100644 index 000000000..426366fc1 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +@ApiModel("支付应用信息更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppUpdateReqVO extends PayAppBaseVO { + + @ApiModelProperty(value = "应用编号", required = true) + @NotNull(message = "应用编号不能为空") + private Long id; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateStatusReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateStatusReqVO.java new file mode 100644 index 000000000..28a3218ae --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateStatusReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@ApiModel("应用更新状态 Request VO") +@Data +public class PayAppUpdateStatusReqVO { + + @ApiModelProperty(value = "商户编号", required = true, example = "1024") + @NotNull(message = "商户编号不能为空") + private Long id; + + @ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 SysCommonStatusEnum 枚举") + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/PayChannelController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/PayChannelController.java new file mode 100644 index 000000000..2b1d468f8 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/PayChannelController.java @@ -0,0 +1,129 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*; +import cn.iocoder.yudao.adminserver.modules.pay.convert.channel.PayChannelConvert; +import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 支付渠道 controller 组件 + * @author aquan + */ +@Api(tags = "支付渠道") +@RestController +@RequestMapping("/pay/channel") +@Validated +public class PayChannelController { + + @Resource + private PayChannelService channelService; + + + @PostMapping("/create") + @ApiOperation("创建支付渠道 ") + @PreAuthorize("@ss.hasPermission('pay:channel:create')") + public CommonResult createChannel(@Valid @RequestBody PayChannelCreateReqVO createReqVO) { + return success(channelService.createChannel(createReqVO)); + } + + @PutMapping("/update") + @ApiOperation("更新支付渠道 ") + @PreAuthorize("@ss.hasPermission('pay:channel:update')") + public CommonResult updateChannel(@Valid @RequestBody PayChannelUpdateReqVO updateReqVO) { + channelService.updateChannel(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @ApiOperation("删除支付渠道 ") + @ApiImplicitParam(name = "id", value = "编号", required = true) + @PreAuthorize("@ss.hasPermission('pay:channel:delete')") + public CommonResult deleteChannel(@RequestParam("id") Long id) { + channelService.deleteChannel(id); + return success(true); + } + + @GetMapping("/get") + @ApiOperation("获得支付渠道 ") + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('pay:channel:query')") + public CommonResult getChannel(@RequestParam("id") Long id) { + PayChannelDO channel = channelService.getChannel(id); + return success(PayChannelConvert.INSTANCE.convert(channel)); + } + + @GetMapping("/list") + @ApiOperation("获得支付渠道列表") + @ApiImplicitParam(name = "ids", value = "编号列表", + required = true, example = "1024,2048", dataTypeClass = List.class) + @PreAuthorize("@ss.hasPermission('pay:channel:query')") + public CommonResult> getChannelList(@RequestParam("ids") Collection ids) { + List list = channelService.getChannelList(ids); + return success(PayChannelConvert.INSTANCE.convertList(list)); + } + + @GetMapping("/page") + @ApiOperation("获得支付渠道分页") + @PreAuthorize("@ss.hasPermission('pay:channel:query')") + public CommonResult> getChannelPage(@Valid PayChannelPageReqVO pageVO) { + PageResult pageResult = channelService.getChannelPage(pageVO); + return success(PayChannelConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/export-excel") + @ApiOperation("导出支付渠道Excel") + @PreAuthorize("@ss.hasPermission('pay:channel:export')") + @OperateLog(type = EXPORT) + public void exportChannelExcel(@Valid PayChannelExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = channelService.getChannelList(exportReqVO); + // 导出 Excel + List datas = PayChannelConvert.INSTANCE.convertList02(list); + ExcelUtils.write(response, "支付渠道.xls", "数据", PayChannelExcelVO.class, datas); + } + + @GetMapping("/get-channel") + @ApiOperation("根据条件查询微信支付渠道") + @ApiImplicitParams({ + @ApiImplicitParam(name = "merchantId", value = "商户编号", + required = true, example = "1", dataTypeClass = Long.class), + @ApiImplicitParam(name = "appId", value = "应用编号", + required = true, example = "1", dataTypeClass = Long.class), + @ApiImplicitParam(name = "code", value = "支付渠道编码", + required = true, example = "wx_pub", dataTypeClass = String.class) + }) + @PreAuthorize("@ss.hasPermission('pay:channel:query')") + public CommonResult getChannel( + @RequestParam Long merchantId, @RequestParam Long appId, @RequestParam String code) { + + // 獲取渠道 + PayChannelDO channel = channelService.getChannelByConditions(merchantId, appId, code); + if (channel == null) { + return success(new PayChannelRespVO()); + } + // 拼凑数据 + PayChannelRespVO respVo = PayChannelConvert.INSTANCE.convert(channel); + return success(respVo); + } +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelBaseVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelBaseVO.java new file mode 100644 index 000000000..42a98b972 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelBaseVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +/** +* 支付渠道 + Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayChannelBaseVO { + + @ApiModelProperty(value = "渠道编码", required = true) + @NotNull(message = "渠道编码不能为空") + private String code; + + @ApiModelProperty(value = "开启状态", required = true) + @NotNull(message = "开启状态不能为空") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "渠道费率,单位:百分比", required = true) + @NotNull(message = "渠道费率,单位:百分比不能为空") + private Double feeRate; + + @ApiModelProperty(value = "商户编号", required = true) + @NotNull(message = "商户编号不能为空") + private Long merchantId; + + @ApiModelProperty(value = "应用编号", required = true) + @NotNull(message = "应用编号不能为空") + private Long appId; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelCreateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelCreateReqVO.java new file mode 100644 index 000000000..a814b7b86 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelCreateReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotBlank; + +@ApiModel("支付渠道 创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelCreateReqVO extends PayChannelBaseVO { + + @ApiModelProperty(value = "通道配置的 json 字符串") + @NotBlank(message = "通道配置不能为空") + private String config; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExcelVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExcelVO.java new file mode 100644 index 000000000..a4618780a --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExcelVO.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; + +import com.alibaba.excel.annotation.ExcelProperty; + +/** + * 支付渠道 + Excel VO + * + * @author 芋艿 + */ +@Data +public class PayChannelExcelVO { + + @ExcelProperty("商户编号") + private Long id; + + @ExcelProperty("渠道编码") + private String code; + + @ExcelProperty("开启状态") + private Integer status; + + @ExcelProperty("备注") + private String remark; + + @ExcelProperty("渠道费率,单位:百分比") + private Double feeRate; + + @ExcelProperty("商户编号") + private Long merchantId; + + @ExcelProperty("应用编号") + private Long appId; + + /** + * todo @芋艿 mapStruct 存在转换问题 + * java: Can't map property "PayClientConfig payChannelDO.config" to "String payChannelExcelVO.config". + * Consider to declare/implement a mapping method: "String map(PayClientConfig value)". + */ + /// @ExcelProperty("支付渠道配置") + /// private String config; + + @ExcelProperty("创建时间") + private Date createTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExportReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExportReqVO.java new file mode 100644 index 000000000..29e3acdaf --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExportReqVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel(value = "支付渠道 Excel 导出 Request VO", description = "参数和 PayChannelPageReqVO 是一致的") +@Data +public class PayChannelExportReqVO { + + @ApiModelProperty(value = "渠道编码") + private String code; + + @ApiModelProperty(value = "开启状态") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "渠道费率,单位:百分比") + private Double feeRate; + + @ApiModelProperty(value = "商户编号") + private Long merchantId; + + @ApiModelProperty(value = "应用编号") + private Long appId; + + @ApiModelProperty(value = "支付渠道配置") + private String config; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelPageReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelPageReqVO.java new file mode 100644 index 000000000..83851f944 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelPageReqVO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel("支付渠道 分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelPageReqVO extends PageParam { + + @ApiModelProperty(value = "渠道编码") + private String code; + + @ApiModelProperty(value = "开启状态") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "渠道费率,单位:百分比") + private Double feeRate; + + @ApiModelProperty(value = "商户编号") + private Long merchantId; + + @ApiModelProperty(value = "应用编号") + private Long appId; + + @ApiModelProperty(value = "支付渠道配置") + private String config; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelRespVO.java new file mode 100644 index 000000000..02873b9f5 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelRespVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; + +@ApiModel("支付渠道 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelRespVO extends PayChannelBaseVO { + + @ApiModelProperty(value = "商户编号", required = true) + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + + @ApiModelProperty(value = "配置", required = true) + private String config; +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelUpdateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelUpdateReqVO.java new file mode 100644 index 000000000..fdefc7bad --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelUpdateReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +@ApiModel("支付渠道 更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelUpdateReqVO extends PayChannelBaseVO { + + @ApiModelProperty(value = "商户编号", required = true) + @NotNull(message = "商户编号不能为空") + private Long id; + + @ApiModelProperty(value = "通道配置的json字符串") + @NotBlank(message = "通道配置不能为空") + private String config; +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/PayMerchantController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/PayMerchantController.java new file mode 100644 index 000000000..b581e99ff --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/PayMerchantController.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.*; +import cn.iocoder.yudao.adminserver.modules.pay.convert.merchant.PayMerchantConvert; +import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Api(tags = "支付商户信息") +@RestController +@RequestMapping("/pay/merchant") +@Validated +public class PayMerchantController { + + @Resource + private PayMerchantService merchantService; + + @PostMapping("/create") + @ApiOperation("创建支付商户信息") + @PreAuthorize("@ss.hasPermission('pay:merchant:create')") + public CommonResult createMerchant(@Valid @RequestBody PayMerchantCreateReqVO createReqVO) { + return success(merchantService.createMerchant(createReqVO)); + } + + @PutMapping("/update") + @ApiOperation("更新支付商户信息") + @PreAuthorize("@ss.hasPermission('pay:merchant:update')") + public CommonResult updateMerchant(@Valid @RequestBody PayMerchantUpdateReqVO updateReqVO) { + merchantService.updateMerchant(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @ApiOperation("修改支付商户状态") + @PreAuthorize("@ss.hasPermission('pay:merchant:update')") + public CommonResult updateMerchantStatus(@Valid @RequestBody PayMerchantUpdateStatusReqVO reqVO) { + merchantService.updateMerchantStatus(reqVO.getId(), reqVO.getStatus()); + return success(true); + } + + @DeleteMapping("/delete") + @ApiOperation("删除支付商户信息") + @ApiImplicitParam(name = "id", value = "编号", required = true) + @PreAuthorize("@ss.hasPermission('pay:merchant:delete')") + public CommonResult deleteMerchant(@RequestParam("id") Long id) { + merchantService.deleteMerchant(id); + return success(true); + } + + @GetMapping("/get") + @ApiOperation("获得支付商户信息") + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('pay:merchant:query')") + public CommonResult getMerchant(@RequestParam("id") Long id) { + PayMerchantDO merchant = merchantService.getMerchant(id); + return success(PayMerchantConvert.INSTANCE.convert(merchant)); + } + + @GetMapping("/list-by-name") + @ApiOperation("根据商户名称获得支付商户信息列表") + @ApiImplicitParam(name = "name", value = "商户名称", example = "芋道", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('pay:merchant:query')") + public CommonResult> getMerchantListByName(@RequestParam(required = false) String name) { + List merchantListDO = merchantService.getMerchantListByName(name); + return success(PayMerchantConvert.INSTANCE.convertList(merchantListDO)); + } + + @GetMapping("/list") + @ApiOperation("获得支付商户信息列表") + @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class) + @PreAuthorize("@ss.hasPermission('pay:merchant:query')") + public CommonResult> getMerchantList(@RequestParam("ids") Collection ids) { + List list = merchantService.getMerchantList(ids); + return success(PayMerchantConvert.INSTANCE.convertList(list)); + } + + @GetMapping("/page") + @ApiOperation("获得支付商户信息分页") + @PreAuthorize("@ss.hasPermission('pay:merchant:query')") + public CommonResult> getMerchantPage(@Valid PayMerchantPageReqVO pageVO) { + PageResult pageResult = merchantService.getMerchantPage(pageVO); + return success(PayMerchantConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/export-excel") + @ApiOperation("导出支付商户信息 Excel") + @PreAuthorize("@ss.hasPermission('pay:merchant:export')") + @OperateLog(type = EXPORT) + public void exportMerchantExcel(@Valid PayMerchantExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = merchantService.getMerchantList(exportReqVO); + // 导出 Excel + List datas = PayMerchantConvert.INSTANCE.convertList02(list); + ExcelUtils.write(response, "支付商户信息.xls", "数据", PayMerchantExcelVO.class, datas); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantBaseVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantBaseVO.java new file mode 100644 index 000000000..ed7b69a3f --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantBaseVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** +* 支付商户信息 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayMerchantBaseVO { + + @ApiModelProperty(value = "商户全称", required = true) + @NotNull(message = "商户全称不能为空") + private String name; + + @ApiModelProperty(value = "商户简称", required = true) + @NotNull(message = "商户简称不能为空") + private String shortName; + + @ApiModelProperty(value = "开启状态", required = true) + @NotNull(message = "开启状态不能为空") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantCreateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantCreateReqVO.java new file mode 100644 index 000000000..2128de88b --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +@ApiModel("支付商户信息创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayMerchantCreateReqVO extends PayMerchantBaseVO { + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExcelVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExcelVO.java new file mode 100644 index 000000000..cb229fb11 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExcelVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 支付商户信息 Excel VO + * + * @author 芋艿 + */ +@Data +public class PayMerchantExcelVO { + + @ExcelProperty("商户编号") + private Long id; + + @ExcelProperty("商户号") + private String no; + + @ExcelProperty("商户全称") + private String name; + + @ExcelProperty("商户简称") + private String shortName; + + @ExcelProperty(value = "开启状态",converter = DictConvert.class) + @DictFormat("sys_common_status") + private Integer status; + + @ExcelProperty("备注") + private String remark; + + @ExcelProperty("创建时间") + private Date createTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExportReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExportReqVO.java new file mode 100644 index 000000000..c6c322282 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExportReqVO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel(value = "支付商户信息 Excel 导出 Request VO", description = "参数和 PayMerchantPageReqVO 是一致的") +@Data +public class PayMerchantExportReqVO { + + @ApiModelProperty(value = "商户号") + private String no; + + @ApiModelProperty(value = "商户全称") + private String name; + + @ApiModelProperty(value = "商户简称") + private String shortName; + + @ApiModelProperty(value = "开启状态") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantPageReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantPageReqVO.java new file mode 100644 index 000000000..8a66ac953 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantPageReqVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel("支付商户信息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayMerchantPageReqVO extends PageParam { + + @ApiModelProperty(value = "商户号") + private String no; + + @ApiModelProperty(value = "商户全称") + private String name; + + @ApiModelProperty(value = "商户简称") + private String shortName; + + @ApiModelProperty(value = "开启状态") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantRespVO.java new file mode 100644 index 000000000..d5b194359 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantRespVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +@ApiModel("支付商户信息 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayMerchantRespVO extends PayMerchantBaseVO { + + @ApiModelProperty(value = "商户编号", required = true) + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + + /** + * 商户号 + * 例如说,M233666999 + * 只有新增时插入,不允许修改 + */ + private String no; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateReqVO.java new file mode 100644 index 000000000..031d34fd5 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import lombok.*; +import java.util.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +@ApiModel("支付商户信息更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayMerchantUpdateReqVO extends PayMerchantBaseVO { + + @ApiModelProperty(value = "商户编号", required = true) + @NotNull(message = "商户编号不能为空") + private Long id; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateStatusReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateStatusReqVO.java new file mode 100644 index 000000000..635876954 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateStatusReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@ApiModel("商户更新状态 Request VO") +@Data +public class PayMerchantUpdateStatusReqVO { + + @ApiModelProperty(value = "商户编号", required = true, example = "1024") + @NotNull(message = "商户编号不能为空") + private Long id; + + @ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 SysCommonStatusEnum 枚举") + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayOrderController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayOrderController.java new file mode 100755 index 000000000..2ac44c66e --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayOrderController.java @@ -0,0 +1,177 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.*; +import cn.iocoder.yudao.adminserver.modules.pay.convert.order.PayOrderConvert; +import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService; +import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderExtensionService; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; +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.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +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 javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 支付订单 controller 组件 + * + * @author aquan + */ +@Api(tags = "支付订单") +@RestController +@RequestMapping("/pay/order") +@Validated +public class PayOrderController { + + /** + * 订单 service 组件 + */ + @Resource + private PayOrderService orderService; + + /** + * 订单扩展 service 组件 + */ + @Resource + private PayOrderExtensionService orderExtensionService; + + /** + * 商户 service 组件 + */ + @Resource + private PayMerchantService merchantService; + + /** + * 应用 service 组件 + */ + @Resource + private PayAppService appService; + + @GetMapping("/get") + @ApiOperation("获得支付订单") + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult getOrder(@RequestParam("id") Long id) { + PayOrderDO order = orderService.getOrder(id); + if (ObjectUtil.isNull(order)) { + return success(new PayOrderDetailsRespVO()); + } + + PayMerchantDO merchantDO = merchantService.getMerchant(order.getMerchantId()); + PayAppDO appDO = appService.getApp(order.getAppId()); + PayChannelEnum channelEnum = PayChannelEnum.getByCode(order.getChannelCode()); + + PayOrderDetailsRespVO respVO = PayOrderConvert.INSTANCE.orderDetailConvert(order); + respVO.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户"); + respVO.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用"); + respVO.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道"); + + PayOrderExtensionDO extensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId()); + if (ObjectUtil.isNotNull(extensionDO)) { + respVO.setPayOrderExtension(PayOrderConvert.INSTANCE.orderDetailExtensionConvert(extensionDO)); + } + + return success(respVO); + } + + @GetMapping("/page") + @ApiOperation("获得支付订单分页") + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult> getOrderPage(@Valid PayOrderPageReqVO pageVO) { + PageResult pageResult = orderService.getOrderPage(pageVO); + if (CollectionUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 处理商户ID数据 + Map merchantMap = merchantService.getMerchantMap( + CollectionUtils.convertList(pageResult.getList(), PayOrderDO::getMerchantId)); + // 处理应用ID数据 + Map appMap = appService.getAppMap( + CollectionUtils.convertList(pageResult.getList(), PayOrderDO::getAppId)); + + List pageList = new ArrayList<>(pageResult.getList().size()); + pageResult.getList().forEach(c -> { + PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId()); + PayAppDO appDO = appMap.get(c.getAppId()); + PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode()); + + PayOrderPageItemRespVO orderItem = PayOrderConvert.INSTANCE.pageConvertItemPage(c); + orderItem.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户"); + orderItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用"); + orderItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道"); + pageList.add(orderItem); + }); + return success(new PageResult<>(pageList, pageResult.getTotal())); + } + + @GetMapping("/export-excel") + @ApiOperation("导出支付订单Excel") + @PreAuthorize("@ss.hasPermission('pay:order:export')") + @OperateLog(type = EXPORT) + public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + + List list = orderService.getOrderList(exportReqVO); + if (CollectionUtil.isEmpty(list)) { + ExcelUtils.write(response, "支付订单.xls", "数据", + PayOrderExcelVO.class, new ArrayList<>()); + } + + // 处理商户ID数据 + Map merchantMap = merchantService.getMerchantMap( + CollectionUtils.convertList(list, PayOrderDO::getMerchantId)); + // 处理应用ID数据 + Map appMap = appService.getAppMap( + CollectionUtils.convertList(list, PayOrderDO::getAppId)); + // 处理扩展订单数据 + Map orderExtensionMap = orderExtensionService + .getOrderExtensionMap(CollectionUtils.convertList(list, PayOrderDO::getSuccessExtensionId)); + + List excelDatum = new ArrayList<>(list.size()); + list.forEach(c -> { + PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId()); + PayAppDO appDO = appMap.get(c.getAppId()); + PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode()); + PayOrderExtensionDO orderExtensionDO = orderExtensionMap.get(c.getSuccessExtensionId()); + + PayOrderExcelVO excelItem = PayOrderConvert.INSTANCE.excelConvert(c); + excelItem.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户"); + excelItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用"); + excelItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道"); + excelItem.setNo(ObjectUtil.isNotNull(orderExtensionDO) ? orderExtensionDO.getNo() : ""); + excelDatum.add(excelItem); + }); + + // 导出 Excel + ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelDatum); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayRefundController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayRefundController.java new file mode 100755 index 000000000..912d35254 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayRefundController.java @@ -0,0 +1,174 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.*; +import cn.iocoder.yudao.adminserver.modules.pay.convert.refund.PayRefundConvert; +import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService; +import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderService; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayRefundService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +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.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +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 javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 退款订单 Controller 组件 + * + * @author aquan + */ +@Api(tags = "退款订单") +@RestController +@RequestMapping("/pay/refund") +@Validated +public class PayRefundController { + + @Resource + private PayRefundService refundService; + + /** + * 商户 service 组件 + */ + @Resource + private PayMerchantService merchantService; + + /** + * 应用 service 组件 + */ + @Resource + private PayAppService appService; + + /** + * 订单 service 组件 + */ + @Resource + private PayOrderService orderService; + + + @GetMapping("/get") + @ApiOperation("获得退款订单") + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('pay:refund:query')") + public CommonResult getRefund(@RequestParam("id") Long id) { + PayRefundDO refund = refundService.getRefund(id); + if (ObjectUtil.isNull(refund)) { + return success(new PayRefundDetailsRespVO()); + } + + PayMerchantDO merchantDO = merchantService.getMerchant(refund.getMerchantId()); + PayAppDO appDO = appService.getApp(refund.getAppId()); + PayChannelEnum channelEnum = PayChannelEnum.getByCode(refund.getChannelCode()); + PayOrderDO orderDO = orderService.getOrder(refund.getOrderId()); + + PayRefundDetailsRespVO refundDetail = PayRefundConvert.INSTANCE.refundDetailConvert(refund); + refundDetail.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户"); + refundDetail.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用"); + refundDetail.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道"); + refundDetail.setSubject(orderDO.getSubject()); + + return success(refundDetail); + } + + @GetMapping("/page") + @ApiOperation("获得退款订单分页") + @PreAuthorize("@ss.hasPermission('pay:refund:query')") + public CommonResult> getRefundPage(@Valid PayRefundPageReqVO pageVO) { + PageResult pageResult = refundService.getRefundPage(pageVO); + if (CollectionUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 处理商户ID数据 + Map merchantMap = merchantService.getMerchantMap( + CollectionUtils.convertList(pageResult.getList(), PayRefundDO::getMerchantId)); + // 处理应用ID数据 + Map appMap = appService.getAppMap( + CollectionUtils.convertList(pageResult.getList(), PayRefundDO::getAppId)); + List list = new ArrayList<>(pageResult.getList().size()); + pageResult.getList().forEach(c -> { + PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId()); + PayAppDO appDO = appMap.get(c.getAppId()); + PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode()); + + PayRefundPageItemRespVO item = PayRefundConvert.INSTANCE.pageItemConvert(c); + + item.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户"); + item.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用"); + item.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道"); + list.add(item); + }); + + return success(new PageResult<>(list, pageResult.getTotal())); + } + + @GetMapping("/export-excel") + @ApiOperation("导出退款订单 Excel") + @PreAuthorize("@ss.hasPermission('pay:refund:export')") + @OperateLog(type = EXPORT) + public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + + List list = refundService.getRefundList(exportReqVO); + if (CollectionUtil.isEmpty(list)) { + ExcelUtils.write(response, "退款订单.xls", "数据", + PayRefundExcelVO.class, new ArrayList<>()); + } + + // 处理商户ID数据 + Map merchantMap = merchantService.getMerchantMap( + CollectionUtils.convertList(list, PayRefundDO::getMerchantId)); + // 处理应用ID数据 + Map appMap = appService.getAppMap( + CollectionUtils.convertList(list, PayRefundDO::getAppId)); + + List excelDatum = new ArrayList<>(list.size()); + // 处理商品名称数据 + Map orderMap = orderService.getOrderSubjectMap( + CollectionUtils.convertList(list, PayRefundDO::getOrderId)); + + list.forEach(c -> { + PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId()); + PayAppDO appDO = appMap.get(c.getAppId()); + PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode()); + + PayRefundExcelVO excelItem = PayRefundConvert.INSTANCE.excelConvert(c); + excelItem.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户"); + excelItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用"); + excelItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道"); + excelItem.setSubject(orderMap.get(c.getOrderId()).getSubject()); + + excelDatum.add(excelItem); + }); + + // 导出 Excel + ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelDatum); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderBaseVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderBaseVO.java new file mode 100755 index 000000000..320125a40 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderBaseVO.java @@ -0,0 +1,108 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotNull; +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 支付订单 + * Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @author aquan + */ +@Data +public class PayOrderBaseVO { + + @ApiModelProperty(value = "商户编号", required = true) + @NotNull(message = "商户编号不能为空") + private Long merchantId; + + @ApiModelProperty(value = "应用编号", required = true) + @NotNull(message = "应用编号不能为空") + private Long appId; + + @ApiModelProperty(value = "渠道编号") + private Long channelId; + + @ApiModelProperty(value = "渠道编码") + private String channelCode; + + @ApiModelProperty(value = "商户订单编号", required = true) + @NotNull(message = "商户订单编号不能为空") + private String merchantOrderId; + + @ApiModelProperty(value = "商品标题", required = true) + @NotNull(message = "商品标题不能为空") + private String subject; + + @ApiModelProperty(value = "商品描述", required = true) + @NotNull(message = "商品描述不能为空") + private String body; + + @ApiModelProperty(value = "异步通知地址", required = true) + @NotNull(message = "异步通知地址不能为空") + private String notifyUrl; + + @ApiModelProperty(value = "通知商户支付结果的回调状态", required = true) + @NotNull(message = "通知商户支付结果的回调状态不能为空") + private Integer notifyStatus; + + @ApiModelProperty(value = "支付金额,单位:分", required = true) + @NotNull(message = "支付金额,单位:分不能为空") + private Long amount; + + @ApiModelProperty(value = "渠道手续费,单位:百分比") + private Double channelFeeRate; + + @ApiModelProperty(value = "渠道手续金额,单位:分") + private Long channelFeeAmount; + + @ApiModelProperty(value = "支付状态", required = true) + @NotNull(message = "支付状态不能为空") + private Integer status; + + @ApiModelProperty(value = "用户 IP", required = true) + @NotNull(message = "用户 IP不能为空") + private String userIp; + + @ApiModelProperty(value = "订单失效时间", required = true) + @NotNull(message = "订单失效时间不能为空") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private Date expireTime; + + @ApiModelProperty(value = "订单支付成功时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private Date successTime; + + @ApiModelProperty(value = "订单支付通知时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private Date notifyTime; + + @ApiModelProperty(value = "支付成功的订单拓展单编号") + private Long successExtensionId; + + @ApiModelProperty(value = "退款状态", required = true) + @NotNull(message = "退款状态不能为空") + private Integer refundStatus; + + @ApiModelProperty(value = "退款次数", required = true) + @NotNull(message = "退款次数不能为空") + private Integer refundTimes; + + @ApiModelProperty(value = "退款总金额,单位:分", required = true) + @NotNull(message = "退款总金额,单位:分不能为空") + private Long refundAmount; + + @ApiModelProperty(value = "渠道用户编号") + private String channelUserId; + + @ApiModelProperty(value = "渠道订单号") + private String channelOrderNo; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderDetailsRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderDetailsRespVO.java new file mode 100644 index 000000000..d85a9bcb1 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderDetailsRespVO.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +/** + * 支付订单详细信息,由支付订单和支付订单扩展信息组成 + * + * @author aquan + */ +@ApiModel("支付订单详细信息 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderDetailsRespVO extends PayOrderBaseVO { + + @ApiModelProperty(value = "支付订单编号") + private Long id; + + @ApiModelProperty(value = "商户名称") + private String merchantName; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "渠道编号名称") + private String channelCodeName; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + /** + * 支付订单扩展 + */ + private PayOrderExtension payOrderExtension; + + @Data + @ApiModel("支付订单扩展") + public static class PayOrderExtension { + + @ApiModelProperty(value = "支付订单号") + private String no; + + @ApiModelProperty(value = "支付异步通知的内容") + private String channelNotifyData; + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExcelVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExcelVO.java new file mode 100755 index 000000000..48d69c490 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExcelVO.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order; + +import cn.iocoder.yudao.adminserver.modules.system.enums.SysDictTypeConstants; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 支付订单Excel VO + * + * @author aquan + */ +@Data +public class PayOrderExcelVO { + + @ExcelProperty("支付订单编号") + private Long id; + + @ExcelProperty(value = "商户名称") + private String merchantName; + + @ExcelProperty(value = "应用名称") + private String appName; + + @ExcelProperty("商品标题") + private String subject; + + @ExcelProperty("商户订单编号") + private String merchantOrderId; + + @ExcelProperty("渠道订单号") + private String channelOrderNo; + + @ExcelProperty(value = "支付订单号") + private String no; + + @ExcelProperty("支付金额,单位:元") + private String amount; + + @ExcelProperty("渠道手续金额,单位:元") + private String channelFeeAmount; + + @ExcelProperty("渠道手续费,单位:百分比") + private String channelFeeRate; + + @DictFormat(SysDictTypeConstants.PAY_ORDER_STATUS) + @ExcelProperty(value = "支付状态", converter = DictConvert.class) + private Integer status; + + @DictFormat(SysDictTypeConstants.PAY_ORDER_NOTIFY_STATUS) + @ExcelProperty(value = "通知商户支付结果的回调状态", converter = DictConvert.class) + private Integer notifyStatus; + + @ExcelProperty("异步通知地址") + private String notifyUrl; + + @ExcelProperty("创建时间") + private Date createTime; + + @ExcelProperty("订单支付成功时间") + private Date successTime; + + @ExcelProperty("订单失效时间") + private Date expireTime; + + @ExcelProperty("订单支付通知时间") + private Date notifyTime; + + @ExcelProperty(value = "渠道编号名称") + private String channelCodeName; + + @ExcelProperty("用户 IP") + private String userIp; + + @DictFormat(SysDictTypeConstants.PAY_ORDER_REFUND_STATUS) + @ExcelProperty(value = "退款状态", converter = DictConvert.class) + private Integer refundStatus; + + @ExcelProperty("退款次数") + private Integer refundTimes; + + @ExcelProperty("退款总金额,单位:元") + private String refundAmount; + + @ExcelProperty("商品描述") + private String body; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExportReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExportReqVO.java new file mode 100755 index 000000000..3445c86b9 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExportReqVO.java @@ -0,0 +1,112 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 支付订单 Excel 导出 Request VO + * @author aquan + */ +@ApiModel(value = "支付订单 Excel 导出 Request VO", description = "参数和 PayOrderPageReqVO 是一致的") +@Data +public class PayOrderExportReqVO { + + @ApiModelProperty(value = "商户编号") + private Long merchantId; + + @ApiModelProperty(value = "应用编号") + private Long appId; + + @ApiModelProperty(value = "渠道编号") + private Long channelId; + + @ApiModelProperty(value = "渠道编码") + private String channelCode; + + @ApiModelProperty(value = "商户订单编号") + private String merchantOrderId; + + @ApiModelProperty(value = "商品标题") + private String subject; + + @ApiModelProperty(value = "商品描述") + private String body; + + @ApiModelProperty(value = "异步通知地址") + private String notifyUrl; + + @ApiModelProperty(value = "通知商户支付结果的回调状态") + private Integer notifyStatus; + + @ApiModelProperty(value = "支付金额,单位:分") + private Long amount; + + @ApiModelProperty(value = "渠道手续费,单位:百分比") + private Double channelFeeRate; + + @ApiModelProperty(value = "渠道手续金额,单位:分") + private Long channelFeeAmount; + + @ApiModelProperty(value = "支付状态") + private Integer status; + + @ApiModelProperty(value = "用户 IP") + private String userIp; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始订单失效时间") + private Date beginExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束订单失效时间") + private Date endExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始订单支付成功时间") + private Date beginSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束订单支付成功时间") + private Date endSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始订单支付通知时间") + private Date beginNotifyTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束订单支付通知时间") + private Date endNotifyTime; + + @ApiModelProperty(value = "支付成功的订单拓展单编号") + private Long successExtensionId; + + @ApiModelProperty(value = "退款状态") + private Integer refundStatus; + + @ApiModelProperty(value = "退款次数") + private Integer refundTimes; + + @ApiModelProperty(value = "退款总金额,单位:分") + private Long refundAmount; + + @ApiModelProperty(value = "渠道用户编号") + private String channelUserId; + + @ApiModelProperty(value = "渠道订单号") + private String channelOrderNo; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageItemRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageItemRespVO.java new file mode 100755 index 000000000..b24b27a47 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageItemRespVO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +/** + * 支付订单分页 Request VO + * + * @author aquan + */ +@ApiModel("支付订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderPageItemRespVO extends PayOrderBaseVO { + + @ApiModelProperty(value = "支付订单编号", required = true) + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + + @ApiModelProperty(value = "商户名称") + private String merchantName; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "渠道名称") + private String channelCodeName; + + @ApiModelProperty(value = "支付订单号") + private String no; + + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageReqVO.java new file mode 100755 index 000000000..07fa6a820 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageReqVO.java @@ -0,0 +1,118 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 支付订单分页 Request VO + * + * @author aquan + */ +@ApiModel("支付订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderPageReqVO extends PageParam { + + @ApiModelProperty(value = "商户编号") + private Long merchantId; + + @ApiModelProperty(value = "应用编号") + private Long appId; + + @ApiModelProperty(value = "渠道编号") + private Long channelId; + + @ApiModelProperty(value = "渠道编码") + private String channelCode; + + @ApiModelProperty(value = "商户订单编号") + private String merchantOrderId; + + @ApiModelProperty(value = "商品标题") + private String subject; + + @ApiModelProperty(value = "商品描述") + private String body; + + @ApiModelProperty(value = "异步通知地址") + private String notifyUrl; + + @ApiModelProperty(value = "通知商户支付结果的回调状态") + private Integer notifyStatus; + + @ApiModelProperty(value = "支付金额,单位:分") + private Long amount; + + @ApiModelProperty(value = "渠道手续费,单位:百分比") + private Double channelFeeRate; + + @ApiModelProperty(value = "渠道手续金额,单位:分") + private Long channelFeeAmount; + + @ApiModelProperty(value = "支付状态") + private Integer status; + + @ApiModelProperty(value = "用户 IP") + private String userIp; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始订单失效时间") + private Date beginExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束订单失效时间") + private Date endExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始订单支付成功时间") + private Date beginSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束订单支付成功时间") + private Date endSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始订单支付通知时间") + private Date beginNotifyTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束订单支付通知时间") + private Date endNotifyTime; + + @ApiModelProperty(value = "支付成功的订单拓展单编号") + private Long successExtensionId; + + @ApiModelProperty(value = "退款状态") + private Integer refundStatus; + + @ApiModelProperty(value = "退款次数") + private Integer refundTimes; + + @ApiModelProperty(value = "退款总金额,单位:分") + private Long refundAmount; + + @ApiModelProperty(value = "渠道用户编号") + private String channelUserId; + + @ApiModelProperty(value = "渠道订单号") + private String channelOrderNo; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderRespVO.java new file mode 100755 index 000000000..c46693635 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderRespVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +/** + * 支付订单 Response VO + * + * @author aquan + */ +@ApiModel("支付订单 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderRespVO extends PayOrderBaseVO { + + @ApiModelProperty(value = "支付订单编号", required = true) + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundBaseVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundBaseVO.java new file mode 100755 index 000000000..c8421b035 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundBaseVO.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotNull; +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** +* 退款订单 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayRefundBaseVO { + + @ApiModelProperty(value = "商户编号", required = true) + @NotNull(message = "商户编号不能为空") + private Long merchantId; + + @ApiModelProperty(value = "应用编号", required = true) + @NotNull(message = "应用编号不能为空") + private Long appId; + + @ApiModelProperty(value = "渠道编号", required = true) + @NotNull(message = "渠道编号不能为空") + private Long channelId; + + @ApiModelProperty(value = "渠道编码", required = true) + @NotNull(message = "渠道编码不能为空") + private String channelCode; + + @ApiModelProperty(value = "支付订单编号 pay_order 表id", required = true) + @NotNull(message = "支付订单编号 pay_order 表id不能为空") + private Long orderId; + + @ApiModelProperty(value = "交易订单号 pay_extension 表no 字段", required = true) + @NotNull(message = "交易订单号 pay_extension 表no 字段不能为空") + private String tradeNo; + + @ApiModelProperty(value = "商户订单编号(商户系统生成)", required = true) + @NotNull(message = "商户订单编号(商户系统生成)不能为空") + private String merchantOrderId; + + @ApiModelProperty(value = "商户退款订单号(商户系统生成)", required = true) + @NotNull(message = "商户退款订单号(商户系统生成)不能为空") + private String merchantRefundNo; + + @ApiModelProperty(value = "异步通知商户地址", required = true) + @NotNull(message = "异步通知商户地址不能为空") + private String notifyUrl; + + @ApiModelProperty(value = "通知商户退款结果的回调状态", required = true) + @NotNull(message = "通知商户退款结果的回调状态不能为空") + private Integer notifyStatus; + + @ApiModelProperty(value = "退款状态", required = true) + @NotNull(message = "退款状态不能为空") + private Integer status; + + @ApiModelProperty(value = "退款类型(部分退款,全部退款)", required = true) + @NotNull(message = "退款类型(部分退款,全部退款)不能为空") + private Integer type; + + @ApiModelProperty(value = "支付金额,单位分", required = true) + @NotNull(message = "支付金额,单位分不能为空") + private Long payAmount; + + @ApiModelProperty(value = "退款金额,单位分", required = true) + @NotNull(message = "退款金额,单位分不能为空") + private Long refundAmount; + + @ApiModelProperty(value = "退款原因", required = true) + @NotNull(message = "退款原因不能为空") + private String reason; + + @ApiModelProperty(value = "用户 IP") + private String userIp; + + @ApiModelProperty(value = "渠道订单号,pay_order 中的channel_order_no 对应", required = true) + @NotNull(message = "渠道订单号,pay_order 中的channel_order_no 对应不能为空") + private String channelOrderNo; + + @ApiModelProperty(value = "渠道退款单号,渠道返回") + private String channelRefundNo; + + @ApiModelProperty(value = "渠道调用报错时,错误码") + private String channelErrorCode; + + @ApiModelProperty(value = "渠道调用报错时,错误信息") + private String channelErrorMsg; + + @ApiModelProperty(value = "支付渠道的额外参数") + private String channelExtras; + + @ApiModelProperty(value = "退款失效时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private Date expireTime; + + @ApiModelProperty(value = "退款成功时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private Date successTime; + + @ApiModelProperty(value = "退款通知时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private Date notifyTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundCreateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundCreateReqVO.java new file mode 100755 index 000000000..79b05c625 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundCreateReqVO.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import lombok.*; +import io.swagger.annotations.*; + +@ApiModel("退款订单创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundCreateReqVO extends PayRefundBaseVO { + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundDetailsRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundDetailsRespVO.java new file mode 100755 index 000000000..d1d6739ee --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundDetailsRespVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; +import java.util.Date; + +/** + * 退款订单详情 Response VO + * + * @author aquan + */ +@ApiModel("退款订单详情 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundDetailsRespVO extends PayRefundBaseVO { + + @ApiModelProperty(value = "支付退款编号", required = true) + private Long id; + + @ApiModelProperty(value = "商户名称") + private String merchantName; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "渠道编号名称") + private String channelCodeName; + + @NotNull(message = "商品标题不能为空") + private String subject; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "更新时间") + private Date updateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExcelVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExcelVO.java new file mode 100755 index 000000000..be90f1a61 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExcelVO.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import cn.iocoder.yudao.adminserver.modules.system.enums.SysDictTypeConstants; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 退款订单 Excel VO + * + * @author aquan + */ +@Data +public class PayRefundExcelVO { + + @ExcelProperty("支付退款编号") + private Long id; + + @ExcelProperty("商品名称") + private String subject; + + @ExcelProperty(value = "商户名称") + private String merchantName; + + @ExcelProperty(value = "应用名称") + private String appName; + + @ExcelProperty(value = "渠道编号名称") + private String channelCodeName; + + @ExcelProperty("交易订单号") + private String tradeNo; + + @ExcelProperty("商户订单编号") + private String merchantOrderId; + + @ExcelProperty("商户退款订单号") + private String merchantRefundNo; + + @ExcelProperty("异步通知商户地址") + private String notifyUrl; + + @DictFormat(SysDictTypeConstants.PAY_ORDER_NOTIFY_STATUS) + @ExcelProperty(value = "商户退款结果回调状态", converter = DictConvert.class) + private Integer notifyStatus; + + @DictFormat(SysDictTypeConstants.PAY_REFUND_ORDER_STATUS) + @ExcelProperty(value = "退款状态", converter = DictConvert.class) + private Integer status; + + @DictFormat(SysDictTypeConstants.PAY_REFUND_ORDER_TYPE) + @ExcelProperty(value = "退款类型", converter = DictConvert.class) + private Integer type; + + @ExcelProperty("支付金额,单位:元") + private String payAmount; + + @ExcelProperty("退款金额,单位:元") + private String refundAmount; + + @ExcelProperty("退款原因") + private String reason; + + @ExcelProperty("用户付款 IP") + private String userIp; + + @ExcelProperty("渠道订单号") + private String channelOrderNo; + + @ExcelProperty("渠道退款单号") + private String channelRefundNo; + + @ExcelProperty("创建时间") + private Date createTime; + + @ExcelProperty("退款成功时间") + private Date successTime; + + @ExcelProperty("退款通知时间") + private Date notifyTime; + + @ExcelProperty("退款失效时间") + private Date expireTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExportReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExportReqVO.java new file mode 100755 index 000000000..5a0e9c4e0 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExportReqVO.java @@ -0,0 +1,111 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel(value = "退款订单 Excel 导出 Request VO", description = "参数和 PayRefundPageReqVO 是一致的") +@Data +public class PayRefundExportReqVO { + + @ApiModelProperty(value = "商户编号") + private Long merchantId; + + @ApiModelProperty(value = "应用编号") + private Long appId; + + @ApiModelProperty(value = "渠道编号") + private Long channelId; + + @ApiModelProperty(value = "渠道编码") + private String channelCode; + + @ApiModelProperty(value = "支付订单编号 pay_order 表id") + private Long orderId; + + @ApiModelProperty(value = "交易订单号 pay_extension 表no 字段") + private String tradeNo; + + @ApiModelProperty(value = "商户订单编号(商户系统生成)") + private String merchantOrderId; + + @ApiModelProperty(value = "商户退款订单号(商户系统生成)") + private String merchantRefundNo; + + @ApiModelProperty(value = "异步通知商户地址") + private String notifyUrl; + + @ApiModelProperty(value = "通知商户退款结果的回调状态") + private Integer notifyStatus; + + @ApiModelProperty(value = "退款状态") + private Integer status; + + @ApiModelProperty(value = "退款类型(部分退款,全部退款)") + private Integer type; + + @ApiModelProperty(value = "支付金额,单位分") + private Long payAmount; + + @ApiModelProperty(value = "退款金额,单位分") + private Long refundAmount; + + @ApiModelProperty(value = "退款原因") + private String reason; + + @ApiModelProperty(value = "用户 IP") + private String userIp; + + @ApiModelProperty(value = "渠道订单号,pay_order 中的channel_order_no 对应") + private String channelOrderNo; + + @ApiModelProperty(value = "渠道退款单号,渠道返回") + private String channelRefundNo; + + @ApiModelProperty(value = "渠道调用报错时,错误码") + private String channelErrorCode; + + @ApiModelProperty(value = "渠道调用报错时,错误信息") + private String channelErrorMsg; + + @ApiModelProperty(value = "支付渠道的额外参数") + private String channelExtras; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始退款失效时间") + private Date beginExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束退款失效时间") + private Date endExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始退款成功时间") + private Date beginSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束退款成功时间") + private Date endSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始退款通知时间") + private Date beginNotifyTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束退款通知时间") + private Date endNotifyTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageItemRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageItemRespVO.java new file mode 100755 index 000000000..69e881bce --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageItemRespVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +/** + * 退款订单分页查询 Response VO + * @author aquan + */ +@ApiModel("退款订单分页查询 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundPageItemRespVO extends PayRefundBaseVO { + + @ApiModelProperty(value = "支付订单编号", required = true) + private Long id; + + @ApiModelProperty(value = "商户名称") + private String merchantName; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "渠道名称") + private String channelCodeName; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageReqVO.java new file mode 100755 index 000000000..103a7a27c --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageReqVO.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel("退款订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundPageReqVO extends PageParam { + + @ApiModelProperty(value = "商户编号") + private Long merchantId; + + @ApiModelProperty(value = "应用编号") + private Long appId; + + @ApiModelProperty(value = "渠道编号") + private Long channelId; + + @ApiModelProperty(value = "渠道编码") + private String channelCode; + + @ApiModelProperty(value = "支付订单编号 pay_order 表id") + private Long orderId; + + @ApiModelProperty(value = "交易订单号 pay_extension 表no 字段") + private String tradeNo; + + @ApiModelProperty(value = "商户订单编号(商户系统生成)") + private String merchantOrderId; + + @ApiModelProperty(value = "商户退款订单号(商户系统生成)") + private String merchantRefundNo; + + @ApiModelProperty(value = "异步通知商户地址") + private String notifyUrl; + + @ApiModelProperty(value = "通知商户退款结果的回调状态") + private Integer notifyStatus; + + @ApiModelProperty(value = "退款状态") + private Integer status; + + @ApiModelProperty(value = "退款类型(部分退款,全部退款)") + private Integer type; + + @ApiModelProperty(value = "支付金额,单位分") + private Long payAmount; + + @ApiModelProperty(value = "退款金额,单位分") + private Long refundAmount; + + @ApiModelProperty(value = "退款原因") + private String reason; + + @ApiModelProperty(value = "用户 IP") + private String userIp; + + @ApiModelProperty(value = "渠道订单号,pay_order 中的channel_order_no 对应") + private String channelOrderNo; + + @ApiModelProperty(value = "渠道退款单号,渠道返回") + private String channelRefundNo; + + @ApiModelProperty(value = "渠道调用报错时,错误码") + private String channelErrorCode; + + @ApiModelProperty(value = "渠道调用报错时,错误信息") + private String channelErrorMsg; + + @ApiModelProperty(value = "支付渠道的额外参数") + private String channelExtras; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始退款失效时间") + private Date beginExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束退款失效时间") + private Date endExpireTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始退款成功时间") + private Date beginSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束退款成功时间") + private Date endSuccessTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始退款通知时间") + private Date beginNotifyTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束退款通知时间") + private Date endNotifyTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundRespVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundRespVO.java new file mode 100755 index 000000000..901a0fe85 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundRespVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +@ApiModel("退款订单 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundRespVO extends PayRefundBaseVO { + + @ApiModelProperty(value = "支付退款编号", required = true) + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundUpdateReqVO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundUpdateReqVO.java new file mode 100755 index 000000000..8cb2dcf67 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundUpdateReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo; + +import lombok.*; +import io.swagger.annotations.*; +import javax.validation.constraints.*; + +@ApiModel("退款订单更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundUpdateReqVO extends PayRefundBaseVO { + + @ApiModelProperty(value = "支付退款编号", required = true) + @NotNull(message = "支付退款编号不能为空") + private Long id; + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/app/PayAppConvert.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/app/PayAppConvert.java new file mode 100644 index 000000000..15da37cbf --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/app/PayAppConvert.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.adminserver.modules.pay.convert.app; + +import java.util.*; + +import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.SysUserPageItemRespVO; +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.*; + +/** + * 支付应用信息 Convert + * + * @author 芋艿 + */ +@Mapper +public interface PayAppConvert { + + PayAppConvert INSTANCE = Mappers.getMapper(PayAppConvert.class); + + PayAppPageItemRespVO pageConvert (PayAppDO bean); + + PayAppPageItemRespVO.PayMerchant convert(PayMerchantDO bean); + + PayAppDO convert(PayAppCreateReqVO bean); + + PayAppDO convert(PayAppUpdateReqVO bean); + + PayAppRespVO convert(PayAppDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/channel/PayChannelConvert.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/channel/PayChannelConvert.java new file mode 100644 index 000000000..ee0473c23 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/channel/PayChannelConvert.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.adminserver.modules.pay.convert.channel; + +import java.util.*; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*; + +/** + * 支付渠道 + Convert + * + * @author 芋艿 + */ +@Mapper +public interface PayChannelConvert { + + PayChannelConvert INSTANCE = Mappers.getMapper(PayChannelConvert.class); + + @Mapping(target = "config",ignore = true) + PayChannelDO convert(PayChannelCreateReqVO bean); + + @Mapping(target = "config",ignore = true) + PayChannelDO convert(PayChannelUpdateReqVO bean); + + @Mapping(target = "config",expression = "java(cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString(bean.getConfig()))") + PayChannelRespVO convert(PayChannelDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + + + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/merchant/PayMerchantConvert.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/merchant/PayMerchantConvert.java new file mode 100644 index 000000000..9483ff120 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/merchant/PayMerchantConvert.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.adminserver.modules.pay.convert.merchant; + +import java.util.*; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.*; + +/** + * 支付商户信息 Convert + * + * @author 芋艿 + */ +@Mapper +public interface PayMerchantConvert { + + PayMerchantConvert INSTANCE = Mappers.getMapper(PayMerchantConvert.class); + + PayMerchantDO convert(PayMerchantCreateReqVO bean); + + PayMerchantDO convert(PayMerchantUpdateReqVO bean); + + PayMerchantRespVO convert(PayMerchantDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/order/PayOrderConvert.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/order/PayOrderConvert.java new file mode 100755 index 000000000..308298ac6 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/order/PayOrderConvert.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.adminserver.modules.pay.convert.order; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderDetailsRespVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExcelVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageItemRespVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderRespVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +/** + * 支付订单 Convert + * + * @author aquan + */ +@Mapper +public interface PayOrderConvert { + + PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class); + + + + PayOrderRespVO convert(PayOrderDO bean); + + /** + * 订单DO 转换为 详细订单 RespVO + * + * @param bean 订单DO + * @return 详细订单 RespVO + */ + PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean); + + /** + * 订单扩展DO 转换为 详细订单扩展 RespVO + * + * @param bean 订单扩展DO + * @return 详细订单扩展 RespVO + */ + PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + + /** + * 订单DO转自定义分页对象 + * + * @param bean 订单DO + * @return 分页对象 + */ + PayOrderPageItemRespVO pageConvertItemPage(PayOrderDO bean); + + /** + * 订单DO 转 订单导出excel VO + * + * @param bean 订单 DO + * @return 订单导出excel VO + */ + default PayOrderExcelVO excelConvert(PayOrderDO bean) { + if (bean == null) { + return null; + } + + PayOrderExcelVO payOrderExcelVO = new PayOrderExcelVO(); + + payOrderExcelVO.setId(bean.getId()); + payOrderExcelVO.setSubject(bean.getSubject()); + payOrderExcelVO.setMerchantOrderId(bean.getMerchantOrderId()); + payOrderExcelVO.setChannelOrderNo(bean.getChannelOrderNo()); + payOrderExcelVO.setStatus(bean.getStatus()); + payOrderExcelVO.setNotifyStatus(bean.getNotifyStatus()); + payOrderExcelVO.setNotifyUrl(bean.getNotifyUrl()); + payOrderExcelVO.setCreateTime(bean.getCreateTime()); + payOrderExcelVO.setSuccessTime(bean.getSuccessTime()); + payOrderExcelVO.setExpireTime(bean.getExpireTime()); + payOrderExcelVO.setNotifyTime(bean.getNotifyTime()); + payOrderExcelVO.setUserIp(bean.getUserIp()); + payOrderExcelVO.setRefundStatus(bean.getRefundStatus()); + payOrderExcelVO.setRefundTimes(bean.getRefundTimes()); + payOrderExcelVO.setBody(bean.getBody()); + + BigDecimal multiple = new BigDecimal(100); + + payOrderExcelVO.setAmount(BigDecimal.valueOf(bean.getAmount()) + .divide(multiple, 2, RoundingMode.HALF_UP).toString()); + + payOrderExcelVO.setChannelFeeAmount(BigDecimal.valueOf(bean.getChannelFeeAmount()) + .divide(multiple, 2, RoundingMode.HALF_UP).toString()); + payOrderExcelVO.setChannelFeeRate(java.math.BigDecimal.valueOf(bean.getChannelFeeRate()) + .multiply(multiple).toString()); + payOrderExcelVO.setRefundAmount(BigDecimal.valueOf(bean.getRefundAmount()) + .divide(multiple, 2, RoundingMode.HALF_UP).toString()); + + return payOrderExcelVO; + } +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/refund/PayRefundConvert.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/refund/PayRefundConvert.java new file mode 100755 index 000000000..8f5ee66d3 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/refund/PayRefundConvert.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.adminserver.modules.pay.convert.refund; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.*; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +/** + * 退款订单 Convert + * + * @author aquan + */ +@Mapper +public interface PayRefundConvert { + + PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class); + + PayRefundDO convert(PayRefundCreateReqVO bean); + + PayRefundDO convert(PayRefundUpdateReqVO bean); + + PayRefundRespVO convert(PayRefundDO bean); + + /** + * 退款订单 DO 转 退款详情订单 VO + * + * @param bean 退款订单 DO + * @return 退款详情订单 VO + */ + PayRefundDetailsRespVO refundDetailConvert(PayRefundDO bean); + + /** + * 退款订单DO 转 分页退款条目VO + * + * @param bean 退款订单DO + * @return 分页退款条目VO + */ + PayRefundPageItemRespVO pageItemConvert(PayRefundDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + + /** + * 退款订单DO 转 导出excel VO + * + * @param bean 退款订单DO + * @return 导出 excel VO + */ + default PayRefundExcelVO excelConvert(PayRefundDO bean) { + if (bean == null) { + return null; + } + + PayRefundExcelVO payRefundExcelVO = new PayRefundExcelVO(); + + payRefundExcelVO.setId(bean.getId()); + payRefundExcelVO.setTradeNo(bean.getTradeNo()); + payRefundExcelVO.setMerchantOrderId(bean.getMerchantOrderId()); + payRefundExcelVO.setMerchantRefundNo(bean.getMerchantRefundNo()); + payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl()); + payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus()); + payRefundExcelVO.setStatus(bean.getStatus()); + payRefundExcelVO.setType(bean.getType()); + payRefundExcelVO.setReason(bean.getReason()); + payRefundExcelVO.setUserIp(bean.getUserIp()); + payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo()); + payRefundExcelVO.setChannelRefundNo(bean.getChannelRefundNo()); + payRefundExcelVO.setExpireTime(bean.getExpireTime()); + payRefundExcelVO.setSuccessTime(bean.getSuccessTime()); + payRefundExcelVO.setNotifyTime(bean.getNotifyTime()); + payRefundExcelVO.setCreateTime(bean.getCreateTime()); + + BigDecimal multiple = new BigDecimal(100); + payRefundExcelVO.setPayAmount(BigDecimal.valueOf(bean.getPayAmount()) + .divide(multiple, 2, RoundingMode.HALF_UP).toString()); + payRefundExcelVO.setRefundAmount(BigDecimal.valueOf(bean.getRefundAmount()) + .divide(multiple, 2, RoundingMode.HALF_UP).toString()); + + return payRefundExcelVO; + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/app/PayAppMapper.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/app/PayAppMapper.java new file mode 100644 index 000000000..c6e3b38e9 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/app/PayAppMapper.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 支付应用信息 Mapper + * + * @author aquan + */ +@Mapper +public interface PayAppMapper extends BaseMapperX { + + /** + * 分页查询 + * + * @param reqVO 支付应用信息分页查询条件 + * @param merchantIds 商户 ID 集合 + * @return 支付应用信息 + */ + default PageResult selectPage(PayAppPageReqVO reqVO, Collection merchantIds) { + return selectPage(reqVO, new QueryWrapperX() + .likeIfPresent("name", reqVO.getName()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("remark", reqVO.getRemark()) + .eqIfPresent("pay_notify_url", reqVO.getPayNotifyUrl()) + .eqIfPresent("refund_notify_url", reqVO.getRefundNotifyUrl()) + .inIfPresent("merchant_id", merchantIds) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + /** + * 列表查询 + * + * @param reqVO 支付应用信息 Excel 导出查询条件 + * @param merchantIds 商户 ID 集合 + * @return 支付应用信息 + */ + default List selectList(PayAppExportReqVO reqVO, Collection merchantIds) { + return selectList(new QueryWrapperX() + .likeIfPresent("name", reqVO.getName()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("remark", reqVO.getRemark()) + .eqIfPresent("pay_notify_url", reqVO.getPayNotifyUrl()) + .eqIfPresent("refund_notify_url", reqVO.getRefundNotifyUrl()) + .inIfPresent("merchant_id", merchantIds) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + /** + * 根据 商户 ID 查询支付应用信息 + * + * @param merchantId 商户 ID + * @return 支付应用信息列表 + */ + default List getListByMerchantId(String merchantId) { + return selectList(new LambdaQueryWrapper() + .select(PayAppDO::getId, PayAppDO::getName) + .eq(PayAppDO::getMerchantId, merchantId)); + } + + /** + * 根据商户号统计存在的支付应用数量 + * + * @param merchantId 商户 ID + * @return 支付应用数量 + */ + default Long selectCount(Long merchantId) { + return selectCount(new LambdaQueryWrapper().eq(PayAppDO::getMerchantId, merchantId)); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/channel/PayChannelMapper.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/channel/PayChannelMapper.java new file mode 100644 index 000000000..54a4829a9 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/channel/PayChannelMapper.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 支付渠道 + Mapper + * + * @author 芋艿 + */ +@Mapper +public interface PayChannelMapper extends BaseMapperX { + + default PageResult selectPage(PayChannelPageReqVO reqVO) { + return selectPage(reqVO, new QueryWrapperX() + .eqIfPresent("code", reqVO.getCode()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("remark", reqVO.getRemark()) + .eqIfPresent("fee_rate", reqVO.getFeeRate()) + .eqIfPresent("merchant_id", reqVO.getMerchantId()) + .eqIfPresent("app_id", reqVO.getAppId()) + // .eqIfPresent("config", reqVO.getConfig()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id") ); + } + + default List selectList(PayChannelExportReqVO reqVO) { + return selectList(new QueryWrapperX() + .eqIfPresent("code", reqVO.getCode()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("remark", reqVO.getRemark()) + .eqIfPresent("fee_rate", reqVO.getFeeRate()) + .eqIfPresent("merchant_id", reqVO.getMerchantId()) + .eqIfPresent("app_id", reqVO.getAppId()) + // .eqIfPresent("config", reqVO.getConfig()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id") ); + } + + /** + * 根据条件获取通道数量 + * + * @param merchantId 商户编号 + * @param appid 应用编号 + * @param code 通道编码 + * @return 数量 + */ + default Integer selectCount(Long merchantId, Long appid, String code) { + return this.selectCount(new QueryWrapper().lambda() + .eq(PayChannelDO::getMerchantId, merchantId) + .eq(PayChannelDO::getAppId, appid) + .eq(PayChannelDO::getCode, code)).intValue(); + } + + /** + * 根据条件获取通道 + * + * @param merchantId 商户编号 + * @param appid 应用编号 + * @param code 通道编码 + * @return 数量 + */ + default PayChannelDO selectOne(Long merchantId, Long appid, String code) { + return this.selectOne((new QueryWrapper().lambda() + .eq(PayChannelDO::getMerchantId, merchantId) + .eq(PayChannelDO::getAppId, appid) + .eq(PayChannelDO::getCode, code) + )); + } + + /** + * 根据支付应用ID集合获得支付渠道列表 + * + * @param appIds 应用编号集合 + * @return 支付渠道列表 + */ + default List getChannelListByAppIds(Collection appIds){ + return this.selectList(new QueryWrapper().lambda() + .in(PayChannelDO::getAppId, appIds)); + } +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/merchant/PayMerchantMapper.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/merchant/PayMerchantMapper.java new file mode 100644 index 000000000..f143b1857 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/merchant/PayMerchantMapper.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 支付商户信息 Mapper + * + * @author 芋艿 + */ +@Mapper +public interface PayMerchantMapper extends BaseMapperX { + + default PageResult selectPage(PayMerchantPageReqVO reqVO) { + return selectPage(reqVO, new QueryWrapperX() + .likeIfPresent("no", reqVO.getNo()) + .likeIfPresent("name", reqVO.getName()) + .likeIfPresent("short_name", reqVO.getShortName()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("remark", reqVO.getRemark()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + default List selectList(PayMerchantExportReqVO reqVO) { + return selectList(new QueryWrapperX() + .likeIfPresent("no", reqVO.getNo()) + .likeIfPresent("name", reqVO.getName()) + .likeIfPresent("short_name", reqVO.getShortName()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("remark", reqVO.getRemark()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + /** + * 根据商户名称模糊查询商户集合 + * + * @param merchantName 商户名称 + * @return 商户集合 + */ + default List getMerchantListByName(String merchantName) { + return this.selectList(new QueryWrapperX() + .likeIfPresent("name", merchantName)); + } +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderExtensionMapper.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderExtensionMapper.java new file mode 100755 index 000000000..47baabb31 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderExtensionMapper.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 支付订单 Mapper + * + * @author aquan + */ +@Mapper +public interface PayOrderExtensionMapper extends BaseMapperX { + + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderMapper.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderMapper.java new file mode 100755 index 000000000..1fb9e870a --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderMapper.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 支付订单 Mapper 组件 + * + * @author aquan + */ +@Mapper +public interface PayOrderMapper extends BaseMapperX { + + default PageResult selectPage(PayOrderPageReqVO reqVO) { + return selectPage(reqVO, new QueryWrapperX() + .eqIfPresent("merchant_id", reqVO.getMerchantId()) + .eqIfPresent("app_id", reqVO.getAppId()) + .eqIfPresent("channel_id", reqVO.getChannelId()) + .eqIfPresent("channel_code", reqVO.getChannelCode()) + .likeIfPresent("merchant_order_id", reqVO.getMerchantOrderId()) + .eqIfPresent("notify_status", reqVO.getNotifyStatus()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("refund_status", reqVO.getRefundStatus()) + .likeIfPresent("channel_order_no", reqVO.getChannelOrderNo()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + default List selectList(PayOrderExportReqVO reqVO) { + return selectList(new QueryWrapperX() + .eqIfPresent("merchant_id", reqVO.getMerchantId()) + .eqIfPresent("app_id", reqVO.getAppId()) + .eqIfPresent("channel_id", reqVO.getChannelId()) + .eqIfPresent("channel_code", reqVO.getChannelCode()) + .likeIfPresent("merchant_order_id", reqVO.getMerchantOrderId()) + .eqIfPresent("notify_status", reqVO.getNotifyStatus()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("refund_status", reqVO.getRefundStatus()) + .likeIfPresent("channel_order_no", reqVO.getChannelOrderNo()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + /** + * 根据订单 ID 集合查询订单商品名称 + * + * @param idList 订单 ID 集合 + * @return 只包含商品名称和标题的订单集合对象 + */ + default List findByIdListQueryOrderSubject(Collection idList) { + return selectList(new LambdaQueryWrapper() + .select(PayOrderDO::getId, PayOrderDO::getSubject) + .in(PayOrderDO::getId, idList)); + } + + /** + * 查询符合的订单数量 + * + * @param appId 应用编号 + * @param status 订单状态 + * @return 条数 + */ + default Long selectCount(Long appId, Integer status) { + + return selectCount(new LambdaQueryWrapper() + .eq(PayOrderDO::getAppId, appId) + .in(PayOrderDO::getStatus, status)); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayRefundMapper.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayRefundMapper.java new file mode 100755 index 000000000..2fd3f2299 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayRefundMapper.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 退款订单 Mapper + * + * @author aquan + */ +@Mapper +public interface PayRefundMapper extends BaseMapperX { + + default PageResult selectPage(PayRefundPageReqVO reqVO) { + return selectPage(reqVO, new QueryWrapperX() + .eqIfPresent("merchant_id", reqVO.getMerchantId()) + .eqIfPresent("app_id", reqVO.getAppId()) + .eqIfPresent("channel_code", reqVO.getChannelCode()) + .likeIfPresent("merchant_refund_no", reqVO.getMerchantRefundNo()) + .eqIfPresent("type", reqVO.getType()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("notify_status", reqVO.getNotifyStatus()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + default List selectList(PayRefundExportReqVO reqVO) { + return selectList(new QueryWrapperX() + .eqIfPresent("merchant_id", reqVO.getMerchantId()) + .eqIfPresent("app_id", reqVO.getAppId()) + .eqIfPresent("channel_code", reqVO.getChannelCode()) + .likeIfPresent("merchant_refund_no", reqVO.getMerchantRefundNo()) + .eqIfPresent("type", reqVO.getType()) + .eqIfPresent("status", reqVO.getStatus()) + .eqIfPresent("notify_status", reqVO.getNotifyStatus()) + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc("id")); + } + + /** + * 查询符合的订单数量 + * + * @param appId 应用编号 + * @param status 订单状态 + * @return 条数 + */ + default Long selectCount(Long appId, Integer status) { + + return selectCount(new LambdaQueryWrapper() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getStatus, status)); + } +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/enums/PayErrorCodeConstants.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/enums/PayErrorCodeConstants.java new file mode 100644 index 000000000..df39d9efc --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/enums/PayErrorCodeConstants.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.adminserver.modules.pay.enums; diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppService.java new file mode 100644 index 000000000..da83ca825 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppService.java @@ -0,0 +1,103 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.app; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 支付应用信息 Service 接口 + * + * @author 芋艿 + */ +public interface PayAppService { + + /** + * 创建支付应用信息 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createApp(@Valid PayAppCreateReqVO createReqVO); + + /** + * 更新支付应用信息 + * + * @param updateReqVO 更新信息 + */ + void updateApp(@Valid PayAppUpdateReqVO updateReqVO); + + /** + * 删除支付应用信息 + * + * @param id 编号 + */ + void deleteApp(Long id); + + /** + * 获得支付应用信息 + * + * @param id 编号 + * @return 支付应用信息 + */ + PayAppDO getApp(Long id); + + /** + * 获得支付应用信息列表 + * + * @param ids 编号 + * @return 支付应用信息列表 + */ + List getAppList(Collection ids); + + /** + * 获得支付应用信息分页 + * + * @param pageReqVO 分页查询 + * @return 支付应用信息分页 + */ + PageResult getAppPage(PayAppPageReqVO pageReqVO); + + /** + * 获得支付应用信息列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 支付应用信息列表 + */ + List getAppList(PayAppExportReqVO exportReqVO); + + /** + * 修改应用信息状态 + * + * @param id 应用编号 + * @param status 状态{@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + void updateAppStatus(Long id, Integer status); + + /** + * 根据商户 ID 获得支付应用信息列表, + * + * @param merchantId 商户 ID + * @return 支付应用信息列表 + */ + List getListByMerchantId(String merchantId); + + /** + * 获得指定编号的商户 Map + * + * @param appIdList 应用编号集合 + * @return 商户 Map + */ + default Map getAppMap(Collection appIdList) { + List list = this.getAppList(appIdList); + return CollectionUtils.convertMap(list, PayAppDO::getId); + } +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/impl/PayAppServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/impl/PayAppServiceImpl.java new file mode 100644 index 000000000..bb6e765b3 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/impl/PayAppServiceImpl.java @@ -0,0 +1,189 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.app.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.convert.app.PayAppConvert; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderMapper; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import com.google.common.annotations.VisibleForTesting; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.*; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * 支付应用信息 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +public class PayAppServiceImpl implements PayAppService { + + @Resource + private PayAppMapper appMapper; + + @Resource + private PayMerchantMapper merchantMapper; + + @Resource + private PayOrderMapper orderMapper; + + @Resource + private PayRefundMapper refundMapper; + + + @Override + public Long createApp(PayAppCreateReqVO createReqVO) { + // 插入 + PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO); + appMapper.insert(app); + // 返回 + return app.getId(); + } + + @Override + public void updateApp(PayAppUpdateReqVO updateReqVO) { + // 校验存在 + this.validateAppExists(updateReqVO.getId()); + // 更新 + PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO); + appMapper.updateById(updateObj); + } + + @Override + public void deleteApp(Long id) { + // 校验存在 + this.validateAppExists(id); + this.validateOrderTransactionExist(id); + + // 删除 + appMapper.deleteById(id); + } + + private void validateAppExists(Long id) { + if (appMapper.selectById(id) == null) { + throw exception(PAY_APP_NOT_FOUND); + } + } + + @Override + public PayAppDO getApp(Long id) { + return appMapper.selectById(id); + } + + @Override + public List getAppList(Collection ids) { + return appMapper.selectBatchIds(ids); + } + + @Override + public PageResult getAppPage(PayAppPageReqVO pageReqVO) { + Set merchantIdList = this.getMerchantCondition(pageReqVO.getMerchantName()); + if (StrUtil.isNotBlank(pageReqVO.getMerchantName()) && CollectionUtil.isEmpty(merchantIdList)) { + return new PageResult<>(); + } + return appMapper.selectPage(pageReqVO, merchantIdList); + } + + @Override + public List getAppList(PayAppExportReqVO exportReqVO) { + Set merchantIdList = this.getMerchantCondition(exportReqVO.getMerchantName()); + if (StrUtil.isNotBlank(exportReqVO.getMerchantName()) && CollectionUtil.isEmpty(merchantIdList)) { + return new ArrayList<>(); + } + return appMapper.selectList(exportReqVO, merchantIdList); + } + + /** + * 获取商户编号集合,根据商户名称模糊查询得到所有的商户编号集合 + * + * @param merchantName 商户名称 + * @return 商户编号集合 + */ + private Set getMerchantCondition(String merchantName) { + if (StrUtil.isBlank(merchantName)) { + return Collections.emptySet(); + } + return convertSet(merchantMapper.getMerchantListByName(merchantName), PayMerchantDO::getId); + } + + /** + * 修改应用信息状态 + * + * @param id 应用编号 + * @param status 状态{@link CommonStatusEnum} + */ + @Override + public void updateAppStatus(Long id, Integer status) { + // 校验商户存在 + this.checkAppExists(id); + // 更新状态 + PayAppDO app = new PayAppDO(); + app.setId(id); + app.setStatus(status); + appMapper.updateById(app); + } + + /** + * 根据商户 ID 获得支付应用信息列表, + * + * @param merchantId 商户 ID + * @return 支付应用信息列表 + */ + @Override + public List getListByMerchantId(String merchantId) { + return appMapper.getListByMerchantId(merchantId); + } + + /** + * 检查商户是否存在 + * + * @param id 商户编号 + */ + @VisibleForTesting + public void checkAppExists(Long id) { + if (id == null) { + return; + } + PayAppDO payApp = appMapper.selectById(id); + if (payApp == null) { + throw exception(PAY_APP_NOT_FOUND); + } + } + + /** + * 验证是否存在交易中或者退款中等处理中状态的订单 + * + * @param appId 应用 ID + */ + private void validateOrderTransactionExist(Long appId) { + // 查看交易订单 + if (orderMapper.selectCount(appId, PayOrderStatusEnum.WAITING.getStatus()) > 0) { + throw exception(PAY_APP_EXIST_TRANSACTION_ORDER_CANT_DELETE); + } + // 查看退款订单 + if (refundMapper.selectCount(appId, PayRefundStatusEnum.CREATE.getStatus()) > 0) { + throw exception(PAY_APP_EXIST_TRANSACTION_ORDER_CANT_DELETE); + } + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelService.java new file mode 100644 index 000000000..1eeb567dd --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelService.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.channel; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 支付渠道 Service 接口 + * + * @author aquan + */ +public interface PayChannelService { + + /** + * 创建支付渠道 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createChannel(@Valid PayChannelCreateReqVO createReqVO); + + /** + * 更新支付渠道 + * + * @param updateReqVO 更新信息 + */ + void updateChannel(@Valid PayChannelUpdateReqVO updateReqVO); + + /** + * 删除支付渠道 + * + * @param id 编号 + */ + void deleteChannel(Long id); + + /** + * 获得支付渠道 + * + * @param id 编号 + * @return 支付渠道 + */ + PayChannelDO getChannel(Long id); + + /** + * 获得支付渠道 + * 列表 + * + * @param ids 编号 + * @return 支付渠道 + * 列表 + */ + List getChannelList(Collection ids); + + /** + * 获得支付渠道 + * 分页 + * + * @param pageReqVO 分页查询 + * @return 支付渠道 + * 分页 + */ + PageResult getChannelPage(PayChannelPageReqVO pageReqVO); + + /** + * 获得支付渠道 + * 列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 支付渠道列表 + */ + List getChannelList(PayChannelExportReqVO exportReqVO); + + /** + * 根据支付应用ID集合获得支付渠道列表 + * + * @param appIds 应用编号集合 + * @return 支付渠道列表 + */ + List getChannelListByAppIds(Collection appIds); + + /** + * 根据条件获取通道数量 + * + * @param merchantId 商户编号 + * @param appid 应用编号 + * @param code 通道编码 + * @return 数量 + */ + Integer getChannelCountByConditions(Long merchantId, Long appid, String code); + + /** + * 根据条件获取通道 + * + * @param merchantId 商户编号 + * @param appid 应用编号 + * @param code 通道编码 + * @return 数量 + */ + PayChannelDO getChannelByConditions(Long merchantId, Long appid, String code); + + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/impl/PayChannelServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/impl/PayChannelServiceImpl.java new file mode 100644 index 000000000..6ba21f8c3 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/impl/PayChannelServiceImpl.java @@ -0,0 +1,161 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.channel.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelUpdateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.convert.channel.PayChannelConvert; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel.PayChannelMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Validator; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR; +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 支付渠道 Service 实现类 + * + * @author aquan + */ +@Service +@Slf4j +@Validated +public class PayChannelServiceImpl implements PayChannelService { + + @Resource + private PayChannelMapper channelMapper; + + @Resource + private Validator validator; + + @Override + public Long createChannel(PayChannelCreateReqVO reqVO) { + // 断言是否有重复的 + PayChannelDO channelDO = this.getChannelByConditions(reqVO.getMerchantId(), reqVO.getAppId(), reqVO.getCode()); + if (ObjectUtil.isNotNull(channelDO)) { + throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR); + } + + // 新增渠道 + PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO); + settingConfigAndCheckParam(channel, reqVO.getConfig()); + channelMapper.insert(channel); + return channel.getId(); + } + + @Override + public void updateChannel(PayChannelUpdateReqVO updateReqVO) { + // 校验存在 + this.validateChannelExists(updateReqVO.getId()); + // 更新 + PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO); + settingConfigAndCheckParam(channel, updateReqVO.getConfig()); + channelMapper.updateById(channel); + } + + @Override + public void deleteChannel(Long id) { + // 校验存在 + this.validateChannelExists(id); + // 删除 + channelMapper.deleteById(id); + } + + private void validateChannelExists(Long id) { + if (channelMapper.selectById(id) == null) { + throw exception(CHANNEL_NOT_EXISTS); + } + } + + @Override + public PayChannelDO getChannel(Long id) { + return channelMapper.selectById(id); + } + + @Override + public List getChannelList(Collection ids) { + return channelMapper.selectBatchIds(ids); + } + + @Override + public PageResult getChannelPage(PayChannelPageReqVO pageReqVO) { + return channelMapper.selectPage(pageReqVO); + } + + @Override + public List getChannelList(PayChannelExportReqVO exportReqVO) { + return channelMapper.selectList(exportReqVO); + } + + /** + * 根据支付应用ID集合获得支付渠道列表 + * + * @param appIds 应用编号集合 + * @return 支付渠道列表 + */ + @Override + public List getChannelListByAppIds(Collection appIds) { + return channelMapper.getChannelListByAppIds(appIds); + } + + + /** + * 根据条件获取通道数量 + * + * @param merchantId 商户编号 + * @param appid 应用编号 + * @param code 通道编码 + * @return 数量 + */ + @Override + public Integer getChannelCountByConditions(Long merchantId, Long appid, String code) { + return this.channelMapper.selectCount(merchantId, appid, code); + } + + /** + * 根据条件获取通道 + * + * @param merchantId 商户编号 + * @param appid 应用编号 + * @param code 通道编码 + * @return 数量 + */ + @Override + public PayChannelDO getChannelByConditions(Long merchantId, Long appid, String code) { + return this.channelMapper.selectOne(merchantId, appid, code); + } + + /** + * 设置渠道配置以及参数校验 + * + * @param channel 渠道 + * @param configStr 配置 + */ + private void settingConfigAndCheckParam(PayChannelDO channel, String configStr) { + // 得到这个渠道是微信的还是支付宝的 + Class payClass = PayChannelEnum.getByCode(channel.getCode()).getConfigClass(); + if (ObjectUtil.isNull(payClass)) { + throw exception(CHANNEL_NOT_EXISTS); + } + PayClientConfig config = JSONUtil.toBean(configStr, payClass); + + // 验证参数 + config.validate(validator); + channel.setConfig(config); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantService.java new file mode 100644 index 000000000..b8e74dd75 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantService.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.merchant; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantUpdateReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 支付商户信息 Service 接口 + * + * @author aquan + */ +public interface PayMerchantService { + + /** + * 创建支付商户信息 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createMerchant(@Valid PayMerchantCreateReqVO createReqVO); + + /** + * 更新支付商户信息 + * + * @param updateReqVO 更新信息 + */ + void updateMerchant(@Valid PayMerchantUpdateReqVO updateReqVO); + + /** + * 删除支付商户信息 + * + * @param id 编号 + */ + void deleteMerchant(Long id); + + /** + * 获得支付商户信息 + * + * @param id 编号 + * @return 支付商户信息 + */ + PayMerchantDO getMerchant(Long id); + + /** + * 获得支付商户信息列表 + * + * @param ids 编号 + * @return 支付商户信息列表 + */ + List getMerchantList(Collection ids); + + /** + * 获得支付商户信息分页 + * + * @param pageReqVO 分页查询 + * @return 支付商户信息分页 + */ + PageResult getMerchantPage(PayMerchantPageReqVO pageReqVO); + + /** + * 获得支付商户信息列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 支付商户信息列表 + */ + List getMerchantList(PayMerchantExportReqVO exportReqVO); + + /** + * 修改商户状态 + * + * @param id 商户编号 + * @param status 状态 + */ + void updateMerchantStatus(Long id, Integer status); + + /** + * 根据商户名称模糊查询商户集合 + * + * @param merchantName 商户名称 + * @return 商户集合 + */ + List getMerchantListByName(String merchantName); + + /** + * 获得指定编号的商户 Map + * + * @param merchantIds 商户编号数组 + * @return 商户 Map + */ + default Map getMerchantMap(Collection merchantIds) { + List list = this.getMerchantList(merchantIds); + return CollectionUtils.convertMap(list, PayMerchantDO::getId); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/impl/PayMerchantServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/impl/PayMerchantServiceImpl.java new file mode 100644 index 000000000..b80132a1f --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/impl/PayMerchantServiceImpl.java @@ -0,0 +1,150 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.merchant.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantUpdateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.convert.merchant.PayMerchantConvert; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import com.google.common.annotations.VisibleForTesting; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_MERCHANT_EXIST_APP_CANT_DELETE; +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_MERCHANT_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 支付商户信息 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +public class PayMerchantServiceImpl implements PayMerchantService { + + @Resource + private PayMerchantMapper merchantMapper; + + @Resource + private PayAppMapper appMapper; + + @Override + public Long createMerchant(PayMerchantCreateReqVO createReqVO) { + // 插入 + PayMerchantDO merchant = PayMerchantConvert.INSTANCE.convert(createReqVO); + merchant.setNo(this.generateMerchantNo()); + merchantMapper.insert(merchant); + // 返回 + return merchant.getId(); + } + + @Override + public void updateMerchant(PayMerchantUpdateReqVO updateReqVO) { + // 校验存在 + this.validateMerchantExists(updateReqVO.getId()); + // 更新 + PayMerchantDO updateObj = PayMerchantConvert.INSTANCE.convert(updateReqVO); + merchantMapper.updateById(updateObj); + } + + @Override + public void deleteMerchant(Long id) { + // 校验 + this.validateMerchantExists(id); + this.validateAppExists(id); + // 删除 + merchantMapper.deleteById(id); + } + + @Override + public PayMerchantDO getMerchant(Long id) { + return merchantMapper.selectById(id); + } + + @Override + public List getMerchantList(Collection ids) { + return merchantMapper.selectBatchIds(ids); + } + + @Override + public PageResult getMerchantPage(PayMerchantPageReqVO pageReqVO) { + return merchantMapper.selectPage(pageReqVO); + } + + @Override + public List getMerchantList(PayMerchantExportReqVO exportReqVO) { + return merchantMapper.selectList(exportReqVO); + } + + @Override + public void updateMerchantStatus(Long id, Integer status) { + // 校验商户存在 + this.checkMerchantExists(id); + // 更新状态 + PayMerchantDO merchant = new PayMerchantDO(); + merchant.setId(id); + merchant.setStatus(status); + merchantMapper.updateById(merchant); + } + + @Override + public List getMerchantListByName(String merchantName) { + return this.merchantMapper.getMerchantListByName(merchantName); + } + + @VisibleForTesting + public void checkMerchantExists(Long id) { + if (id == null) { + return; + } + PayMerchantDO merchant = merchantMapper.selectById(id); + if (merchant == null) { + throw exception(PAY_MERCHANT_NOT_EXISTS); + } + } + + /** + * 校验商户是否存在 + * + * @param id 商户 ID + */ + private void validateMerchantExists(Long id) { + if (ObjectUtil.isNull(merchantMapper.selectById(id))) { + throw exception(PAY_MERCHANT_NOT_EXISTS); + } + } + + /** + * 校验商户是否还存在支付应用 + * + * @param id 商户ID + */ + private void validateAppExists(Long id) { + if (appMapper.selectCount(id) > 0) { + throw exception(PAY_MERCHANT_EXIST_APP_CANT_DELETE); + } + } + + // TODO @芋艿:后续增加下合适的算法 + /** + * 根据年月日时分秒毫秒生成商户号 + * + * @return 商户号 + */ + private String generateMerchantNo() { + return "M" + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmssSSS"); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderExtensionService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderExtensionService.java new file mode 100755 index 000000000..a139956b4 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderExtensionService.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.order; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 支付订单 Service 接口 + * + * @author aquan + */ +public interface PayOrderExtensionService { + + /** + * 获得支付订单 + * + * @param id 编号 + * @return 支付订单 + */ + PayOrderExtensionDO getOrderExtension(Long id); + + /** + * 获得支付订单 + * 列表 + * + * @param ids 编号 + * @return 支付订单 + * 列表 + */ + List getOrderExtensionList(Collection ids); + + + /** + * 根据订单成功的 扩展订单ID 查询所有的扩展订单转 成 map 返回 + * + * @param successExtensionIdList 订单 ID 集合 + * @return 订单扩展 map 集合 + */ + default Map getOrderExtensionMap(Collection successExtensionIdList) { + List list = this.getOrderExtensionList(successExtensionIdList); + return CollectionUtils.convertMap(list, PayOrderExtensionDO::getId); + } + + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderService.java new file mode 100755 index 000000000..ebdf7e340 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderService.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.order; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 支付订单 Service 接口 + * + * @author aquan + */ +public interface PayOrderService { + + /** + * 获得支付订单 + * + * @param id 编号 + * @return 支付订单 + */ + PayOrderDO getOrder(Long id); + + /** + * 获得支付订单 + * 分页 + * + * @param pageReqVO 分页查询 + * @return 支付订单 + * 分页 + */ + PageResult getOrderPage(PayOrderPageReqVO pageReqVO); + + /** + * 获得支付订单 + * 列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 支付订单 + * 列表 + */ + List getOrderList(PayOrderExportReqVO exportReqVO); + + /** + * 根据 ID 集合获取只包含商品名称的订单集合 + * + * @param idList 订单 ID 集合 + * @return 只包含商品名称的订单集合 + */ + List getOrderSubjectList(Collection idList); + + /** + * 根据订单 ID 集合获取订单商品名称Map集合 + * + * @param idList 订单 ID 集合 + * @return 订单商品 map 集合 + */ + default Map getOrderSubjectMap(Collection idList) { + List list = getOrderSubjectList(idList); + return CollectionUtils.convertMap(list, PayOrderDO::getId); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayRefundService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayRefundService.java new file mode 100755 index 000000000..1412bdefe --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayRefundService.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.order; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import java.util.List; + +/** + * 退款订单 Service 接口 + * + * @author aquan + */ +public interface PayRefundService { + + /** + * 获得退款订单 + * + * @param id 编号 + * @return 退款订单 + */ + PayRefundDO getRefund(Long id); + + /** + * 获得退款订单分页 + * + * @param pageReqVO 分页查询 + * @return 退款订单分页 + */ + PageResult getRefundPage(PayRefundPageReqVO pageReqVO); + + /** + * 获得退款订单列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 退款订单列表 + */ + List getRefundList(PayRefundExportReqVO exportReqVO); + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderExtensionServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderExtensionServiceImpl.java new file mode 100755 index 000000000..197275657 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderExtensionServiceImpl.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.order.impl; + +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderExtensionMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderExtensionService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; + +/** + * 支付订单 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +public class PayOrderExtensionServiceImpl implements PayOrderExtensionService { + + @Resource + private PayOrderExtensionMapper orderExtensionMapper; + + @Override + public PayOrderExtensionDO getOrderExtension(Long id) { + return orderExtensionMapper.selectById(id); + } + + @Override + public List getOrderExtensionList(Collection ids) { + return orderExtensionMapper.selectBatchIds(ids); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderServiceImpl.java new file mode 100755 index 000000000..dd6d0a8bd --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderServiceImpl.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.order.impl; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; + +/** + * 支付订单 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +public class PayOrderServiceImpl implements PayOrderService { + + @Resource + private PayOrderMapper orderMapper; + + @Override + public PayOrderDO getOrder(Long id) { + return orderMapper.selectById(id); + } + + @Override + public PageResult getOrderPage(PayOrderPageReqVO pageReqVO) { + return orderMapper.selectPage(pageReqVO); + } + + @Override + public List getOrderList(PayOrderExportReqVO exportReqVO) { + return orderMapper.selectList(exportReqVO); + } + + /** + * 根据 ID 集合获取只包含商品名称的订单集合 + * + * @param idList 订单 ID 集合 + * @return 只包含商品名称的订单集合 + */ + @Override + public List getOrderSubjectList(Collection idList) { + return orderMapper.findByIdListQueryOrderSubject(idList); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayRefundServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayRefundServiceImpl.java new file mode 100755 index 000000000..00f42ebcd --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayRefundServiceImpl.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.order.impl; + +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayRefundService; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 退款订单 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +public class PayRefundServiceImpl implements PayRefundService { + + @Resource + private PayRefundMapper refundMapper; + + @Override + public PayRefundDO getRefund(Long id) { + return refundMapper.selectById(id); + } + + @Override + public PageResult getRefundPage(PayRefundPageReqVO pageReqVO) { + return refundMapper.selectPage(pageReqVO); + } + + @Override + public List getRefundList(PayRefundExportReqVO exportReqVO) { + return refundMapper.selectList(exportReqVO); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysDictTypeConstants.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysDictTypeConstants.java index 2a1dece7f..cc7ae83f8 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysDictTypeConstants.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysDictTypeConstants.java @@ -22,6 +22,31 @@ public interface SysDictTypeConstants { String SMS_SEND_STATUS = "sys_sms_send_status"; // 短信发送状态 String SMS_RECEIVE_STATUS = "sys_sms_receive_status"; // 短信接收状态 + /** + * 支付-订单-订单状态 + */ + String PAY_ORDER_STATUS = "pay_order_status"; + + /** + * 支付-订单-订单回调商户状态 + */ + String PAY_ORDER_NOTIFY_STATUS = "pay_order_notify_status"; + + /** + * 支付-订单-订单退款状态 + */ + String PAY_ORDER_REFUND_STATUS = "pay_order_refund_status"; + + /** + * 支付-退款订单-退款状态 + */ + String PAY_REFUND_ORDER_STATUS = "pay_refund_order_status"; + + /** + * 支付-退款订单-退款类别 + */ + String PAY_REFUND_ORDER_TYPE = "pay_refund_order_type"; + String BPM_TASK_ASSIGN_RULE_TYPE = "bpm_task_assign_rule_type"; // 任务分配规则类型 String BPM_TASK_ASSIGN_SCRIPT = "bpm_task_assign_script"; // 任务分配自定义脚本 diff --git a/yudao-admin-server/src/main/resources/application-local.yaml b/yudao-admin-server/src/main/resources/application-local.yaml index adc2755a0..2e26fd6fe 100644 --- a/yudao-admin-server/src/main/resources/application-local.yaml +++ b/yudao-admin-server/src/main/resources/application-local.yaml @@ -183,6 +183,7 @@ yudao: - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 pay: pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify + pay-return-url: http://niubi.natapp1.cc/api/pay/order/return refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify demo: false # 关闭演示模式 diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehaviorTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehaviorTest.java index 21fe1fb88..d42a858a2 100644 --- a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehaviorTest.java +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehaviorTest.java @@ -31,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -class BpmUserTaskActivitiBehaviorTest extends BaseMockitoUnitTest { +public class BpmUserTaskActivitiBehaviorTest extends BaseMockitoUnitTest { @InjectMocks private BpmUserTaskActivitiBehavior behavior; diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppServiceTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppServiceTest.java new file mode 100644 index 000000000..7dee06402 --- /dev/null +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppServiceTest.java @@ -0,0 +1,245 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.app; + +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.adminserver.BaseDbUnitTest; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.app.impl.PayAppServiceImpl; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_APP_NOT_FOUND; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link PayAppServiceImpl} 的单元测试类 + * + * @author 芋艿 + */ +@Import(PayAppServiceImpl.class) +public class PayAppServiceTest extends BaseDbUnitTest { + + @Resource + private PayAppServiceImpl appService; + + @Resource + private PayAppMapper appMapper; + + @MockBean(name = "payMerchantMapper") + private PayMerchantMapper payMerchantMapper; + + @Test + public void testCreateApp_success() { + // 准备参数 + PayAppCreateReqVO reqVO = randomPojo(PayAppCreateReqVO.class, o -> + o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus()))); + + // 调用 + Long appId = appService.createApp(reqVO); + // 断言 + assertNotNull(appId); + // 校验记录的属性是否正确 + PayAppDO app = appMapper.selectById(appId); + assertPojoEquals(reqVO, app); + } + + @Test + public void testUpdateApp_success() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, o -> + o.setStatus(CommonStatusEnum.DISABLE.getStatus())); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setId(dbApp.getId()); // 设置更新的 ID + }); + + // 调用 + appService.updateApp(reqVO); + // 校验是否更新正确 + PayAppDO app = appMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, app); + } + + @Test + public void testUpdateApp_notExists() { + // 准备参数 + PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> + o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus()))); + // 调用, 并断言异常 + assertServiceException(() -> appService.updateApp(reqVO), PAY_APP_NOT_FOUND); + } + + @Test + public void testDeleteApp_success() { + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, o -> + o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus()))); + appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbApp.getId(); + + // 调用 + appService.deleteApp(id); + // 校验数据不存在了 + assertNull(appMapper.selectById(id)); + } + + @Test + public void testDeleteApp_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> appService.deleteApp(id), PAY_APP_NOT_FOUND); + } + + @Test + public void testGetAppPage() { + Long merchantId = 1L; + Long mismatchMerchantId = 2L; + + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到 + o.setName("灿灿姐的杂货铺"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("敏敏姐的小卖铺"); + o.setPayNotifyUrl("https://www.hc.com"); + o.setRefundNotifyUrl("https://www.xm.com"); + o.setMerchantId(merchantId); + o.setCreateTime(buildTime(2021,11,20)); + }); + + // mock 数据 + PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到 + o.setId(merchantId); + o.setNo("M1008611"); + o.setName("灿哥的杂货铺"); + o.setShortName("灿灿子"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("灿哥的杂货铺"); + o.setCreateTime(buildTime(2021,11,3)); + }); + + Mockito.when(payMerchantMapper.getMerchantListByName(dbMerchant.getName())) + .thenReturn(Collections.singletonList(dbMerchant)); + + appMapper.insert(dbApp); + // 测试 name 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setName("敏敏姐的杂货铺"))); + // 测试 status 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 remark 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRemark("灿灿姐的小卖部"))); + // 测试 payNotifyUrl 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setPayNotifyUrl("xm.com"))); + // 测试 refundNotifyUrl 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRefundNotifyUrl("hc.com"))); + // 测试 merchantId 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setMerchantId(mismatchMerchantId))); + // 测试 createTime 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setCreateTime(buildTime(2021,12,21)))); + // 准备参数 + PayAppPageReqVO reqVO = new PayAppPageReqVO(); + reqVO.setName("灿灿姐的杂货铺"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setRemark("敏敏姐的小卖铺"); + reqVO.setPayNotifyUrl("https://www.hc.com"); + reqVO.setRefundNotifyUrl("https://www.xm.com"); + reqVO.setMerchantName(dbMerchant.getName()); + reqVO.setBeginCreateTime(buildTime(2021,11,19)); + reqVO.setEndCreateTime(buildTime(2021,11,21)); + + // 调用 + PageResult pageResult = appService.getAppPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbApp, pageResult.getList().get(0)); + } + + @Test // TODO 请修改 null 为需要的值 + public void testGetAppList() { + Long merchantId = 1L; + Long mismatchMerchantId = 2L; + + // mock 数据 + PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到 + o.setName("灿灿姐的杂货铺"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("敏敏姐的小卖铺"); + o.setPayNotifyUrl("https://www.hc.com"); + o.setRefundNotifyUrl("https://www.xm.com"); + o.setMerchantId(merchantId); + o.setCreateTime(buildTime(2021,11,20)); + }); + + // mock 数据 + PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到 + o.setId(merchantId); + o.setNo("M1008611"); + o.setName("灿哥的杂货铺"); + o.setShortName("灿灿子"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("灿哥的杂货铺"); + o.setCreateTime(buildTime(2021,11,3)); + }); + + Mockito.when(payMerchantMapper.getMerchantListByName(dbMerchant.getName())) + .thenReturn(Collections.singletonList(dbMerchant)); + + appMapper.insert(dbApp); + // 测试 name 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setName("敏敏姐的杂货铺"))); + // 测试 status 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 remark 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRemark("灿灿姐的小卖部"))); + // 测试 payNotifyUrl 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setPayNotifyUrl("xm.com"))); + // 测试 refundNotifyUrl 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRefundNotifyUrl("hc.com"))); + // 测试 merchantId 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setMerchantId(mismatchMerchantId))); + // 测试 createTime 不匹配 + appMapper.insert(cloneIgnoreId(dbApp, o -> o.setCreateTime(buildTime(2021,12,21)))); + // 准备参数 + PayAppExportReqVO reqVO = new PayAppExportReqVO(); + reqVO.setName("灿灿姐的杂货铺"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setRemark("敏敏姐的小卖铺"); + reqVO.setPayNotifyUrl("https://www.hc.com"); + reqVO.setRefundNotifyUrl("https://www.xm.com"); + reqVO.setMerchantName(dbMerchant.getName()); + reqVO.setBeginCreateTime(buildTime(2021,11,19)); + reqVO.setEndCreateTime(buildTime(2021,11,21)); + + // 调用 + List list = appService.getAppList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbApp, list.get(0)); + } + +} diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelConfig.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelConfig.java new file mode 100644 index 000000000..bff90e6e0 --- /dev/null +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelConfig.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.channel; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.validation.Validation; +import javax.validation.Validator; + +/** + * 用于初始化 validator Bean 对象 + * @author aquan + */ +@Configuration +public class PayChannelConfig { + + @Bean + public Validator validator(){ + return Validation.buildDefaultValidatorFactory().getValidator(); + } +} diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelServiceTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelServiceTest.java new file mode 100644 index 000000000..b8d2bc917 --- /dev/null +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelServiceTest.java @@ -0,0 +1,404 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.channel; + +import cn.iocoder.yudao.adminserver.BaseDbUnitTest; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelUpdateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel.PayChannelMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.channel.impl.PayChannelServiceImpl; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; +import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; +import com.alibaba.fastjson.JSON; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link PayChannelServiceImpl} 的单元测试类 + * + * @author 芋艿 + */ +@Import({ + PayChannelServiceImpl.class, + PayChannelConfig.class +}) +public class PayChannelServiceTest extends BaseDbUnitTest { + + @Resource + private PayChannelServiceImpl channelService; + + @Resource + private PayChannelMapper channelMapper; + + @Test + public void testCreateWechatVersion2Channel_success() { + // 准备参数 + + WXPayClientConfig v2Config = getV2Config(); + PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { + o.setCode(PayChannelEnum.WX_PUB.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(JSON.toJSONString(v2Config)); + }); + + // 调用 + Long channelId = channelService.createChannel(reqVO); + // 断言 + assertNotNull(channelId); + // 校验记录的属性是否正确 + PayChannelDO channel = channelMapper.selectById(channelId); + assertPojoEquals(reqVO, channel, "config"); + // 关于config 对象应该拿出来重新对比 + assertPojoEquals(v2Config, channel.getConfig()); + + } + + @Test + public void testCreateWechatVersion3Channel_success() { + // 准备参数 + WXPayClientConfig v3Config = getV3Config(); + PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { + o.setCode(PayChannelEnum.WX_PUB.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(JSON.toJSONString(v3Config)); + }); + + // 调用 + Long channelId = channelService.createChannel(reqVO); + // 断言 + assertNotNull(channelId); + // 校验记录的属性是否正确 + PayChannelDO channel = channelMapper.selectById(channelId); + assertPojoEquals(reqVO, channel, "config"); + // 关于config 对象应该拿出来重新对比 + assertPojoEquals(v3Config, channel.getConfig()); + } + + @Test + public void testCreateAliPayPublicKeyChannel_success() { + // 准备参数 + + AlipayPayClientConfig payClientConfig = getPublicKeyConfig(); + PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(JSON.toJSONString(payClientConfig)); + }); + + // 调用 + Long channelId = channelService.createChannel(reqVO); + // 断言 + assertNotNull(channelId); + // 校验记录的属性是否正确 + PayChannelDO channel = channelMapper.selectById(channelId); + assertPojoEquals(reqVO, channel, "config"); + // 关于config 对象应该拿出来重新对比 + assertPojoEquals(payClientConfig, channel.getConfig()); + + } + + @Test + public void testCreateAliPayCertificateChannel_success() { + // 准备参数 + + AlipayPayClientConfig payClientConfig = getCertificateConfig(); + PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(JSON.toJSONString(payClientConfig)); + }); + + // 调用 + Long channelId = channelService.createChannel(reqVO); + // 断言 + assertNotNull(channelId); + // 校验记录的属性是否正确 + PayChannelDO channel = channelMapper.selectById(channelId); + assertPojoEquals(reqVO, channel, "config"); + // 关于config 对象应该拿出来重新对比 + assertPojoEquals(payClientConfig, channel.getConfig()); + } + + @Test + public void testUpdateChannel_success() { + // mock 数据 + AlipayPayClientConfig payClientConfig = getCertificateConfig(); + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(payClientConfig); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig(); + PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> { + o.setCode(dbChannel.getCode()); + o.setStatus(dbChannel.getStatus()); + o.setConfig(JSON.toJSONString(payClientPublicKeyConfig)); + o.setId(dbChannel.getId()); // 设置更新的 ID + }); + + // 调用 + channelService.updateChannel(reqVO); + // 校验是否更新正确 + PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, channel, "config"); + assertPojoEquals(payClientPublicKeyConfig, channel.getConfig()); + } + + @Test + public void testUpdateChannel_notExists() { + // 准备参数 + AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig(); + PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(JSON.toJSONString(payClientPublicKeyConfig)); + }); + + // 调用, 并断言异常 + assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_EXISTS); + } + + @Test + public void testDeleteChannel_success() { + // mock 数据 + AlipayPayClientConfig payClientConfig = getCertificateConfig(); + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setConfig(payClientConfig); + }); + channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbChannel.getId(); + + // 调用 + channelService.deleteChannel(id); + // 校验数据不存在了 + assertNull(channelMapper.selectById(id)); + } + + @Test + public void testDeleteChannel_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_EXISTS); + } + + @Test // TODO 请修改 null 为需要的值 + public void testGetChannelPage() { + // mock 数据 + AlipayPayClientConfig payClientConfig = getCertificateConfig(); + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到 + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("灿灿子的支付渠道"); + o.setFeeRate(0.03); + o.setMerchantId(1L); + o.setAppId(1L); + o.setConfig(payClientConfig); + o.setCreateTime(buildTime(2021,11,20)); + }); + channelMapper.insert(dbChannel); + // 执行拷贝的时候会出现异常,所以在插入后要重置为null 后续在写入新的 + dbChannel.setConfig(null); + // 测试 code 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setCode(PayChannelEnum.WX_PUB.getCode()); + })); + // 测试 status 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + })); + // 测试 remark 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o ->{ + o.setConfig(payClientConfig); + o.setRemark("敏敏子的渠道"); + })); + // 测试 feeRate 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setFeeRate(1.23); + })); + // 测试 merchantId 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setMerchantId(2L); + })); + // 测试 appId 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setAppId(2L); + })); + // 测试 createTime 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setCreateTime(buildTime(2021, 10, 20)); + })); + // 准备参数 + PayChannelPageReqVO reqVO = new PayChannelPageReqVO(); + reqVO.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setRemark("灿灿子的支付渠道"); + reqVO.setFeeRate(0.03); + reqVO.setMerchantId(1L); + reqVO.setAppId(1L); + reqVO.setConfig(JSON.toJSONString(payClientConfig)); + reqVO.setBeginCreateTime(buildTime(2021,11,19)); + reqVO.setEndCreateTime(buildTime(2021,11,21)); + + // 调用 + PageResult pageResult = channelService.getChannelPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbChannel, pageResult.getList().get(0), "config"); + assertPojoEquals(payClientConfig, pageResult.getList().get(0).getConfig()); + + } + + @Test + public void testGetChannelList() { + // mock 数据 + AlipayPayClientConfig payClientConfig = getCertificateConfig(); + PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到 + o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("灿灿子的支付渠道"); + o.setFeeRate(0.03); + o.setMerchantId(1L); + o.setAppId(1L); + o.setConfig(payClientConfig); + o.setCreateTime(buildTime(2021,11,20)); + }); + channelMapper.insert(dbChannel); + // 执行拷贝的时候会出现异常,所以在插入后要重置为null 后续在写入新的 + dbChannel.setConfig(null); + // 测试 code 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setCode(PayChannelEnum.WX_PUB.getCode()); + })); + // 测试 status 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + })); + // 测试 remark 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o ->{ + o.setConfig(payClientConfig); + o.setRemark("敏敏子的渠道"); + })); + // 测试 feeRate 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setFeeRate(1.23); + })); + // 测试 merchantId 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setMerchantId(2L); + })); + // 测试 appId 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setAppId(2L); + })); + // 测试 createTime 不匹配 + channelMapper.insert(cloneIgnoreId(dbChannel, o -> { + o.setConfig(payClientConfig); + o.setCreateTime(buildTime(2021, 10, 20)); + })); + // 准备参数 + PayChannelExportReqVO reqVO = new PayChannelExportReqVO(); + reqVO.setCode(PayChannelEnum.ALIPAY_APP.getCode()); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setRemark("灿灿子的支付渠道"); + reqVO.setFeeRate(0.03); + reqVO.setMerchantId(1L); + reqVO.setAppId(1L); + reqVO.setConfig(JSON.toJSONString(payClientConfig)); + reqVO.setBeginCreateTime(buildTime(2021,11,19)); + reqVO.setEndCreateTime(buildTime(2021,11,21)); + + // 调用 + List list = channelService.getChannelList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbChannel, list.get(0), "config"); + assertPojoEquals(payClientConfig, list.get(0).getConfig()); + } + + + public WXPayClientConfig getV2Config() { + return new WXPayClientConfig() + .setAppId("APP00001") + .setMchId("MCH00001") + .setApiVersion(WXPayClientConfig.API_VERSION_V2) + .setMchKey("dsa1d5s6a1d6sa16d1sa56d15a61das6") + .setApiV3Key("") + .setPrivateCertContent("") + .setPrivateKeyContent(""); + } + + public WXPayClientConfig getV3Config() { + return new WXPayClientConfig() + .setAppId("APP00001") + .setMchId("MCH00001") + .setApiVersion(WXPayClientConfig.API_VERSION_V3) + .setMchKey("") + .setApiV3Key("sdadasdsadadsa") + .setPrivateKeyContent("dsa445das415d15asd16ad156as") + .setPrivateCertContent("dsadasd45asd4s5a"); + + } + + public AlipayPayClientConfig getPublicKeyConfig() { + return new AlipayPayClientConfig() + .setServerUrl(AlipayPayClientConfig.SERVER_URL_PROD) + .setAppId("APP00001") + .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT) + .setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY) + .setPrivateKey("13131321312") + .setAlipayPublicKey("13321321321") + .setAppCertContent("") + .setAlipayPublicCertContent("") + .setRootCertContent(""); + } + + public AlipayPayClientConfig getCertificateConfig() { + return new AlipayPayClientConfig() + .setServerUrl(AlipayPayClientConfig.SERVER_URL_PROD) + .setAppId("APP00001") + .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT) + .setMode(AlipayPayClientConfig.MODE_CERTIFICATE) + .setPrivateKey("") + .setAlipayPublicKey("") + .setAppCertContent("13321321321sda") + .setAlipayPublicCertContent("13321321321aqeqw") + .setRootCertContent("13321321321dsad"); + } + + +} diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantServiceTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantServiceTest.java new file mode 100644 index 000000000..0e7cf135d --- /dev/null +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantServiceTest.java @@ -0,0 +1,193 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.merchant; + +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.adminserver.BaseDbUnitTest; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantCreateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantUpdateReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.impl.PayMerchantServiceImpl; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_MERCHANT_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.*; + +/** +* {@link PayMerchantServiceImpl} 的单元测试类 +* +* @author aquan +*/ +@Import(PayMerchantServiceImpl.class) +public class PayMerchantServiceTest extends BaseDbUnitTest { + + @Resource + private PayMerchantServiceImpl merchantService; + + @Resource + private PayMerchantMapper merchantMapper; + + @Test + public void testCreateMerchant_success() { + // 准备参数 + PayMerchantCreateReqVO reqVO = randomPojo(PayMerchantCreateReqVO.class,o -> + o.setStatus(RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())); + + // 调用 + Long merchantId = merchantService.createMerchant(reqVO); + // 断言 + assertNotNull(merchantId); + // 校验记录的属性是否正确 + PayMerchantDO merchant = merchantMapper.selectById(merchantId); + assertPojoEquals(reqVO, merchant); + } + + @Test + public void testUpdateMerchant_success() { + // mock 数据 + PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> + o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + merchantMapper.insert(dbMerchant);// @Sql: 先插入出一条存在的数据 + // 准备参数 + PayMerchantUpdateReqVO reqVO = randomPojo(PayMerchantUpdateReqVO.class, o -> { + o.setId(dbMerchant.getId()); // 设置更新的 ID + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + }); + + // 调用 + merchantService.updateMerchant(reqVO); + // 校验是否更新正确 + PayMerchantDO merchant = merchantMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, merchant); + } + + @Test + public void testUpdateMerchant_notExists() { + // 准备参数 + PayMerchantUpdateReqVO reqVO = randomPojo(PayMerchantUpdateReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> merchantService.updateMerchant(reqVO), PAY_MERCHANT_NOT_EXISTS); + } + + @Test + public void testDeleteMerchant_success() { + // mock 数据 + PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, + o-> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + merchantMapper.insert(dbMerchant);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbMerchant.getId(); + + // 调用 + merchantService.deleteMerchant(id); + // 校验数据不存在了 + assertNull(merchantMapper.selectById(id)); + } + + @Test + public void testDeleteMerchant_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> merchantService.deleteMerchant(id), PAY_MERCHANT_NOT_EXISTS); + } + + @Test + public void testGetMerchantPage() { + // mock 数据 + PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到 + o.setNo("M1008611"); + o.setName("灿哥的杂货铺"); + o.setShortName("灿灿子"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("灿哥的杂货铺"); + o.setCreateTime(buildTime(2021,11,3)); + }); + merchantMapper.insert(dbMerchant); + // 测试 no 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setNo("M200000"))); + // 测试 name 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setName("斌哥的杂货铺"))); + // 测试 shortName 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setShortName("斌斌子"))); + // 测试 status 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 remark 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setRemark("斌哥的杂货铺"))); + // 测试 createTime 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setCreateTime(buildTime(2022,12,4)))); + // 准备参数 + PayMerchantPageReqVO reqVO = new PayMerchantPageReqVO(); + reqVO.setNo("M1008611"); + reqVO.setName("灿哥的杂货铺"); + reqVO.setShortName("灿灿子"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setRemark("灿哥的杂货铺"); + reqVO.setBeginCreateTime(buildTime(2021,11,2)); + reqVO.setEndCreateTime(buildTime(2021,11,4)); + + // 调用 + PageResult pageResult = merchantService.getMerchantPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbMerchant, pageResult.getList().get(0)); + } + + @Test + public void testGetMerchantList() { + // mock 数据 + PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到 + o.setNo("M1008611"); + o.setName("灿哥的杂货铺"); + o.setShortName("灿灿子"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setRemark("灿哥的杂货铺"); + o.setCreateTime(buildTime(2021,11,3)); + }); + merchantMapper.insert(dbMerchant); + // 测试 no 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setNo("M200000"))); + // 测试 name 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setName("斌哥的杂货铺"))); + // 测试 shortName 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setShortName("斌斌子"))); + // 测试 status 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 remark 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setRemark("斌哥的杂货铺"))); + // 测试 createTime 不匹配 + merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setCreateTime(buildTime(2022,12,4)))); + // 准备参数 + PayMerchantExportReqVO reqVO = new PayMerchantExportReqVO(); + reqVO.setNo("M1008611"); + reqVO.setName("灿哥的杂货铺"); + reqVO.setShortName("灿灿子"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setRemark("灿哥的杂货铺"); + reqVO.setBeginCreateTime(buildTime(2021,11,2)); + reqVO.setEndCreateTime(buildTime(2021,11,4)); + + // 调用 + List list = merchantService.getMerchantList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbMerchant, list.get(0)); + } + +} diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderServiceTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderServiceTest.java new file mode 100755 index 000000000..85b00a2d0 --- /dev/null +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderServiceTest.java @@ -0,0 +1,196 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.order; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.adminserver.BaseDbUnitTest; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.impl.PayOrderServiceImpl; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link PayOrderServiceImpl} 的单元测试类 + * + * @author 芋艿 + */ +@Import(PayOrderServiceImpl.class) +public class PayOrderServiceTest extends BaseDbUnitTest { + + @Resource + private PayOrderServiceImpl orderService; + + @Resource + private PayOrderMapper orderMapper; + + public String generateNo() { + return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999); + } + + @Test + public void testGetOrderPage() { + + String merchantOrderId = generateNo(); + String channelOrderId = generateNo(); + + // mock 数据 + PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 + o.setMerchantId(1L); + o.setAppId(1L); + o.setChannelId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setMerchantOrderId(merchantOrderId); + o.setSubject("灿灿子的炸弹猫"); + o.setBody("斌斌子送给灿灿子的炸弹猫"); + o.setNotifyUrl("https://hc.com/lbh"); + o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + o.setAmount(10000L); + o.setChannelFeeRate(0.01); + o.setChannelFeeAmount(1L); + o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + o.setUserIp("127.0.0.1"); + o.setCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0)); + o.setExpireTime(DateUtils.buildTime(2018, 1, 1, 10, 30, 0)); + o.setSuccessTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 2)); + o.setNotifyTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15)); + o.setSuccessExtensionId(1L); + o.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + o.setRefundTimes(0); + o.setRefundAmount(0L); + o.setChannelUserId("1008611"); + o.setChannelOrderNo(channelOrderId); + o.setUpdateTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15)); + }); + orderMapper.insert(dbOrder); + // 测试 merchantId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantId(2L))); + // 测试 appId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L))); + // 测试 channelId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelId(2L))); + // 测试 channelCode 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantOrderId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(generateNo()))); + // 测试 notifyStatus 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus()))); + // 测试 status 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); + // 测试 refundStatus 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus()))); + // 测试 createTime 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(DateUtils.buildTime(2019, 1, 1, 10, 10, + 1)))); + // 准备参数 + PayOrderPageReqVO reqVO = new PayOrderPageReqVO(); + reqVO.setMerchantId(1L); + reqVO.setAppId(1L); + reqVO.setChannelId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantOrderId(merchantOrderId); + reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + reqVO.setBeginCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0)); + reqVO.setEndCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0)); + + // 调用 + PageResult pageResult = orderService.getOrderPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbOrder, pageResult.getList().get(0)); + // assertEquals(0, dbOrder.getUpdateTime().compareTo(pageResult.getList().get(0).getUpdateTime())); + } + + @Test + public void testGetOrderList() { + // mock 数据 + String merchantOrderId = generateNo(); + String channelOrderId = generateNo(); + PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 + o.setMerchantId(1L); + o.setAppId(1L); + o.setChannelId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setMerchantOrderId(merchantOrderId); + o.setSubject("灿灿子的炸弹猫"); + o.setBody("斌斌子送给灿灿子的炸弹猫"); + o.setNotifyUrl("https://hc.com/lbh"); + o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + o.setAmount(10000L); + o.setChannelFeeRate(0.01); + o.setChannelFeeAmount(1L); + o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + o.setUserIp("127.0.0.1"); + o.setCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0)); + o.setExpireTime(DateUtils.buildTime(2018, 1, 1, 10, 30, 0)); + o.setSuccessTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 2)); + o.setNotifyTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15)); + o.setSuccessExtensionId(1L); + o.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + o.setRefundTimes(0); + o.setRefundAmount(0L); + o.setChannelUserId("1008611"); + o.setChannelOrderNo(channelOrderId); + o.setUpdateTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15)); + + }); + orderMapper.insert(dbOrder); + // 测试 merchantId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantId(2L))); + // 测试 appId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L))); + // 测试 channelId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelId(2L))); + // 测试 channelCode 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantOrderId 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(generateNo()))); + // 测试 notifyStatus 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus()))); + // 测试 status 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); + // 测试 refundStatus 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus()))); + // 测试 createTime 不匹配 + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(DateUtils.buildTime(2019, 1, 1, 10, 10, + 1)))); + // 准备参数 + PayOrderExportReqVO reqVO = new PayOrderExportReqVO(); + reqVO.setMerchantId(1L); + reqVO.setAppId(1L); + reqVO.setChannelId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantOrderId(merchantOrderId); + reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); + reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + reqVO.setBeginCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0)); + reqVO.setEndCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0)); + + // 调用 + List list = orderService.getOrderList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbOrder, list.get(0)); + } + +} diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/refund/PayRefundServiceTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/refund/PayRefundServiceTest.java new file mode 100755 index 000000000..ee19dba14 --- /dev/null +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/refund/PayRefundServiceTest.java @@ -0,0 +1,183 @@ +package cn.iocoder.yudao.adminserver.modules.pay.service.refund; + +import cn.iocoder.yudao.adminserver.BaseDbUnitTest; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO; +import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.adminserver.modules.pay.service.order.impl.PayRefundServiceImpl; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/** + * {@link PayRefundServiceImpl} 的单元测试类 + * + * @author aquan + */ +@Import(PayRefundServiceImpl.class) +public class PayRefundServiceTest extends BaseDbUnitTest { + + @Resource + private PayRefundServiceImpl refundService; + + @Resource + private PayRefundMapper refundMapper; + + + @Test + public void testGetRefundPage() { + // mock 数据 + PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 + o.setMerchantId(1L); + o.setAppId(1L); + o.setChannelId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setOrderId(1L); + o.setTradeNo("OT0000001"); + o.setMerchantOrderId("MOT0000001"); + o.setMerchantRefundNo("MRF0000001"); + o.setNotifyUrl("https://www.cancanzi.com"); + o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); + o.setType(PayRefundTypeEnum.SOME.getStatus()); + o.setPayAmount(100L); + o.setRefundAmount(500L); + o.setReason("就是想退款了,你有意见吗"); + o.setUserIp("127.0.0.1"); + o.setChannelOrderNo("CH0000001"); + o.setChannelRefundNo("CHR0000001"); + o.setChannelErrorCode(""); + o.setChannelErrorMsg(""); + o.setChannelExtras(""); + o.setExpireTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 30)); + o.setSuccessTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 15)); + o.setNotifyTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 20)); + o.setCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10)); + o.setUpdateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 35)); + }); + refundMapper.insert(dbRefund); + // 测试 merchantId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantId(2L))); + // 测试 appId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); + // 测试 channelCode 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantRefundNo 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112"))); + // 测试 notifyStatus 不匹配 + refundMapper.insert( + cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus()))); + // 测试 status 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus()))); + // 测试 type 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus()))); + // 测试 createTime 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> + o.setCreateTime(DateUtils.buildTime(2022, 1, 1, 10, 10, 10)))); + // 准备参数 + PayRefundPageReqVO reqVO = new PayRefundPageReqVO(); + reqVO.setMerchantId(1L); + reqVO.setAppId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantRefundNo("MRF0000001"); + reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); + reqVO.setType(PayRefundTypeEnum.SOME.getStatus()); + reqVO.setBeginCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10)); + reqVO.setEndCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 12)); + + + // 调用 + PageResult pageResult = refundService.getRefundPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbRefund, pageResult.getList().get(0)); + } + + @Test + public void testGetRefundList() { + // mock 数据 + PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 + o.setMerchantId(1L); + o.setAppId(1L); + o.setChannelId(1L); + o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + o.setOrderId(1L); + o.setTradeNo("OT0000001"); + o.setMerchantOrderId("MOT0000001"); + o.setMerchantRefundNo("MRF0000001"); + o.setNotifyUrl("https://www.cancanzi.com"); + o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); + o.setType(PayRefundTypeEnum.SOME.getStatus()); + o.setPayAmount(100L); + o.setRefundAmount(500L); + o.setReason("就是想退款了,你有意见吗"); + o.setUserIp("127.0.0.1"); + o.setChannelOrderNo("CH0000001"); + o.setChannelRefundNo("CHR0000001"); + o.setChannelErrorCode(""); + o.setChannelErrorMsg(""); + o.setChannelExtras(""); + o.setExpireTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 30)); + o.setSuccessTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 15)); + o.setNotifyTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 20)); + o.setCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10)); + o.setUpdateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 35)); + }); + refundMapper.insert(dbRefund); + // 测试 merchantId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantId(2L))); + // 测试 appId 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); + // 测试 channelCode 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); + // 测试 merchantRefundNo 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112"))); + // 测试 notifyStatus 不匹配 + refundMapper.insert( + cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus()))); + // 测试 status 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus()))); + // 测试 type 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus()))); + // 测试 createTime 不匹配 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> + o.setCreateTime(DateUtils.buildTime(2022, 1, 1, 10, 10, 10)))); + + // 准备参数 + PayRefundExportReqVO reqVO = new PayRefundExportReqVO(); + reqVO.setMerchantId(1L); + reqVO.setAppId(1L); + reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); + reqVO.setMerchantRefundNo("MRF0000001"); + reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); + reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); + reqVO.setType(PayRefundTypeEnum.SOME.getStatus()); + reqVO.setBeginCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10)); + reqVO.setEndCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 12)); + + // 调用 + List list = refundService.getRefundList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbRefund, list.get(0)); + } + +} diff --git a/yudao-admin-server/src/test/resources/sql/clean.sql b/yudao-admin-server/src/test/resources/sql/clean.sql index e5b362062..530e39b31 100644 --- a/yudao-admin-server/src/test/resources/sql/clean.sql +++ b/yudao-admin-server/src/test/resources/sql/clean.sql @@ -26,5 +26,12 @@ DELETE FROM "sys_error_code"; DELETE FROM "sys_social_user"; DELETE FROM "sys_tenant"; +-- pay 开头的 DB +DELETE FROM pay_merchant; +DELETE FROM pay_app; +DELETE FROM pay_channel; +DELETE FROM pay_order; +DELETE FROM pay_refund; + -- bpm 开头的 DB DELETE FROM "bpm_form"; diff --git a/yudao-admin-server/src/test/resources/sql/create_tables.sql b/yudao-admin-server/src/test/resources/sql/create_tables.sql index b6623a5e7..7b78c8e58 100644 --- a/yudao-admin-server/src/test/resources/sql/create_tables.sql +++ b/yudao-admin-server/src/test/resources/sql/create_tables.sql @@ -474,6 +474,131 @@ CREATE TABLE IF NOT EXISTS "sys_tenant" ( ) COMMENT '租户'; +CREATE TABLE IF NOT EXISTS "pay_merchant" +( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "no" varchar(32) NOT NULL, + "name" varchar(64) NOT NULL, + "short_name" varchar(64) NOT NULL, + "status" tinyint NOT NULL, + "remark" varchar(255) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '支付商户信息'; + +CREATE TABLE IF NOT EXISTS "pay_app" +( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(64) NOT NULL, + "status" tinyint NOT NULL, + "remark" varchar(255) DEFAULT NULL, + `pay_notify_url` varchar(1024) NOT NULL, + `refund_notify_url` varchar(1024) NOT NULL, + `merchant_id` bigint(20) NOT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '支付应用信息'; + +CREATE TABLE IF NOT EXISTS "pay_channel" +( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar(32) NOT NULL, + "status" tinyint(4) NOT NULL, + "remark" varchar(255) DEFAULT NULL, + "fee_rate" double NOT NULL DEFAULT 0, + "merchant_id" bigint(20) NOT NULL, + "app_id" bigint(20) NOT NULL, + "config" varchar(10240) NOT NULL, + "creator" varchar(64) NULL DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) NULL DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '支付渠道'; + +CREATE TABLE IF NOT EXISTS `pay_order` +( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `merchant_id` bigint(20) NOT NULL, + `app_id` bigint(20) NOT NULL, + `channel_id` bigint(20) DEFAULT NULL, + `channel_code` varchar(32) DEFAULT NULL, + `merchant_order_id` varchar(64) NOT NULL, + `subject` varchar(32) NOT NULL, + `body` varchar(128) NOT NULL, + `notify_url` varchar(1024) NOT NULL, + `notify_status` tinyint(4) NOT NULL, + `amount` bigint(20) NOT NULL, + `channel_fee_rate` double DEFAULT 0, + `channel_fee_amount` bigint(20) DEFAULT 0, + `status` tinyint(4) NOT NULL, + `user_ip` varchar(50) NOT NULL, + `expire_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `success_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, + `notify_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, + `success_extension_id` bigint(20) DEFAULT NULL COMMENT '支付成功的订单拓展单编号', + `refund_status` tinyint(4) NOT NULL, + `refund_times` tinyint(4) NOT NULL, + `refund_amount` bigint(20) NOT NULL, + `channel_user_id` varchar(255) DEFAULT NULL, + `channel_order_no` varchar(64) DEFAULT NULL, + `creator` varchar(64) DEFAULT '', + `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updater` varchar(64) DEFAULT '', + `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '支付订单'; + + + +CREATE TABLE IF NOT EXISTS `pay_refund` +( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `req_no` varchar(64) NOT NULL, + `merchant_id` bigint(20) NOT NULL, + `app_id` bigint(20) NOT NULL, + `channel_id` bigint(20) NOT NULL, + `channel_code` varchar(32) NOT NULL, + `order_id` bigint(20) NOT NULL, + `trade_no` varchar(64) NOT NULL, + `merchant_order_id` varchar(64) NOT NULL, + `merchant_refund_no` varchar(64) NOT NULL, + `notify_url` varchar(1024) NOT NULL, + `notify_status` tinyint(4) NOT NULL, + `status` tinyint(4) NOT NULL, + `type` tinyint(4) NOT NULL, + `pay_amount` bigint(20) NOT NULL, + `refund_amount` bigint(20) NOT NULL, + `reason` varchar(256) NOT NULL, + `user_ip` varchar(50) NULL DEFAULT NULL, + `channel_order_no` varchar(64) NOT NULL, + `channel_refund_no` varchar(64) NULL DEFAULT NULL, + `channel_error_code` varchar(128) NULL DEFAULT NULL, + `channel_error_msg` varchar(256) NULL DEFAULT NULL, + `channel_extras` varchar(1024) NULL DEFAULT NULL, + `expire_time` datetime(0) NULL DEFAULT NULL, + `success_time` datetime(0) NULL DEFAULT NULL, + `notify_time` datetime(0) NULL DEFAULT NULL, + `creator` varchar(64) NULL DEFAULT '', + `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updater` varchar(64) NULL DEFAULT '', + `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted` bit(1) NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT = '退款订单'; + + + CREATE TABLE IF NOT EXISTS "bpm_form" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" varchar(63) NOT NULL, diff --git a/yudao-admin-ui/package.json b/yudao-admin-ui/package.json index 9e01ad04f..f22b809b9 100644 --- a/yudao-admin-ui/package.json +++ b/yudao-admin-ui/package.json @@ -42,7 +42,7 @@ "clipboard": "2.0.6", "core-js": "3.19.1", "echarts": "4.9.0", - "element-ui": "2.15.0", + "element-ui": "^2.15.6", "file-saver": "2.0.4", "fuse.js": "6.4.3", "highlight.js": "9.18.5", diff --git a/yudao-admin-ui/src/api/pay/app.js b/yudao-admin-ui/src/api/pay/app.js new file mode 100644 index 000000000..b8deeb1a7 --- /dev/null +++ b/yudao-admin-ui/src/api/pay/app.js @@ -0,0 +1,78 @@ +import request from '@/utils/request' + +// 创建支付应用信息 +export function createApp(data) { + return request({ + url: '/pay/app/create', + method: 'post', + data: data + }) +} + +// 更新支付应用信息 +export function updateApp(data) { + return request({ + url: '/pay/app/update', + method: 'put', + data: data + }) +} + +// 支付应用信息状态修改 +export function changeAppStatus(id, status) { + const data = { + id, + status + } + return request({ + url: '/pay/app/update-status', + method: 'put', + data: data + }) +} + +// 删除支付应用信息 +export function deleteApp(id) { + return request({ + url: '/pay/app/delete?id=' + id, + method: 'delete' + }) +} + +// 获得支付应用信息 +export function getApp(id) { + return request({ + url: '/pay/app/get?id=' + id, + method: 'get' + }) +} + +// 获得支付应用信息分页 +export function getAppPage(query) { + return request({ + url: '/pay/app/page', + method: 'get', + params: query + }) +} + +// 导出支付应用信息 Excel +export function exportAppExcel(query) { + return request({ + url: '/pay/app/export-excel', + method: 'get', + params: query, + responseType: 'blob' + }) +} + +// 根据商ID称搜索应用列表 +export function getAppListByMerchantId(merchantId) { + return request({ + url: '/pay/app/list-merchant-id', + params:{ + merchantId:merchantId + }, + method: 'get' + }) +} diff --git a/yudao-admin-ui/src/api/pay/channel.js b/yudao-admin-ui/src/api/pay/channel.js new file mode 100644 index 000000000..de66770bd --- /dev/null +++ b/yudao-admin-ui/src/api/pay/channel.js @@ -0,0 +1,71 @@ +import request from '@/utils/request' + +// 创建支付渠道 +export function createChannel(data) { + return request({ + url: '/pay/channel/create', + method: 'post', + data: data + }) +} + + +// 更新支付渠道 +export function updateChannel(data) { + return request({ + url: '/pay/channel/update', + method: 'put', + data: data + }) +} + +// 删除支付渠道 +export function deleteChannel(id) { + return request({ + url: '/pay/channel/delete?id=' + id, + method: 'delete' + }) +} + +// 获得支付渠道 +// export function getChannel(id) { +// return request({ +// url: '/pay/channel/get?id=' + id, +// method: 'get' +// }) +// } + + + +// 获得支付渠道分页 +export function getChannelPage(query) { + return request({ + url: '/pay/channel/page', + method: 'get', + params: query + }) +} + +// 导出支付渠道Excel +export function exportChannelExcel(query) { + return request({ + url: '/pay/channel/export-excel', + method: 'get', + params: query, + responseType: 'blob' + }) +} + +// 获得支付渠道 +export function getChannel(merchantId,appId,code) { + return request({ + url: '/pay/channel/get-channel', + params:{ + merchantId:merchantId, + appId:appId, + code:code + }, + method: 'get' + }) +} + diff --git a/yudao-admin-ui/src/api/pay/merchant.js b/yudao-admin-ui/src/api/pay/merchant.js new file mode 100644 index 000000000..51b604ee2 --- /dev/null +++ b/yudao-admin-ui/src/api/pay/merchant.js @@ -0,0 +1,77 @@ +import request from '@/utils/request' + +// 创建支付商户信息 +export function createMerchant(data) { + return request({ + url: '/pay/merchant/create', + method: 'post', + data: data + }) +} + +// 更新支付商户信息 +export function updateMerchant(data) { + return request({ + url: '/pay/merchant/update', + method: 'put', + data: data + }) +} + +// 支付商户状态修改 +export function changeMerchantStatus(id, status) { + const data = { + id, + status + } + return request({ + url: '/pay/merchant/update-status', + method: 'put', + data: data + }) +} + +// 删除支付商户信息 +export function deleteMerchant(id) { + return request({ + url: '/pay/merchant/delete?id=' + id, + method: 'delete' + }) +} + +// 获得支付商户信息 +export function getMerchant(id) { + return request({ + url: '/pay/merchant/get?id=' + id, + method: 'get' + }) +} +// 根据商户名称搜索商户列表 +export function getMerchantListByName(name) { + return request({ + url: '/pay/merchant/list-by-name', + params:{ + name:name + }, + method: 'get' + }) +} + +// 获得支付商户信息分页 +export function getMerchantPage(query) { + return request({ + url: '/pay/merchant/page', + method: 'get', + params: query + }) +} + +// 导出支付商户信息 Excel +export function exportMerchantExcel(query) { + return request({ + url: '/pay/merchant/export-excel', + method: 'get', + params: query, + responseType: 'blob' + }) +} diff --git a/yudao-admin-ui/src/api/pay/order.js b/yudao-admin-ui/src/api/pay/order.js new file mode 100755 index 000000000..bff95bca7 --- /dev/null +++ b/yudao-admin-ui/src/api/pay/order.js @@ -0,0 +1,54 @@ +import request from '@/utils/request' + +// 创建支付订单 +export function createOrder(data) { + return request({ + url: '/pay/order/create', + method: 'post', + data: data + }) +} + +// 更新支付订单 +export function updateOrder(data) { + return request({ + url: '/pay/order/update', + method: 'put', + data: data + }) +} + +// 删除支付订单 +export function deleteOrder(id) { + return request({ + url: '/pay/order/delete?id=' + id, + method: 'delete' + }) +} + +// 获得支付订单 +export function getOrder(id) { + return request({ + url: '/pay/order/get?id=' + id, + method: 'get' + }) +} + +// 获得支付订单分页 +export function getOrderPage(query) { + return request({ + url: '/pay/order/page', + method: 'get', + params: query + }) +} + +// 导出支付订单 Excel +export function exportOrderExcel(query) { + return request({ + url: '/pay/order/export-excel', + method: 'get', + params: query, + responseType: 'blob' + }) +} diff --git a/yudao-admin-ui/src/api/pay/refund.js b/yudao-admin-ui/src/api/pay/refund.js new file mode 100755 index 000000000..09b64d5e0 --- /dev/null +++ b/yudao-admin-ui/src/api/pay/refund.js @@ -0,0 +1,54 @@ +import request from '@/utils/request' + +// 创建退款订单 +export function createRefund(data) { + return request({ + url: '/pay/refund/create', + method: 'post', + data: data + }) +} + +// 更新退款订单 +export function updateRefund(data) { + return request({ + url: '/pay/refund/update', + method: 'put', + data: data + }) +} + +// 删除退款订单 +export function deleteRefund(id) { + return request({ + url: '/pay/refund/delete?id=' + id, + method: 'delete' + }) +} + +// 获得退款订单 +export function getRefund(id) { + return request({ + url: '/pay/refund/get?id=' + id, + method: 'get' + }) +} + +// 获得退款订单分页 +export function getRefundPage(query) { + return request({ + url: '/pay/refund/page', + method: 'get', + params: query + }) +} + +// 导出退款订单 Excel +export function exportRefundExcel(query) { + return request({ + url: '/pay/refund/export-excel', + method: 'get', + params: query, + responseType: 'blob' + }) +} diff --git a/yudao-admin-ui/src/assets/icons/svg/config.svg b/yudao-admin-ui/src/assets/icons/svg/config.svg new file mode 100644 index 000000000..79db5fb6a --- /dev/null +++ b/yudao-admin-ui/src/assets/icons/svg/config.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/yudao-admin-ui/src/assets/icons/svg/merchant.svg b/yudao-admin-ui/src/assets/icons/svg/merchant.svg new file mode 100644 index 000000000..f1ecb8127 --- /dev/null +++ b/yudao-admin-ui/src/assets/icons/svg/merchant.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/yudao-admin-ui/src/assets/icons/svg/order.svg b/yudao-admin-ui/src/assets/icons/svg/order.svg new file mode 100644 index 000000000..676ce3c59 --- /dev/null +++ b/yudao-admin-ui/src/assets/icons/svg/order.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/yudao-admin-ui/src/assets/icons/svg/pay.svg b/yudao-admin-ui/src/assets/icons/svg/pay.svg new file mode 100644 index 000000000..ddab05def --- /dev/null +++ b/yudao-admin-ui/src/assets/icons/svg/pay.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/yudao-admin-ui/src/assets/icons/svg/percentSign.svg b/yudao-admin-ui/src/assets/icons/svg/percentSign.svg new file mode 100644 index 000000000..bedbce63d --- /dev/null +++ b/yudao-admin-ui/src/assets/icons/svg/percentSign.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/yudao-admin-ui/src/utils/constants.js b/yudao-admin-ui/src/utils/constants.js index d0251ae68..87ca6e8cc 100644 --- a/yudao-admin-ui/src/utils/constants.js +++ b/yudao-admin-ui/src/utils/constants.js @@ -16,7 +16,7 @@ export const SysCommonStatusEnum = { * 菜单的类型枚举 */ export const SysMenuTypeEnum = { - DIR : 1, // 目录 + DIR: 1, // 目录 MENU: 2, // 菜单 BUTTON: 3 // 按钮 } @@ -90,3 +90,138 @@ export const SysUserSocialTypeEnum = { img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.11/wechat_enterprise.png", } } + +/** + * 支付渠道枚举 + */ +export const PayChannelEnum = { + WX_PUB: { + "code": "wx_pub", + "name": "微信 JSAPI 支付", + }, + WX_LITE: { + "code": "wx_lite", + "name": "微信小程序支付" + }, + WX_APP: { + "code": "wx_app", + "name": "微信 APP 支付" + }, + ALIPAY_PC: { + "code": "alipay_pc", + "name": "支付宝 PC 网站支付" + }, + ALIPAY_WAP: { + "code": "alipay_wap", + "name": "支付宝 WAP 网站支付" + }, + ALIPAY_APP: { + "code": "alipay_app", + "name": "支付宝 APP 支付" + }, + ALIPAY_QR: { + "code": "alipay_qr", + "name": "支付宝扫码支付" + }, +} + +/** + * 支付类型枚举 + */ +export const PayType = { + WECHAT: "WECHAT", + ALIPAY: "ALIPAY" +} + +/** + * 支付订单状态枚举 + */ +export const payOrderStatusEnum = { + WAITING: { + status: 0, + name: '未支付' + }, + SUCCESS: { + status: 10, + name: '已支付' + }, + CLOSED: { + status: 20, + name: '未支付' + } +} + +/** + * 支付订单回调状态枚举 + */ +export const payOrderNotifyStatusEnum = { + NO: { + status: 0, + name: '未通知' + }, + SUCCESS: { + status: 10, + name: '通知成功' + }, + FAILURE: { + status: 20, + name: '通知失败' + } +} + +/** + * 支付订单退款状态枚举 + */ +export const payOrderRefundStatusEnum = { + NO: { + status: 0, + name: '未退款' + }, + SOME: { + status: 10, + name: '部分退款' + }, + ALL: { + status: 20, + name: '全部退款' + } +} + +/** + * 支付退款订单状态枚举 + */ +export const payRefundStatusEnum = { + CREATE:{ + status:0, + name: '退款订单生成' + }, + SUCCESS:{ + status:1, + name: '退款成功' + }, + FAILURE:{ + status:2, + name: '退款失败' + }, + PROCESSING_NOTIFY:{ + status:3, + name: '退款中,渠道通知结果' + }, + PROCESSING_QUERY:{ + status:4, + name: '退款中,系统查询结果' + }, + UNKNOWN_RETRY:{ + status:5, + name: '状态未知,请重试' + }, + UNKNOWN_QUERY:{ + status:6, + name: '状态未知,系统查询结果' + }, + CLOSE:{ + status:99, + name: '退款关闭' + } +} + diff --git a/yudao-admin-ui/src/utils/dict.js b/yudao-admin-ui/src/utils/dict.js index 3d97494e7..e10414d40 100644 --- a/yudao-admin-ui/src/utils/dict.js +++ b/yudao-admin-ui/src/utils/dict.js @@ -41,8 +41,29 @@ export const DICT_TYPE = { BPM_PROCESS_INSTANCE_STATUS: 'bpm_process_instance_status', BPM_PROCESS_INSTANCE_RESULT: 'bpm_process_instance_result', BPM_TASK_ASSIGN_SCRIPT: 'bpm_task_assign_script', - OA_LEAVE_STATUS: 'flow_status', // todo 芋艿:可以删除 - BPM_OA_LEAVE_TYPE: 'bpm_oa_leave_type' + BPM_OA_LEAVE_TYPE: 'bpm_oa_leave_type', + + // pay + // 微信渠道版本 + PAY_CHANNEL_WECHAT_VERSION:'pay_channel_wechat_version', + // 支付渠道支付宝算法类型 + PAY_CHANNEL_ALIPAY_SIGN_TYPE:'pay_channel_alipay_sign_type', + // 支付宝公钥类型 + PAY_CHANNEL_ALIPAY_MODE:'pay_channel_alipay_mode', + // 支付宝网关地址 + PAY_CHANNEL_ALIPAY_SERVER_TYPE:'pay_channel_alipay_server_type', + // 支付渠道编码类型 + PAY_CHANNEL_CODE_TYPE: 'pay_channel_code_type', + // 商户支付订单回调状态 + PAY_ORDER_NOTIFY_STATUS: 'pay_order_notify_status', + // 商户支付订单状态 + PAY_ORDER_STATUS: 'pay_order_status', + // 商户支付订单退款状态 + PAY_ORDER_REFUND_STATUS: 'pay_order_refund_status', + // 退款订单状态 + PAY_REFUND_ORDER_STATUS: 'pay_refund_order_status', + // 退款订单类别 + PAY_REFUND_ORDER_TYPE: 'pay_refund_order_type', } /** diff --git a/yudao-admin-ui/src/utils/ruoyi.js b/yudao-admin-ui/src/utils/ruoyi.js index 82f8aecc5..b25bb755e 100644 --- a/yudao-admin-ui/src/utils/ruoyi.js +++ b/yudao-admin-ui/src/utils/ruoyi.js @@ -7,66 +7,68 @@ const baseURL = process.env.VUE_APP_BASE_API // 日期格式化 export function parseTime(time, pattern) { - if (arguments.length === 0 || !time) { - return null - } - const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time.replace(new RegExp(/-/gm), '/'); - } - if ((typeof time === 'number') && (time.toString().length === 10)) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/'); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str } // 表单重置 export function resetForm(refName) { - if (this.$refs[refName]) { - this.$refs[refName].resetFields(); - } + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } } // 添加日期范围 export function addDateRange(params, dateRange, propName) { const search = params; search.params = {}; - if (null != dateRange && '' !== dateRange) { - if (typeof(propName) === "undefined") { - search["beginTime"] = dateRange[0]; - search["endTime"] = dateRange[1]; - } else { - search["begin" + propName] = dateRange[0]; - search["end" + propName] = dateRange[1]; - } - } - return search; + if (null != dateRange && '' !== dateRange) { + if (typeof (propName) === "undefined") { + search["beginTime"] = dateRange[0]; + search["endTime"] = dateRange[1]; + } else { + search["begin" + propName] = dateRange[0]; + search["end" + propName] = dateRange[1]; + } + } + return search; } /** @@ -98,21 +100,21 @@ export function addBeginAndEndTime(params, dateRange, propName) { return params; } -// 回显数据字典 +// 回显数据字典 原若依所保留,请使用 dict.js 中的新方法 export function selectDictLabel(datas, value) { - var actions = []; - Object.keys(datas).some((key) => { - if (datas[key].dictValue == ('' + value)) { - actions.push(datas[key].dictLabel); - return true; - } - }) - return actions.join(''); + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].dictValue == ('' + value)) { + actions.push(datas[key].dictLabel); + return true; + } + }) + return actions.join(''); } // 通用下载方法 export function download(fileName) { - window.location.href = baseURL + "/common/download?fileName=" + encodeURI(fileName) + "&delete=" + true; + window.location.href = baseURL + "/common/download?fileName=" + encodeURI(fileName) + "&delete=" + true; } // 下载 Excel 方法 @@ -147,7 +149,7 @@ function download0(data, fileName, mineType) { window.URL = window.URL || window.webkitURL; let href = URL.createObjectURL(blob); let downA = document.createElement("a"); - downA.href = href; + downA.href = href; downA.download = fileName; downA.click(); // 销毁超连接 @@ -156,24 +158,24 @@ function download0(data, fileName, mineType) { // 字符串格式化(%s ) export function sprintf(str) { - var args = arguments, flag = true, i = 1; - str = str.replace(/%s/g, function () { - var arg = args[i++]; - if (typeof arg === 'undefined') { - flag = false; - return ''; - } - return arg; - }); - return flag ? str : ''; + var args = arguments, flag = true, i = 1; + str = str.replace(/%s/g, function () { + var arg = args[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; } // 转换字符串,undefined,null等转化为"" export function praseStrEmpty(str) { - if (!str || str == "undefined" || str == "null") { - return ""; - } - return str; + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; } /** @@ -185,21 +187,42 @@ export function praseStrEmpty(str) { * @param {*} rootId 根Id 默认 0 */ export function handleTree(data, id, parentId, children, rootId) { - id = id || 'id' - parentId = parentId || 'parentId' - children = children || 'children' - rootId = rootId || Math.min.apply(Math, data.map(item => { return item[parentId] })) || 0 - //对源数据深度克隆 - const cloneData = JSON.parse(JSON.stringify(data)) - //循环所有项 - const treeData = cloneData.filter(father => { - let branchArr = cloneData.filter(child => { - //返回每一项的子级数组 - return father[id] === child[parentId] - }); - branchArr.length > 0 ? father.children = branchArr : ''; - //返回第一层 - return father[parentId] === rootId; - }); - return treeData !== '' ? treeData : data; + id = id || 'id' + parentId = parentId || 'parentId' + children = children || 'children' + rootId = rootId || Math.min.apply(Math, data.map(item => { + return item[parentId] + })) || 0 + //对源数据深度克隆 + const cloneData = JSON.parse(JSON.stringify(data)) + //循环所有项 + const treeData = cloneData.filter(father => { + let branchArr = cloneData.filter(child => { + //返回每一项的子级数组 + return father[id] === child[parentId] + }); + branchArr.length > 0 ? father.children = branchArr : ''; + //返回第一层 + return father[parentId] === rootId; + }); + return treeData !== '' ? treeData : data; +} + +/** + * 获取当前时间 + * @param timeStr 时分秒 字符串 格式为 xx:xx:xx + */ +export function getNowDateTime(timeStr) { + let now = new Date(); + let year = now.getFullYear(); //得到年份 + let month = (now.getMonth() + 1).toString().padStart(2, "0"); //得到月份 + let day = now.getDate().toString().padStart(2, "0"); //得到日期 + + if (timeStr != null) { + return `${year}-${month}-${day} ${timeStr}`; + } + let hours = now.getHours().toString().padStart(2, "0") // 得到小时; + let minutes = now.getMinutes().toString().padStart(2, "0") // 得到分钟; + let seconds = now.getSeconds().toString().padStart(2, "0") // 得到秒; + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } diff --git a/yudao-admin-ui/src/views/pay/app/components/aliPayChannelForm.vue b/yudao-admin-ui/src/views/pay/app/components/aliPayChannelForm.vue new file mode 100644 index 000000000..58d40e768 --- /dev/null +++ b/yudao-admin-ui/src/views/pay/app/components/aliPayChannelForm.vue @@ -0,0 +1,358 @@ + + + diff --git a/yudao-admin-ui/src/views/pay/app/components/wechatChannelForm.vue b/yudao-admin-ui/src/views/pay/app/components/wechatChannelForm.vue new file mode 100644 index 000000000..3ca70c3bd --- /dev/null +++ b/yudao-admin-ui/src/views/pay/app/components/wechatChannelForm.vue @@ -0,0 +1,299 @@ + + + diff --git a/yudao-admin-ui/src/views/pay/app/index.vue b/yudao-admin-ui/src/views/pay/app/index.vue new file mode 100644 index 000000000..6b1e614fb --- /dev/null +++ b/yudao-admin-ui/src/views/pay/app/index.vue @@ -0,0 +1,492 @@ + + + diff --git a/yudao-admin-ui/src/views/pay/merchant/index.vue b/yudao-admin-ui/src/views/pay/merchant/index.vue new file mode 100644 index 000000000..6aa597357 --- /dev/null +++ b/yudao-admin-ui/src/views/pay/merchant/index.vue @@ -0,0 +1,296 @@ + + + diff --git a/yudao-admin-ui/src/views/pay/order/index.vue b/yudao-admin-ui/src/views/pay/order/index.vue new file mode 100755 index 000000000..cb77d1dd2 --- /dev/null +++ b/yudao-admin-ui/src/views/pay/order/index.vue @@ -0,0 +1,528 @@ + + + + diff --git a/yudao-admin-ui/src/views/pay/refund/index.vue b/yudao-admin-ui/src/views/pay/refund/index.vue new file mode 100755 index 000000000..ca9966a3c --- /dev/null +++ b/yudao-admin-ui/src/views/pay/refund/index.vue @@ -0,0 +1,542 @@ + + + + diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java new file mode 100644 index 000000000..4a4ada555 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.coreservice.modules.pay.convert.order; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayRefundCoreConvert { + + PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class); + + //TODO 太多需要处理了, 暂时不用 + @Mappings(value = { + @Mapping(source = "amount", target = "payAmount"), + @Mapping(source = "id", target = "orderId"), + @Mapping(target = "status",ignore = true) + }) + PayRefundDO convert(PayOrderDO orderDO); + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayChannelDO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayChannelDO.java index 2101e6634..5275dd428 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayChannelDO.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayChannelDO.java @@ -46,6 +46,10 @@ public class PayChannelDO extends BaseDO { * 渠道费率,单位:百分比 */ private Double feeRate; + /** + * 备注 + */ + private String remark; /** * 商户编号 diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayMerchantDO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayMerchantDO.java index d2b20111a..a0c72b9ec 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayMerchantDO.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayMerchantDO.java @@ -2,6 +2,8 @@ package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; @@ -29,6 +31,7 @@ public class PayMerchantDO extends BaseDO { /** * 商户号 * 例如说,M233666999 + * 只有新增时插入,不允许修改 */ private String no; /** diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java index ccaed8a2c..65cf94bf2 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum; -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; @@ -135,7 +135,7 @@ public class PayOrderDO extends BaseDO { /** * 退款状态 * - * 枚举 {@link PayOrderRefundStatusEnum} + * 枚举 {@link PayRefundTypeEnum} */ private Integer refundStatus; /** diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderExtensionDO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderExtensionDO.java index 92347647a..5a6b8431a 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderExtensionDO.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderExtensionDO.java @@ -16,7 +16,7 @@ import java.util.Map; * * @author 芋道源码 */ -@TableName("pay_order_extension") +@TableName(value = "pay_order_extension",autoResultMap = true) @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java index 77e5a51e7..fc897a5e0 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java @@ -3,9 +3,13 @@ package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import lombok.Data; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; import java.util.Date; @@ -17,19 +21,21 @@ import java.util.Date; * * @author 芋道源码 */ +@TableName("pay_refund") @Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor public class PayRefundDO extends BaseDO { /** * 退款单编号,数据库自增 */ + @TableId private Long id; - /** - * 退款单号,根据规则生成 - * - * 例如说,R202109181134287570000 - */ - private String no; + /** * 商户编号 * @@ -61,23 +67,48 @@ public class PayRefundDO extends BaseDO { */ private Long orderId; + /** + * 交易订单号,根据规则生成 + * 调用支付渠道时,使用该字段作为对接的订单号。 + * 1. 调用微信支付 https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 时,使用该字段作为 out_trade_no + * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no + * 这里对应 pay_extension 里面的 no + * 例如说,P202110132239124200055 + */ + private String tradeNo; + + // ========== 商户相关字段 ========== /** - * 商户退款订单号 - * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一 TODO 芋艿:需要在测试下 + * 商户订单编号 */ + private String merchantOrderId; + + /** + * 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。 + * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一 + * 个商户退款订单,对应一条退款请求记录。可多次提交。 渠道保持幂等 + * 使用商户退款单,作为退款请求号 + * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml 中的 out_refund_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no + * 退款请求号。 + * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。 + * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更, + * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。 + * 退款单请求号,根据规则生成 + * 例如说,R202109181134287570000 + */ + // TODO @jason:merchantRefundNo =》merchantRefundOId private String merchantRefundNo; -// /** -// * 商户拓展参数 -// */ -// private String merchantExtra; + /** * 异步通知地址 */ private String notifyUrl; + /** * 通知商户退款结果的回调状态 - * TODO 芋艿:0 未发送 1 已发送 + * TODO 0 未发送 1 已发送 */ private Integer notifyStatus; @@ -85,44 +116,81 @@ public class PayRefundDO extends BaseDO { /** * 退款状态 * - * TODO 芋艿:状态枚举 + * 枚举 {@link PayRefundStatusEnum} */ private Integer status; + /** - * 用户 IP + * 退款类型(部分退款,全部退款) + * + * 枚举 {@link PayRefundTypeEnum} */ - private String userIp; + private Integer type; + /** + * 支付金额,单位:分 + */ + private Long payAmount; /** * 退款金额,单位:分 */ - private Long amount; + private Long refundAmount; + /** * 退款原因 */ private String reason; + /** - * 订单退款成功时间 + * 用户 IP */ - private Date successTime; + private String userIp; + + // ========== 渠道相关字段 ========== /** + * 渠道订单号,pay_order 中的channel_order_no 对应 + */ + private String channelOrderNo; + /** + * 微信中的 refund_id + * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml + * 支付宝没有 + * 渠道退款单号,渠道返回 + */ + private String channelRefundNo; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + + /** + * 支付渠道的额外参数 + * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html + */ + private String channelExtras; + + + /** + * TODO * 退款失效时间 */ private Date expireTime; /** - * 支付渠道的额外参数 - * - * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html + * 退款成功时间 */ - private String channelExtra; + private Date successTime; + /** + * 退款通知时间 + */ + private Date notifyTime; + + - // ========== 渠道相关字段 ========== - /** - * 渠道订单号 - */ - private String channelOrderNo; - /** - * 渠道退款号 - */ - private String channelRefundNo; } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundCoreMapper.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundCoreMapper.java new file mode 100644 index 000000000..8de1396dd --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundCoreMapper.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + + +/** + * 退款订单 Mapper + * + */ +@Mapper +public interface PayRefundCoreMapper extends BaseMapperX { + + default PayRefundDO selectByReqNo(String reqNo) { + return selectOne("req_no", reqNo); + } + + default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){ + return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo); + } + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java index d2b940223..f5133ad05 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java @@ -9,23 +9,56 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode; */ public interface PayErrorCodeCoreConstants { - // ========== APP 模块 1-007-000-000 ========== + /** + * ========== APP 模块 1-007-000-000 ========== + */ ErrorCode PAY_APP_NOT_FOUND = new ErrorCode(1007000000, "App 不存在"); ErrorCode PAY_APP_IS_DISABLE = new ErrorCode(1007000002, "App 已经被禁用"); + ErrorCode PAY_APP_EXIST_TRANSACTION_ORDER_CANT_DELETE = new ErrorCode(1007000003, "支付应用存在交易中的订单,无法删除"); - // ========== CHANNEL 模块 1-007-001-000 ========== + /** + * ========== CHANNEL 模块 1-007-001-000 ========== + */ ErrorCode PAY_CHANNEL_NOT_FOUND = new ErrorCode(1007001000, "支付渠道的配置不存在"); ErrorCode PAY_CHANNEL_IS_DISABLE = new ErrorCode(1007001001, "支付渠道已经禁用"); ErrorCode PAY_CHANNEL_CLIENT_NOT_FOUND = new ErrorCode(1007001002, "支付渠道的客户端不存在"); - - // ========== ORDER 模块 1-007-002-000 ========== + ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在"); + ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道"); + ErrorCode CHANNEL_WECHAT_VERSION_2_MCH_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v2版本中商户密钥不可为空"); + ErrorCode CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001007,"微信渠道v3版本apiclient_key.pem不可为空"); + ErrorCode CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL = new ErrorCode(1007001008,"微信渠道v3版本中apiclient_cert.pem不可为空"); + ErrorCode PAY_CHANNEL_NOTIFY_VERIFY_FAILED = new ErrorCode(1007001009, "渠道通知校验失败"); + /** + * ========== ORDER 模块 1-007-002-000 ========== + */ ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1007002000, "支付订单不存在"); ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付"); ErrorCode PAY_ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付"); ErrorCode PAY_ORDER_ERROR_USER = new ErrorCode(1007002003, "支付订单用户不正确"); - // ========== ORDER 模块(拓展单) 1-007-003-000 ========== + + /** + * ========== ORDER 模块(拓展单) 1-007-003-000 ========== + */ ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在"); ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付"); ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007003002, "支付订单不处于已支付"); + // ========== 支付模块(退款) 1-007-006-000 ========== + ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额"); + ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款"); + ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空"); + 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, "支付商户存在支付应用,无法删除"); + + + + + } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java new file mode 100644 index 000000000..2a28ae9fc --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.coreservice.modules.pay.enums.order; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PayRefundStatusEnum { + + CREATE(0, "退款订单生成"), + SUCCESS(1, "退款成功"), + FAILURE(2, "退款失败"), + CLOSE(99, "退款关闭"); + + private final Integer status; + private final String name; +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundTypeEnum.java similarity index 88% rename from yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java rename to yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundTypeEnum.java index 3c283a9d5..ba7ee2fcc 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundTypeEnum.java @@ -11,7 +11,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum PayOrderRefundStatusEnum implements IntArrayValuable { +public enum PayRefundTypeEnum implements IntArrayValuable { NO(0, "未退款"), SOME(10, "部分退款"), diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java index 4e7b91547..dff6de7fc 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java @@ -5,8 +5,10 @@ import cn.hutool.http.HttpUtil; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyLogDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyLogCoreMapper; import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyTaskCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper; import cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify.PayNotifyLockCoreRedisDAO; import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum; import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum; @@ -72,6 +74,9 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService { @Resource private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO; + @Resource + private PayRefundCoreMapper payRefundCoreMapper; + @Resource @Lazy // 循环依赖(自己依赖自己),避免报错 private PayNotifyCoreServiceImpl self; @@ -89,7 +94,9 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService { setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl()); } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { // TODO 芋艿,需要实现下哈 - throw new UnsupportedOperationException("需要实现"); + PayRefundDO refundDO = payRefundCoreMapper.selectById(task.getDataId()); + task.setMerchantId(refundDO.getMerchantId()).setAppId(refundDO.getAppId()) + .setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl()); } // 执行插入 diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java index 705800892..d436d759b 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java @@ -17,12 +17,15 @@ import javax.validation.constraints.NotNull; @AllArgsConstructor public class PayRefundOrderReqVO { - @ApiModelProperty(value = "商户订单编号", required = true, example = "10") - @NotEmpty(message = "商户订单号不能为空") + @ApiModelProperty(value = "商户退款单编号", required = true, example = "10") + @NotEmpty(message = "商户退款单编号不能为空") private String merchantOrderId; @ApiModelProperty(value = "支付退款编号", required = true, example = "20") @NotNull(message = "支付退款编号不能为空") private Long payRefundId; + @ApiModelProperty(value = "退款状态(成功,失败)", required = true, example = "10") + private Integer status; + } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java index 002716eac..07e659229 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.coreservice.modules.pay.service.order; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO; -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO; + +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.*; +import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; import javax.validation.Valid; @@ -43,9 +43,8 @@ public interface PayOrderCoreService { * 通知支付单成功 * * @param channelId 渠道编号 - * @param channelCode 渠道编码 * @param notifyData 通知数据 */ - void notifyPayOrder(Long channelId, String channelCode, String notifyData) throws Exception; + void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception; } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java new file mode 100644 index 000000000..b56384ed6 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order; + +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; + +/** + * 退款单 Core Service + * + * @author jason + */ +public interface PayRefundCoreService { + + /** + * 提交退款申请 + * + * @param reqDTO 退款申请信息 + * @return 退款申请返回信息 + */ + PayRefundRespDTO submitRefundOrder(PayRefundReqDTO reqDTO); + + /** + * 渠道的退款通知 + * + * @param channelId 渠道编号 + * @param notifyData 通知数据 + * @throws Exception 退款通知异常 + */ + void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception; + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundReqDTO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundReqDTO.java new file mode 100644 index 000000000..6ec3fb13d --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundReqDTO.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.coreservice.modules.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 Long amount; + + /** + * 退款原因 + */ + private String reason; + + /** + * 商户退款订单号 + */ + @NotEmpty(message = "商户退款订单号不能为空") + private String merchantRefundId; + + /** + * 用户 IP + */ + private String userIp; +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundRespDTO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundRespDTO.java new file mode 100644 index 000000000..d065f3852 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundRespDTO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.coreservice.modules.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; + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java index 5515dd87c..50229b140 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayOrderCoreConvert; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; @@ -26,6 +25,7 @@ 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 lombok.extern.slf4j.Slf4j; @@ -96,6 +96,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { // 订单相关字段 order.setStatus(PayOrderStatusEnum.WAITING.getStatus()); // 退款相关字段 + // todo @芋艿 创建支付的订单的退款状态枚举是不是有问题,应该是 PayRefundTypeEnum 吧 您这填写的是 PayOrderNotifyStatusEnum 回调状态枚举 order.setRefundStatus(PayOrderNotifyStatusEnum.NO.getStatus()) .setRefundTimes(0).setRefundAmount(0L); payOrderCoreMapper.insert(order); @@ -135,9 +136,11 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { // 调用三方接口 PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderCoreConvert.INSTANCE.convert2(reqDTO); // 商户相关字段 + //TODO jason @芋艿 是否加一个属性 如tradeNo 支付订单号, 用这个merchantOrderId让人迷糊 unifiedOrderReqDTO.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性! .setSubject(order.getSubject()).setBody(order.getBody()) - .setNotifyUrl(genChannelPayNotifyUrl(channel)); + .setNotifyUrl(genChannelPayNotifyUrl(channel)) + .setReturnUrl(genChannelReturnUrl(channel)); // 订单相关字段 unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime()); CommonResult unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO); @@ -149,16 +152,24 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { .setInvokeResponse(unifiedOrderResult.getData()); } + /** + * 根据支付渠道的编码,生成支付渠道的返回地址 + * @param channel 支付渠道 + * @return 支付成功返回的地址。 配置地址 + "/" + channel id + */ + private String genChannelReturnUrl(PayChannelDO channel) { + return payProperties.getPayReturnUrl() + "/" + channel.getId(); + } + /** * 根据支付渠道的编码,生成支付渠道的回调地址 * * @param channel 支付渠道 - * @return 支付渠道的回调地址 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id */ private String genChannelPayNotifyUrl(PayChannelDO channel) { - // _ 转化为 - 的原因,是因为 URL 我们统一采用中划线的原则 - return payProperties.getPayNotifyUrl() + "/" + StrUtil.replace(channel.getCode(), "_", "-") - + "/" + channel.getId(); + //去掉channel code, 似乎没啥用, 用统一的回调地址 + return payProperties.getPayNotifyUrl() + "/" + channel.getId(); } private String generateOrderExtensionNo() { @@ -181,9 +192,9 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { @Override @Transactional - public void notifyPayOrder(Long channelId, String channelCode, String notifyData) throws Exception { + public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception { // TODO 芋艿,记录回调日志 - log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData); + log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); // 校验支付渠道是否有效 PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); @@ -193,6 +204,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); } + // 解析支付结果 PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData); @@ -207,9 +219,10 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } // 1.2 更新 PayOrderExtensionDO + //TODO 支付宝交易超时 TRADE_FINISHED 需要更新交易关闭 int updateCounts = payOrderExtensionCoreMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId()) - .status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData).build()); + .status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build()); if (updateCounts == 0) { // 校验状态,必须是待支付 throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } @@ -225,7 +238,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { } // 2.2 更新 PayOrderDO updateCounts = payOrderCoreMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), - PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channelCode) + PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channel.getCode()) .successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId()) .channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId()) .notifyTime(new Date()).build()); diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java new file mode 100644 index 000000000..944c62aa6 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java @@ -0,0 +1,235 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO; +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.enums.PayNotifyRefundStatusEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Objects; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +@Service +@Slf4j +public class PayRefundCoreServiceImpl implements PayRefundCoreService { + + @Resource + private PayOrderCoreMapper payOrderCoreMapper; + @Resource + private PayRefundCoreMapper payRefundCoreMapper; + @Resource + private PayOrderExtensionCoreMapper payOrderExtensionCoreMapper; + + @Resource + private PayAppCoreService payAppCoreService; + @Resource + private PayChannelCoreService payChannelCoreService; + @Resource + private PayNotifyCoreService payNotifyCoreService; + + @Resource + private PayClientFactory payClientFactory; + + @Override + @Transactional(rollbackFor = Exception.class) + public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) { + // 获得 PayOrderDO + PayOrderDO order = payOrderCoreMapper.selectById(req.getPayOrderId()); + // 校验订单是否存在 + if (Objects.isNull(order) ) { + throw exception(PAY_ORDER_NOT_FOUND); + } + // 校验 App + PayAppDO app = payAppCoreService.validPayApp(order.getAppId()); + // 校验支付渠道是否有效 + PayChannelDO channel = payChannelCoreService.validPayChannel(order.getChannelId()); + // 校验支付客户端是否正确初始化 + PayClient client = payClientFactory.getPayClient(channel.getId()); + if (client == null) { + log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); + } + + // 校验退款的条件 + validatePayRefund(req, order); + // 退款类型 + PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME; + if (Objects.equals(req.getAmount(), order.getAmount())) { + refundType = PayRefundTypeEnum.ALL; + } + PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId()); + PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundId()); + if(Objects.nonNull(payRefundDO)){ + // 退款订单已经提交过。 + //TODO 校验相同退款单的金额 + // TODO @jason:咱要不封装一个 ObjectUtils.equalsAny + if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus()) + || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) { + //已成功退款 + throw exception(PAY_REFUND_SUCCEED); + } + //可以重复提交,保证 退款请求号 一致,由渠道保证幂等 + }else { + // 成功,插入退款单 状态为生成.没有和渠道交互 + // TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下; + payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo()) + .appId(order.getAppId()) + .channelOrderNo(order.getChannelOrderNo()) + .channelCode(order.getChannelCode()) + .channelId(order.getChannelId()) + .merchantId(order.getMerchantId()) + .orderId(order.getId()) + .merchantRefundNo(req.getMerchantRefundId()) + .notifyUrl(app.getRefundNotifyUrl()) + .payAmount(order.getAmount()) + .refundAmount(req.getAmount()) + .userIp(req.getUserIp()) + .merchantOrderId(order.getMerchantOrderId()) + .tradeNo(orderExtensionDO.getNo()) + .status(PayRefundStatusEnum.CREATE.getStatus()) + .reason(req.getReason()) + .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus()) + .type(refundType.getStatus()) + .build(); + payRefundCoreMapper.insert(payRefundDO); + } + // TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下; + PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO(); + unifiedReqDTO.setUserIp(req.getUserIp()) + .setAmount(req.getAmount()) + .setChannelOrderNo(order.getChannelOrderNo()) + .setPayTradeNo(orderExtensionDO.getNo()) + .setMerchantRefundId(req.getMerchantRefundId()) + .setReason(req.getReason()); + // 向渠道发起退款申请 + PayCommonResult refundUnifiedResult = client.unifiedRefund(unifiedReqDTO); + // 检查是否失败,失败抛出业务异常。 + // TODO 渠道的异常记录。 + // TODO @jason:可以先打个 warn log 哈; + refundUnifiedResult.checkError(); + // 成功在 退款回调中处理 + return PayRefundRespDTO.builder().refundId(payRefundDO.getId()).build(); + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) { + log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); + // 校验支付渠道是否有效 + PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); + // 校验支付客户端是否正确初始化 + PayClient client = payClientFactory.getPayClient(channel.getId()); + if (client == null) { + log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); + } + // 解析渠道退款通知数据, 统一处理 + PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData); + if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){ + payRefundSuccess(refundNotify); + } else { + //TODO 支付异常, 支付宝似乎没有支付异常的通知。 + // TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知 + } + } + + private void payRefundSuccess(PayRefundNotifyDTO refundNotify) { + // 校验退款单存在 + PayRefundDO refundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo()); + if (refundDO == null) { + log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo()); + throw exception(PAY_REFUND_NOT_FOUND); + } + + // 得到已退金额 + PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId()); + Long refundedAmount = payOrderDO.getRefundAmount(); + + PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS; + if(Objects.equals(payOrderDO.getAmount(), refundedAmount+ refundDO.getRefundAmount())){ + //支付金额 = 已退金额 + 本次退款金额。 + orderStatus = PayOrderStatusEnum.CLOSED; + } + // 更新支付订单 + PayOrderDO updateOrderDO = new PayOrderDO(); + updateOrderDO.setId(refundDO.getOrderId()) + .setRefundAmount(refundedAmount + refundDO.getRefundAmount()) + .setStatus(orderStatus.getStatus()) + .setRefundTimes(payOrderDO.getRefundTimes() + 1) + .setRefundStatus(refundDO.getType()); + payOrderCoreMapper.updateById(updateOrderDO); + + // 更新退款订单 + PayRefundDO updateRefundDO = new PayRefundDO(); + updateRefundDO.setId(refundDO.getId()) + .setSuccessTime(refundNotify.getRefundSuccessTime()) + .setChannelRefundNo(refundNotify.getChannelOrderNo()) + .setTradeNo(refundNotify.getTradeNo()) + .setNotifyTime(new Date()) + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); + payRefundCoreMapper.updateById(updateRefundDO); + + // 插入退款通知记录 + // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调 + payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder() + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build()); + } + + /** + * 校验是否进行退款 + * @param req 退款申请信息 + * @param order 原始支付订单信息 + */ + private void validatePayRefund(PayRefundReqDTO req, PayOrderDO order) { + // 校验状态,必须是支付状态 + if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) { + throw exception(PAY_ORDER_STATUS_IS_NOT_SUCCESS); + } + // 是否已经全额退款 + if (PayRefundTypeEnum.ALL.getStatus().equals(order.getRefundStatus())) { + throw exception(PAY_REFUND_ALL_REFUNDED); + } + // 校验金额 退款金额不能大于 原定的金额 + if (req.getAmount() + order.getRefundAmount() > order.getAmount()){ + throw exception(PAY_REFUND_AMOUNT_EXCEED); + } + // 校验渠道订单号 + if (StrUtil.isEmpty(order.getChannelOrderNo())) { + throw exception(PAY_REFUND_CHN_ORDER_NO_IS_NULL); + } + //TODO 退款的期限 退款次数的控制 + } + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java new file mode 100644 index 000000000..959cef746 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.coreservice.modules.pay.util; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 支付相关编号的生产 + */ +public class PaySeqUtils { + + private static final AtomicLong REFUND_REQ_NO_SEQ = new AtomicLong(0L); + + private static final AtomicLong MER_REFUND_NO_SEQ = new AtomicLong(0L); + + private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L); + + /** + * 生成商户退款单号,用于测试,应该由商户系统生成 + * @return 商户退款单 + */ + public static String genMerchantRefundNo() { + return String.format("%s%s%04d", "MR", + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN), + (int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000); + } + + /** + * 生成退款请求号 + * @return 退款请求号 + */ + public static String genRefundReqNo() { + return String.format("%s%s%04d", "RR", + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN), + (int) REFUND_REQ_NO_SEQ.getAndIncrement() % 10000); + } + + /** + * 生成商户订单编号号 用于测试,应该由商户系统生成 + * @return 商户订单编号 + */ + public static String genMerchantOrderNo() { + return String.format("%s%s%04d", "MO", + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN), + (int) MER_ORDER_NO_SEQ.getAndIncrement() % 10000); + } +} diff --git a/yudao-core-service/src/test-integration/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayChannelCoreMapperTest.java b/yudao-core-service/src/test-integration/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayChannelCoreMapperTest.java index b214f7b65..18445a4ec 100644 --- a/yudao-core-service/src/test-integration/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayChannelCoreMapperTest.java +++ b/yudao-core-service/src/test-integration/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayChannelCoreMapperTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.coreservice.BaseDbAndRedisIntegrationTest; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import org.junit.jupiter.api.Test; @@ -20,10 +21,10 @@ public class PayChannelCoreMapperTest extends BaseDbAndRedisIntegrationTest { private PayChannelCoreMapper payChannelCoreMapper; /** - * 插入初始配置 + * 插入 {@link PayChannelEnum#WX_PUB} 初始配置 */ @Test - public void testInsert() throws FileNotFoundException { + public void testInsertWxPub() throws FileNotFoundException { PayChannelDO payChannelDO = new PayChannelDO(); payChannelDO.setCode(PayChannelEnum.WX_PUB.getCode()); payChannelDO.setStatus(CommonStatusEnum.ENABLE.getStatus()); @@ -44,6 +45,31 @@ public class PayChannelCoreMapperTest extends BaseDbAndRedisIntegrationTest { payChannelCoreMapper.insert(payChannelDO); } + // TODO @ouyang:Zfb 改成 AlipayQr + /** + * 插入 {@link PayChannelEnum#ALIPAY_QR} 初始配置 + */ + @Test + public void testInsertZfb() { + PayChannelDO payChannelDO = new PayChannelDO(); + payChannelDO.setCode(PayChannelEnum.ALIPAY_QR.getCode()); + payChannelDO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + payChannelDO.setFeeRate(1D); + payChannelDO.setMerchantId(1L); + payChannelDO.setAppId(6L); + // 配置 + AlipayPayClientConfig config = new AlipayPayClientConfig(); + config.setAppId("2021000118634035"); + config.setServerUrl(AlipayPayClientConfig.SERVER_URL_SANDBOX); + config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); + config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8="); + config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); + // 创建客户端 + payChannelDO.setConfig(config); + // 执行插入 + payChannelCoreMapper.insert(payChannelDO); + } + /** * 查询所有支付配置,看看是否都是 ok 的 */ diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml index 217aac56d..68ce1ee7d 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml @@ -30,12 +30,8 @@ - jakarta.validation - jakarta.validation-api - - - org.hibernate.validator - hibernate-validator + org.springframework.boot + spring-boot-starter-validation diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java index c56868477..057a75541 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java @@ -6,8 +6,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.time.Duration; @ConfigurationProperties(prefix = "yudao.pay") @Validated @@ -25,8 +23,16 @@ public class PayProperties { * 退款回调地址 * 注意点,同 {@link #payNotifyUrl} 属性 */ - @NotNull(message = "短信发送频率不能为空") + @NotEmpty(message = "退款回调地址不能为空") @URL(message = "退款回调地址的格式必须是 URL") private String refundNotifyUrl; + + /** + * 支付完成的返回地址 + */ + @URL(message = "支付返回的地址的格式必须是 URL") + @NotEmpty(message = "支付返回的地址不能为空") + private String payReturnUrl; + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java index 96a5233fc..5cde0d5a0 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.framework.pay.core.client; -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.*; /** * 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能 @@ -32,6 +32,42 @@ public interface PayClient { * @return 解析结果 * @throws Exception 解析失败,抛出异常 */ - PayOrderNotifyRespDTO parseOrderNotify(String data) throws Exception; + PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception; + + /** + * 调用支付渠道,进行退款 + * @param reqDTO 统一退款请求信息 + * @return 各支付渠道的统一返回结果 + */ + PayCommonResult unifiedRefund(PayRefundUnifiedReqDTO reqDTO); + + /** + * 解析支付退款通知数据 + * @param notifyData 支付退款通知请求数据 + * @return 支付退款通知的Notify DTO + */ + PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData); + + // TODO @芋艿:后续改成非 default,避免不知道去实现 + /** + * 验证是否渠道通知 + * + * @param notifyData 通知数据 + * @return 默认是 true + */ + default boolean verifyNotifyData(PayNotifyDataDTO notifyData) { + return true; + } + + // TODO @芋艿:后续改成非 default,避免不知道去实现 + /** + * 判断是否为退款通知 + * + * @param notifyData 通知数据 + * @return 默认是 false + */ + default boolean isRefundNotify(PayNotifyDataDTO notifyData){ + return false; + } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java index 8d66aedbf..f14893c4e 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java @@ -2,6 +2,11 @@ package cn.iocoder.yudao.framework.pay.core.client; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; +import java.util.Set; + /** * 支付客户端的配置,本质是支付渠道的配置 * 每个不同的渠道,需要不同的配置,通过子类来定义 @@ -13,4 +18,24 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; // 1. 序列化到时数据库时,增加 @class 属性。 // 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型 public interface PayClientConfig { + + /** + * 配置验证参数是 + * + * @param validator 校验对象 + * @return 配置好的验证参数 + */ + Set> verifyParam(Validator validator); + + /** + * 参数校验 + * + * @param validator 校验对象 + */ + default void validate(Validator validator) { + Set> violations = verifyParam(validator); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java new file mode 100644 index 000000000..bbd237e9c --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +import java.util.Map; + + +/** + * 支付订单,退款订单回调,渠道的统一通知请求数据 + */ +@Data +@ToString +@Builder +public class PayNotifyDataDTO { + + + /** + * HTTP 回调接口的 request body + */ + private String body; + + + /** + * HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 + */ + private Map params; +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java index e9e3fb457..a5c24c24e 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java @@ -42,4 +42,13 @@ public class PayOrderNotifyRespDTO { */ private String data; + /** + * TODO @jason 结合其他的渠道定义成枚举, + * alipay + * TRADE_CLOSED,未付款交易超时关闭,或支付完成后全额退款。 + * TRADE_SUCCESS, 交易支付成功 + * TRADE_FINISHED 交易结束,不可退款。 + */ + private String tradeStatus; + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java index 299f0b3f1..6f99bb80d 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java @@ -44,11 +44,16 @@ public class PayOrderUnifiedReqDTO { @Length(max = 128, message = "商品描述信息长度不能超过128") private String body; /** - * 支付结果的回调地址 + * 支付结果的 notify 回调地址 */ @NotEmpty(message = "支付结果的回调地址不能为空") - @URL(message = "支付结果的回调地址必须是 URL 格式") + @URL(message = "支付结果的 notify 回调地址必须是 URL 格式") private String notifyUrl; + /** + * 支付结果的 return 回调地址 + */ + @URL(message = "支付结果的 return 回调地址必须是 URL 格式") + private String returnUrl; // ========== 订单相关字段 ========== diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java new file mode 100644 index 000000000..05fcff3c1 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto; + +import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +import java.util.Date; + +/** + * 从渠道返回数据中解析得到的支付退款通知的Notify DTO + * + * @author jason + */ +@Data +@ToString +@Builder +public class PayRefundNotifyDTO { + + /** + * 支付渠道编号 + */ + private String channelOrderNo; + + + /** + * 交易订单号,根据规则生成 + * 调用支付渠道时,使用该字段作为对接的订单号。 + * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no + * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no + * 这里对应 pay_extension 里面的 no + * 例如说,P202110132239124200055 + */ + private String tradeNo; + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no + * 退款请求号。 + * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。 + * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更, + * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。 + * 退款单请求号,根据规则生成 + * + * 例如说,RR202109181134287570000 + */ + private String reqNo; + + + /** + * 退款是否成功 + */ + private PayNotifyRefundStatusEnum status; + + + + /** + * 退款成功时间 + */ + private Date refundSuccessTime; + + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java new file mode 100644 index 000000000..79e11eaff --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * 统一 退款 Request DTO + * + * @author jason + */ +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class PayRefundUnifiedReqDTO { + + /** + * 用户 IP + */ + private String userIp; + + // TODO @jason:这个是否为非必传字段呀,只需要传递 payTradeNo 字段即可。尽可能精简 + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 transaction_id + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 trade_no + * 渠道订单号 + */ + private String channelOrderNo; + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no + * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空 + */ + private String payTradeNo; + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no + * 退款请求单号 同一退款请求单号多次请求只退一笔。 + * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo} + */ + @NotEmpty(message = "退款请求单号") + private String merchantRefundId; + + /** + * 退款原因 + */ + @NotEmpty(message = "退款原因不能为空") + private String reason; + + /** + * 退款金额,单位:分 + */ + @NotNull(message = "退款金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Long amount; + + /** + * 退款结果 notify 回调地址, 支付宝退款不需要回调地址, 微信需要 + */ + @URL(message = "支付结果的 notify 回调地址必须是 URL 格式") + private String notifyUrl; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java new file mode 100644 index 000000000..d2e280ae5 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto; + +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +/** + * 统一退款 Response DTO + * + * @author jason + */ +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class PayRefundUnifiedRespDTO { + + /** + * 渠道退款单编号 + */ + private String channelRefundId; +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java index 1e228b252..38b8875a1 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java @@ -1,13 +1,17 @@ package cn.iocoder.yudao.framework.pay.core.client.impl; import cn.hutool.extra.validation.ValidationUtil; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; 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 static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; /** @@ -22,6 +26,7 @@ public abstract class AbstractPayClient implemen * 渠道编号 */ private final Long channelId; + /** * 渠道编码 */ @@ -91,7 +96,26 @@ public abstract class AbstractPayClient implemen return result; } + + protected abstract PayCommonResult doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable; + + @Override + public PayCommonResult unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + PayCommonResult resp; + try { + resp = doUnifiedRefund(reqDTO); + } catch (Throwable ex) { + // 记录异常日志 + log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex); + resp = PayCommonResult.error(ex); + } + return resp; + } + + + protected abstract PayCommonResult doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java index 16cc49ed2..edfd44952 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java @@ -58,10 +58,15 @@ public class PayClientFactoryImpl implements PayClientFactory { PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode); Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelEnum)); // 创建客户端 + // TODO @芋艿 WX_LITE WX_APP 如果不添加在 项目启动的时候去初始化会报错无法启动。所以我手动加了两个,具体需要你来配 switch (channelEnum) { case WX_PUB: return (AbstractPayClient) new WXPubPayClient(channelId, (WXPayClientConfig) config); + case WX_LITE: return (AbstractPayClient) new WXPubPayClient(channelId, (WXPayClientConfig) config); + case WX_APP: return (AbstractPayClient) new WXPubPayClient(channelId, (WXPayClientConfig) config); case ALIPAY_WAP: return (AbstractPayClient) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config); case ALIPAY_QR: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config); + case ALIPAY_APP: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config); + case ALIPAY_PC: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config); } // 创建失败,错误日志 + 抛出异常 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", config); diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java new file mode 100644 index 000000000..7cb24145a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java @@ -0,0 +1,131 @@ +package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +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.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; +import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayConfig; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.domain.AlipayTradeRefundModel; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.AlipayTradeRefundRequest; +import com.alipay.api.response.AlipayTradeRefundResponse; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; + +/** + * 支付宝抽象类, 实现支付宝统一的接口。如退款 + * + * @author jason + */ +@Slf4j +public abstract class AbstractAlipayClient extends AbstractPayClient { + + protected DefaultAlipayClient client; + + public AbstractAlipayClient(Long channelId, String channelCode, + AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) { + super(channelId, channelCode, config, codeMapping); + } + + @Override + @SneakyThrows + protected void doInit() { + AlipayConfig alipayConfig = new AlipayConfig(); + BeanUtil.copyProperties(config, alipayConfig, false); + 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 params = data.getParams(); + 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(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss")) + .data(data.getBody()).build(); + } + + @Override + public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { + Map params = notifyData.getParams(); + 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(DateUtil.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 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.setRefundReason(reqDTO.getReason()); + AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); + refundRequest.setBizModel(model); + try { + AlipayTradeRefundResponse response = client.execute(refundRequest); + log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); + if (response.isSuccess()) { + //退款导致触发的异步通知是发送到支付接口中设置的notify_url + //支付宝不返回退款单号,设置为空 + PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO(); + respDTO.setChannelRefundId(""); + return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping); + } + // 失败。需要抛出异常 + return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping); + } catch (AlipayApiException e) { + // TODO 记录异常日志 + log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e); + return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); + } + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java index f5106ecaa..bbe96c146 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java @@ -3,7 +3,14 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import lombok.Data; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Set; + // TODO 芋艿:参数校验 + /** * 支付宝的 PayClientConfig 实现类 * 属性主要来自 {@link com.alipay.api.AlipayConfig} 的必要属性 @@ -25,11 +32,11 @@ public class AlipayPayClientConfig implements PayClientConfig { /** * 公钥类型 - 公钥模式 */ - private static final Integer MODE_PUBLIC_KEY = 1; + public static final Integer MODE_PUBLIC_KEY = 1; /** * 公钥类型 - 证书模式 */ - private static final Integer MODE_CERTIFICATE = 2; + public static final Integer MODE_CERTIFICATE = 2; /** * 签名算法类型 - RSA @@ -41,18 +48,21 @@ public class AlipayPayClientConfig implements PayClientConfig { * 1. {@link #SERVER_URL_PROD} * 2. {@link #SERVER_URL_SANDBOX} */ + @NotBlank(message = "网关地址不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) private String serverUrl; /** * 开放平台上创建的应用的 ID */ + @NotBlank(message = "开放平台上创建的应用的 ID不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) private String appId; /** * 签名算法类型,推荐:RSA2 - * + *

* {@link #SIGN_TYPE_DEFAULT} */ + @NotBlank(message = "签名算法类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) private String signType; /** @@ -60,30 +70,48 @@ public class AlipayPayClientConfig implements PayClientConfig { * 1. {@link #MODE_PUBLIC_KEY} 情况,privateKey + alipayPublicKey * 2. {@link #MODE_CERTIFICATE} 情况,appCertContent + alipayPublicCertContent + rootCertContent */ + @NotNull(message = "公钥类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) private Integer mode; // ========== 公钥模式 ========== /** * 商户私钥 */ + @NotBlank(message = "商户私钥不能为空", groups = {ModePublicKey.class}) private String privateKey; + /** * 支付宝公钥字符串 */ + @NotBlank(message = "支付宝公钥字符串不能为空", groups = {ModePublicKey.class}) private String alipayPublicKey; // ========== 证书模式 ========== /** * 指定商户公钥应用证书内容字符串 */ + @NotBlank(message = "指定商户公钥应用证书内容不能为空", groups = {ModeCertificate.class}) private String appCertContent; /** * 指定支付宝公钥证书内容字符串 */ + @NotBlank(message = "指定支付宝公钥证书内容不能为空", groups = {ModeCertificate.class}) private String alipayPublicCertContent; /** * 指定根证书内容字符串 */ + @NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class}) private String rootCertContent; + public interface ModePublicKey { + } + + public interface ModeCertificate { + } + + @Override + public Set> verifyParam(Validator validator) { + return validator.validate(this, + MODE_PUBLIC_KEY.equals(this.getMode()) ? ModePublicKey.class : ModeCertificate.class); + } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayCodeMapping.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayCodeMapping.java index 8cafd425d..d9662a01b 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayCodeMapping.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayCodeMapping.java @@ -18,6 +18,10 @@ public class AlipayPayCodeMapping extends AbstractPayCodeMapping { if (Objects.equals(apiCode, "10000")) { return GlobalErrorCodeConstants.SUCCESS; } + // alipay wap api code 返回为null, 暂时定为-9999 + if (Objects.equals(apiCode, "-9999")) { + return GlobalErrorCodeConstants.SUCCESS; + } return null; } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java index e9b5245ff..acdf28e70 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java @@ -1,18 +1,12 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; -import cn.hutool.core.bean.BeanUtil; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; -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.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import com.alipay.api.AlipayApiException; -import com.alipay.api.AlipayConfig; -import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.AlipayTradePrecreateModel; import com.alipay.api.request.AlipayTradePrecreateRequest; import com.alipay.api.response.AlipayTradePrecreateResponse; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; @@ -24,23 +18,12 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString * @author 芋道源码 */ @Slf4j -public class AlipayQrPayClient extends AbstractPayClient { - - private DefaultAlipayClient client; +public class AlipayQrPayClient extends AbstractAlipayClient { public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping()); } - @Override - @SneakyThrows - protected void doInit() { - AlipayConfig alipayConfig = new AlipayConfig(); - BeanUtil.copyProperties(config, alipayConfig, false); - // 真实客户端 - this.client = new DefaultAlipayClient(alipayConfig); - } - @Override public PayCommonResult doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { // 构建 AlipayTradePrecreateModel 请求 @@ -53,7 +36,8 @@ public class AlipayQrPayClient extends AbstractPayClient // 构建 AlipayTradePrecreateRequest AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); request.setBizModel(model); - + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); // 执行请求 AlipayTradePrecreateResponse response; try { @@ -65,10 +49,4 @@ public class AlipayQrPayClient extends AbstractPayClient // TODO 芋艿:sub Code 需要测试下各种失败的情况 return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping); } - - @Override - public PayOrderNotifyRespDTO parseOrderNotify(String data) throws Exception { - // TODO 芋艿:待完成 - return null; - } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java index 7f3bd5a91..0e410ba0c 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java @@ -1,18 +1,16 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; -import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; -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.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import com.alipay.api.AlipayApiException; -import com.alipay.api.AlipayConfig; -import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.AlipayTradeWapPayModel; import com.alipay.api.request.AlipayTradeWapPayRequest; import com.alipay.api.response.AlipayTradeWapPayResponse; -import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; /** * 支付宝【手机网站】的 PayClient 实现类 @@ -20,22 +18,14 @@ import lombok.SneakyThrows; * * @author 芋道源码 */ -public class AlipayWapPayClient extends AbstractPayClient { +@Slf4j +public class AlipayWapPayClient extends AbstractAlipayClient { - private DefaultAlipayClient client; public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping()); } - @Override - @SneakyThrows - protected void doInit() { - AlipayConfig alipayConfig = new AlipayConfig(); - BeanUtil.copyProperties(config, alipayConfig, false); - this.client = new DefaultAlipayClient(alipayConfig); - } - @Override public PayCommonResult doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { // 构建 AlipayTradeWapPayModel 请求 @@ -45,11 +35,17 @@ public class AlipayWapPayClient extends AbstractPayClient model.setBody(reqDTO.getBody()); model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); model.setProductCode("QUICK_WAP_PAY"); // TODO 芋艿:这里咋整 - model.setSellerId("2088102147948060"); // TODO 芋艿:这里咋整 - // TODO 芋艿:userIp + expireTime + //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 AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest(); request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); // 执行请求 AlipayTradeWapPayResponse response; @@ -58,13 +54,22 @@ public class AlipayWapPayClient extends AbstractPayClient } catch (AlipayApiException e) { return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); } -// TODO 芋艿:sub Code - return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping); + + // 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); + } } - @Override - public PayOrderNotifyRespDTO parseOrderNotify(String data) throws Exception { - // TODO 芋艿:待完成 - return null; - } + + + + + + + + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java index 27bab4c1b..79ebbf348 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java @@ -4,10 +4,15 @@ import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import lombok.Data; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import javax.validation.constraints.NotBlank; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.util.Set; // TODO 芋艿:参数校验 + /** * 微信支付的 PayClientConfig 实现类 * 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性 @@ -20,13 +25,11 @@ public class WXPayClientConfig implements PayClientConfig { // TODO 芋艿:V2 or V3 客户端 /** * API 版本 - V2 - * * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1 */ public static final String API_VERSION_V2 = "v2"; /** * API 版本 - V3 - * * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml */ public static final String API_VERSION_V3 = "v3"; @@ -34,14 +37,17 @@ public class WXPayClientConfig implements PayClientConfig { /** * 公众号或者小程序的 appid */ + @NotBlank(message = "APPID 不能为空", groups = {V2.class, V3.class}) private String appId; /** * 商户号 */ + @NotBlank(message = "商户号 不能为空", groups = {V2.class, V3.class}) private String mchId; /** * API 版本 */ + @NotBlank(message = "API 版本 不能为空", groups = {V2.class, V3.class}) private String apiVersion; // ========== V2 版本的参数 ========== @@ -49,39 +55,59 @@ public class WXPayClientConfig implements PayClientConfig { /** * 商户密钥 */ + @NotBlank(message = "商户密钥 不能为空", groups = V2.class) private String mchKey; -// /** -// * apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径. -// * 对应的字符串 -// * -// * 注意,可通过 {@link #main(String[])} 读取 -// */ -// private String keyContent; + /** + * apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径. + * 对应的字符串 + * + * 注意,可通过 {@link #main(String[])} 读取 + */ + /// private String keyContent; // ========== V3 版本的参数 ========== /** * apiclient_key.pem 证书文件的绝对路径或者以 classpath: 开头的类路径. * 对应的字符串 - * * 注意,可通过 {@link #main(String[])} 读取 */ + @NotBlank(message = "apiclient_key 不能为空", groups = V3.class) private String privateKeyContent; /** * apiclient_cert.pem 证书文件的绝对路径或者以 classpath: 开头的类路径. * 对应的字符串 - * + *

* 注意,可通过 {@link #main(String[])} 读取 */ + @NotBlank(message = "apiclient_cert 不能为空", groups = V3.class) private String privateCertContent; /** * apiV3 秘钥值 */ + @NotBlank(message = "apiV3 秘钥值 不能为空", groups = V3.class) private String apiV3Key; + /** + * 分组校验 v2版本 + */ + public interface V2 { + } + + /** + * 分组校验 v3版本 + */ + public interface V3 { + } + + @Override + public Set> verifyParam(Validator validator) { + return validator.validate(this, this.getApiVersion().equals(API_VERSION_V2) ? V2.class : V3.class); + } + public static void main(String[] args) throws FileNotFoundException { String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.p12"; -// String path = "/Users/yunai/Downloads/wx_pay/apiclient_key.pem"; -// String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"; + /// String path = "/Users/yunai/Downloads/wx_pay/apiclient_key.pem"; + /// String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"; System.out.println(IoUtil.readUtf8(new FileInputStream(path))); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java index 03e63c9f4..5d5809ae3 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java @@ -8,8 +8,7 @@ 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.PayOrderNotifyRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.*; 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; @@ -91,6 +90,7 @@ public class WXPubPayClient extends AbstractPayClient { return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping); } + private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { // 构建 WxPayUnifiedOrderRequest 对象 WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() @@ -131,14 +131,27 @@ public class WXPubPayClient extends AbstractPayClient { } @Override - public PayOrderNotifyRespDTO parseOrderNotify(String data) throws WxPayException { - WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data); + public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException { + WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody()); Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS"); // 转换结果 return PayOrderNotifyRespDTO.builder().orderExtensionNo(notifyResult.getOutTradeNo()) .channelOrderNo(notifyResult.getTransactionId()).channelUserId(notifyResult.getOpenid()) .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss")) - .data(data).build(); + .data(data.getBody()).build(); + } + + @Override + public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) { + //TODO 需要实现 + throw new UnsupportedOperationException("需要实现"); + } + + + @Override + protected PayCommonResult doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + //TODO 需要实现 + throw new UnsupportedOperationException(); } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java index ed1389e0e..418676fca 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java @@ -1,6 +1,9 @@ package cn.iocoder.yudao.framework.pay.core.enums; import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; +import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; +import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig; import lombok.AllArgsConstructor; import lombok.Getter; @@ -14,18 +17,21 @@ import lombok.Getter; @AllArgsConstructor public enum PayChannelEnum { - WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号的网页 - WX_LITE("wx_lit","微信小程序支付"), - WX_APP("wx_app", "微信 App 支付"), + /** + * 公众号网页 + */ + WX_PUB("wx_pub", "微信 JSAPI 支付", WXPayClientConfig.class), + WX_LITE("wx_lite", "微信小程序支付", WXPayClientConfig.class), + WX_APP("wx_app", "微信 App 支付", WXPayClientConfig.class), - ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付"), - ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付"), - ALIPAY_APP("alipay_app", "支付宝App 支付"), - ALIPAY_QR("alipay_qr", "支付宝扫码支付"); + 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); /** * 编码 - * + *

* 参考 https://www.pingxx.com/api/支付渠道属性值.html */ private String code; @@ -34,8 +40,24 @@ public enum PayChannelEnum { */ private String name; + /** + * 配置类 + */ + private Class configClass; + + /** + * 微信支付 + */ + public static final String WECHAT = "WECHAT"; + + /** + * 支付宝支付 + */ + public static final String ALIPAY = "ALIPAY"; + public static PayChannelEnum getByCode(String code) { return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values()); } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRefundRespEnum.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRefundRespEnum.java new file mode 100644 index 000000000..53aa16bc2 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRefundRespEnum.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.framework.pay.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 渠道统一的退款返回结果 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum PayChannelRefundRespEnum { + + SUCCESS(1, "退款成功"), + FAILURE(2, "退款失败"), + PROCESSING(3,"退款处理中"), + CLOSED(4, "退款关闭"); + + private final Integer status; + private final String name; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayNotifyRefundStatusEnum.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayNotifyRefundStatusEnum.java new file mode 100644 index 000000000..5137600e0 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayNotifyRefundStatusEnum.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.framework.pay.core.enums; + +/** + * 退款通知, 统一的渠道退款状态 + * + * @author jason + */ +public enum PayNotifyRefundStatusEnum { + /** + * 支付宝 中 全额退款 trade_status=TRADE_CLOSED, 部分退款 trade_status=TRADE_SUCCESS + * 退款成功 + */ + SUCCESS, + + /** + * 支付宝退款通知没有这个状态 + * 退款异常 + */ + ABNORMAL; +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test-integration/java/cn/iocoder/yudao/framework/core/client/impl/PayClientFactoryImplTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test-integration/java/cn/iocoder/yudao/framework/core/client/impl/PayClientFactoryImplTest.java index d7f8e462f..582840e4e 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test-integration/java/cn/iocoder/yudao/framework/core/client/impl/PayClientFactoryImplTest.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test-integration/java/cn/iocoder/yudao/framework/core/client/impl/PayClientFactoryImplTest.java @@ -13,6 +13,7 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; +import com.alipay.api.response.AlipayTradePrecreateResponse; import org.junit.jupiter.api.Test; import java.io.FileInputStream; @@ -75,6 +76,7 @@ public class PayClientFactoryImplTest { * {@link AlipayQrPayClient} */ @Test + @SuppressWarnings("unchecked") public void testCreatePayClient_ALIPAY_QR() { // 创建配置 AlipayPayClientConfig config = new AlipayPayClientConfig(); @@ -89,8 +91,10 @@ public class PayClientFactoryImplTest { PayClient client = payClientFactory.getPayClient(channelId); // 发起支付 PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO(); - CommonResult result = client.unifiedOrder(reqDTO); + reqDTO.setNotifyUrl("http://niubi.natapp1.cc/api/pay/order/notify/alipay-qr/1"); // TODO @tina: 这里改成你的 natapp 回调地址 + CommonResult result = (CommonResult) client.unifiedOrder(reqDTO); System.out.println(JsonUtils.toJsonString(result)); + System.out.println(result.getData().getQrCode()); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java new file mode 100644 index 000000000..a4c14f634 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java @@ -0,0 +1,100 @@ +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.test.core.ut.BaseMockitoUnitTest; +import com.alipay.api.AlipayApiException; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.request.AlipayTradePrecreateRequest; +import com.alipay.api.response.AlipayTradePrecreateResponse; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +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.junit.jupiter.api.Assertions.assertNotSame; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.when; + +public class AlipayQrPayClientTest extends BaseMockitoUnitTest { + + private final AlipayPayClientConfig config = new AlipayPayClientConfig() + .setAppId("2021000118634035") + .setServerUrl(AlipayPayClientConfig.SERVER_URL_SANDBOX) + .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT) + // TODO @tina:key 可以随机就好,简洁一点哈。 + .setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJ" + + "v890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T" + + "01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH" + + "6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScw" + + "lSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63tr" + + "epo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdk" + + "USmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr" + + "8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w" + + "0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENi" + + "vAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPw" + + "YcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQO" + + "LFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsm" + + "yX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i9" + + "5Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOU" + + "hVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD" + + "/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v1" + + "8p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ" + + "8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4e" + + "N0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6p" + + "bKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erx" + + "TRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8=") + .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0" + + "gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBN" + + "lrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZ" + + "ikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); + + // TODO @tina:= 前后要有空格哈 + @InjectMocks + AlipayQrPayClient client=new AlipayQrPayClient(10L,config); + + @Mock + private DefaultAlipayClient defaultAlipayClient; + + @Test + public void testDoInit(){ + client.doInit(); + assertNotSame(defaultAlipayClient, ReflectUtil.getFieldValue(client, "defaultAlipayClient")); + } + + @Test + @Disabled // TODO 芋艿:临时禁用 + public void create() throws AlipayApiException { + // TODO @tina:参数可以尽量随机一点,使用随机方法。这样的好处是,避免对固定参数的依赖,导致可能仅仅满足固定参数的结果 + // 这里,设置可以直接随机整个对象。 + Long shopOrderId = System.currentTimeMillis(); + PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO(); + reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis())); + reqDTO.setAmount(1L); + reqDTO.setBody("内容:" + shopOrderId); + reqDTO.setSubject("标题:"+shopOrderId); + String notify="http://niubi.natapp1.cc/api/pay/order/notify"; + reqDTO.setNotifyUrl(notify); + + AlipayTradePrecreateResponse response=randomPojo(AlipayTradePrecreateResponse.class,o->o.setQrCode("success")); + + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request ->{ + assertEquals(notify,request.getNotifyUrl()); + return true; + }))).thenReturn(response); + + + PayCommonResult 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()); + + } +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java index 18a927afc..b0ba53e9b 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java @@ -4,7 +4,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${yudao.info.base-package} 和 ${yudao.core-service.base-package} -@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"})public class UserServerApplication { +@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"}) +public class UserServerApplication { public static void main(String[] args) { SpringApplication.run(UserServerApplication.class, args); diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java index 74a7249a8..fb022b106 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java @@ -3,10 +3,13 @@ package cn.iocoder.yudao.userserver.modules.pay.controller.order; import cn.hutool.core.bean.BeanUtil; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; +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.userserver.modules.pay.controller.order.vo.PayOrderSubmitReqVO; import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayOrderSubmitRespVO; import io.swagger.annotations.Api; @@ -16,7 +19,10 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import java.util.Map; +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_CHANNEL_CLIENT_NOT_FOUND; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; @@ -29,6 +35,12 @@ public class PayOrderController { @Resource private PayOrderCoreService payOrderCoreService; + @Resource + private PayRefundCoreService payRefundCoreService; + + @Resource + private PayClientFactory payClientFactory; + @PostMapping("/submit") @ApiOperation("提交支付订单") @@ -49,12 +61,61 @@ public class PayOrderController { } // ========== 支付渠道的回调 ========== - + //TODO 芋道源码 换成了统一的地址了 /notify/{channelId},测试通过可以删除 @PostMapping("/notify/wx-pub/{channelId}") - @ApiOperation("通知微信公众号的结果") + @ApiOperation("通知微信公众号支付的结果") public String notifyWxPayOrder(@PathVariable("channelId") Long channelId, @RequestBody String xmlData) throws Exception { - payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.WX_PUB.getCode(), xmlData); + payOrderCoreService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().body(xmlData).build()); + return "success"; + } + + /** + * 统一的跳转页面, 支付宝跳转参数说明 + * https://opendocs.alipay.com/open/203/105285#%E5%89%8D%E5%8F%B0%E5%9B%9E%E8%B7%B3%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E + * @param channelId 渠道id + * @return 返回跳转页面 + */ + @GetMapping(value = "/return/{channelId}") + @ApiOperation("渠道统一的支付成功返回地址") + public String returnAliPayOrder(@PathVariable("channelId") Long channelId, @RequestParam Map params){ + //TODO 可以根据渠道和 app_id 返回不同的页面 + log.info("app_id is {}", params.get("app_id")); + return String.format("渠道[%s]支付成功", channelId); + } + + /** + * 统一的渠道支付回调,支付宝的退款回调 + * + * @param channelId 渠道编号 + * @param params form 参数 + * @param originData http request body + * @return 成功返回 "success" + */ + @PostMapping(value = "/notify/{channelId}") + @ApiOperation("渠道统一的支付成功,或退款成功 通知url") + public String notifyChannelPay(@PathVariable("channelId") Long channelId, + @RequestParam Map params, + @RequestBody String originData) throws Exception { + // 校验支付渠道是否存在 + PayClient payClient = payClientFactory.getPayClient(channelId); + if (payClient == null) { + log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channelId); + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); + } + + // 校验通知数据是否合法 + PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(originData).build(); + payClient.verifyNotifyData(notifyData); + + // 如果是退款,则发起退款通知 + if (payClient.isRefundNotify(notifyData)) { + payRefundCoreService.notifyPayRefund(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build()); + return "success"; + } + + // 如果非退款,则发起支付通知 + payOrderCoreService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build()); return "success"; } diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java new file mode 100644 index 000000000..ba62bdd8a --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.userserver.modules.pay.controller.order; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundReqVO; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundRespVO; +import cn.iocoder.yudao.userserver.modules.pay.convert.order.PayRefundConvert; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +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.util.servlet.ServletUtils.getClientIP; + +@Api(tags = "退款订单") +@RestController +@RequestMapping("/pay/order") +@Validated +@Slf4j +public class PayRefundController { + + @Resource + private PayRefundCoreService payRefundCoreService; + + @PostMapping("/refund") + @ApiOperation("提交退款订单") + public CommonResult submitRefundOrder(@RequestBody PayRefundReqVO reqVO){ + PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO); + req.setUserIp(getClientIP()); + //TODO 测试暂时模拟生成商户退款订单 + if(StrUtil.isEmpty(reqVO.getMerchantRefundId())) { + req.setMerchantRefundId(PaySeqUtils.genMerchantRefundNo()); + } + return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req))); + } + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java new file mode 100644 index 000000000..8287ba036 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.userserver.modules.pay.controller.order.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; + +@ApiModel("退款订单 Req VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundReqVO { + + @ApiModelProperty(value = "支付订单编号自增", required = true, example = "10") + @NotEmpty(message = "支付订单编号自增") + private Long payOrderId; + + @ApiModelProperty(value = "退款金额", required = true, example = "1") + @NotEmpty(message = "退款金额") + private Long amount; + + @ApiModelProperty(value = "退款原因", required = true, example = "不喜欢") + @NotEmpty(message = "退款原因") + private String reason; + + @ApiModelProperty(value = "商户退款订单号", required = true, example = "MR202111180000000001") + //TODO 测试暂时模拟生成 + //@NotEmpty(message = "商户退款订单号") + private String merchantRefundId; + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java new file mode 100644 index 000000000..29059ab28 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.userserver.modules.pay.controller.order.vo; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@ApiModel("提交退款订单 Response VO") +@Data +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundRespVO { + + /** + * 支付退款单编号, 自增 + */ + private Long refundId; +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java new file mode 100644 index 000000000..999a3ba56 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.userserver.modules.pay.convert.order; + +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundReqVO; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundRespVO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * 支付退款 Convert + * + * @author jason + */ +@Mapper +public interface PayRefundConvert { + + PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class); + + PayRefundReqDTO convert(PayRefundReqVO reqVO); + + PayRefundRespVO convert(PayRefundRespDTO req); +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java new file mode 100644 index 000000000..3f27f0b4b --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package cn.iocoder.yudao.userserver.modules.pay.convert; diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md new file mode 100644 index 000000000..8153487b7 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java index 4767c074d..25ebd6d28 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.userserver.modules.shop.controller; import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayRefundOrderReqVO; import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.userserver.modules.shop.controller.vo.ShopOrderCreateRespVO; @@ -43,10 +45,10 @@ public class ShopOrderController { PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO(); reqDTO.setAppId(6L); reqDTO.setUserIp(getClientIP()); - reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis())); + reqDTO.setMerchantOrderId(PaySeqUtils.genMerchantOrderNo()); reqDTO.setSubject("标题:" + shopOrderId); reqDTO.setBody("内容:" + shopOrderId); - reqDTO.setAmount(1); // 单位:分 + reqDTO.setAmount(200); // 单位:分 reqDTO.setExpireTime(DateUtils.addTime(Duration.ofDays(1))); Long payOrderId = payOrderCoreService.createPayOrder(reqDTO); @@ -62,4 +64,11 @@ public class ShopOrderController { return success(true); } + @PostMapping("/refund-notify") + @ApiOperation("退款回调") + public CommonResult refundNotify(@RequestBody @Valid PayRefundOrderReqVO reqVO) { + log.info("[refundNotify][回调成功]"); + return success(true); + } + } diff --git a/yudao-user-server/src/main/resources/application-local.yaml b/yudao-user-server/src/main/resources/application-local.yaml index 7f3a6fec4..8a4131039 100644 --- a/yudao-user-server/src/main/resources/application-local.yaml +++ b/yudao-user-server/src/main/resources/application-local.yaml @@ -158,8 +158,9 @@ yudao: - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 demo: false # 关闭演示模式 pay: - pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify - refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify + pay-notify-url: http://jg6rde.natappfree.cc/api/pay/order/notify + refund-notify-url: http://jg6rde.natappfree.cc/api/pay/refund/notify + pay-return-url: http://jg6rde.natappfree.cc/api/pay/order/return justauth: enabled: true diff --git a/yudao-user-server/src/main/resources/static/pay_alipay_qr.html b/yudao-user-server/src/main/resources/static/pay_alipay_qr.html new file mode 100644 index 000000000..edb364f07 --- /dev/null +++ b/yudao-user-server/src/main/resources/static/pay_alipay_qr.html @@ -0,0 +1,79 @@ + + + + + + 支付测试页 + + + + + +

点击如下按钮,发起支付宝扫码支付的测试
+
+ +
+
+ + + + diff --git a/yudao-user-server/src/main/resources/static/pay_alipay_wap.html b/yudao-user-server/src/main/resources/static/pay_alipay_wap.html new file mode 100644 index 000000000..26438f21f --- /dev/null +++ b/yudao-user-server/src/main/resources/static/pay_alipay_wap.html @@ -0,0 +1,65 @@ + + + + + + 支付测试页 + + + +
点击如下按钮,发起支付的测试
+
+ +
+
+ + + diff --git a/yudao-user-server/src/main/resources/static/pay.html b/yudao-user-server/src/main/resources/static/pay_wx_pub.html similarity index 100% rename from yudao-user-server/src/main/resources/static/pay.html rename to yudao-user-server/src/main/resources/static/pay_wx_pub.html diff --git a/yudao-user-server/src/main/resources/static/qrcode.min.js b/yudao-user-server/src/main/resources/static/qrcode.min.js new file mode 100644 index 000000000..993e88f39 --- /dev/null +++ b/yudao-user-server/src/main/resources/static/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file