Browse Source

新增模拟支付渠道,通知查询为空bug 修改

jason 1 year ago
parent
commit
6a38738760

+ 3 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java

@@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
+import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient;
+import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 import lombok.extern.slf4j.Slf4j;
@@ -68,6 +70,7 @@ public class PayClientFactoryImpl implements PayClientFactory {
             case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
             case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
             case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
+            case MOCK: return (AbstractPayClient<Config>) new MockPayClient(channelId, (MockPayClientConfig) config);
         }
         // 创建失败,错误日志 + 抛出异常
         log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);

+ 64 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
+
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
+import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+/**
+ * 模拟支付的 PayClient 实现类, 模拟支付返回结果都是成功
+ *
+ * @author jason
+ */
+public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
+
+    private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
+
+    public MockPayClient(Long channelId, MockPayClientConfig config) {
+        super(channelId, PayChannelEnum.MOCK.getCode(), config);
+    }
+
+    @Override
+    protected void doInit() {
+
+    }
+
+    @Override
+    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        // 模拟支付渠道订单号为空
+        return PayOrderRespDTO.successOf("", "", LocalDateTime.now(), reqDTO.getOutTradeNo(), MOCK_RESP_SUCCESS_DATA);
+    }
+
+    @Override
+    protected PayOrderRespDTO doGetOrder(String outTradeNo) {
+        // 模拟支付渠道订单号为空
+        return PayOrderRespDTO.successOf("", "", LocalDateTime.now(), outTradeNo, MOCK_RESP_SUCCESS_DATA);
+    }
+
+    @Override
+    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
+        // 模拟支付渠道退款单号为空
+        return PayRefundRespDTO.successOf("", LocalDateTime.now(), reqDTO.getOutRefundNo(), MOCK_RESP_SUCCESS_DATA);
+    }
+
+    @Override
+    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
+        // 模拟支付渠道退款单号为空
+        return PayRefundRespDTO.successOf("", LocalDateTime.now(), outRefundNo, MOCK_RESP_SUCCESS_DATA);
+    }
+
+    @Override
+    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
+        throw new UnsupportedOperationException("模拟支付无退款回调");
+    }
+
+    @Override
+    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
+        throw new UnsupportedOperationException("模拟支付无支付回调");
+    }
+}

+ 25 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClientConfig.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
+
+import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
+import lombok.Data;
+
+import javax.validation.Validator;
+
+/**
+ * 模拟支付的 PayClientConfig 实现类
+ *
+ * @author jason
+ */
+@Data
+public class MockPayClientConfig implements PayClientConfig {
+
+    /**
+     * 配置名称,如果不加任何属性, JsonUtils.parseObject2 解析会报错. 暂时加个名称
+     */
+    private String name;
+
+    @Override
+    public void validate(Validator validator) {
+        // 模拟支付配置无需校验
+    }
+}

+ 4 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.pay.core.enums.channel;
 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.mock.MockPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -26,7 +27,9 @@ public enum PayChannelEnum {
     ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
     ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
     ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
-    ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class);
+    ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
+
+    MOCK("mock", "模拟支付", MockPayClientConfig.class);
 
     /**
      * 编码

+ 4 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.controller.admin.notify;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
@@ -118,6 +119,9 @@ public class PayNotifyController {
     @PreAuthorize("@ss.hasPermission('pay:notify:query')")
     public CommonResult<PageResult<PayNotifyTaskRespVO>> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) {
         PageResult<PayNotifyTaskDO> pageResult = notifyService.getNotifyTaskPage(pageVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
         // 拼接返回
         Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
         return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));

+ 4 - 0
yudao-ui-admin/src/utils/constants.js

@@ -160,6 +160,10 @@ export const PayChannelEnum = {
     "code": "alipay_bar",
     "name": "支付宝条码支付"
   },
+  MOCK : {
+    "code": "mock",
+    "name": "模拟支付"
+  }
 }
 
 /**

+ 108 - 0
yudao-ui-admin/src/views/pay/app/components/mockChannelForm.vue

@@ -0,0 +1,108 @@
+<template>
+  <div>
+    <el-dialog :visible.sync="dialogVisible" :title="title" @closed="close" append-to-body width="800px">
+      <el-form ref="form" :model="formData" :rules="rules" size="medium" label-width="100px" v-loading="formLoading">
+        <el-form-item label-width="180px" label="渠道状态" prop="status">
+          <el-radio-group v-model="formData.status" size="medium">
+            <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value)"
+                      :label="parseInt(dict.value)">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label-width="180px" label="备注" prop="remark">
+          <el-input v-model="formData.remark" :style="{width: '100%'}"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="close">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { createChannel, getChannel, updateChannel } from "@/api/pay/channel";
+import { CommonStatusEnum } from "@/utils/constants";
+
+export default {
+  name: "mockChannelForm",
+  data() {
+    return {
+      dialogVisible: false,
+      formLoading: false,
+      title:'',
+      formData: {
+        appId: '',
+        code: '',
+        status: undefined,
+        feeRate: 0,
+        remark: '',
+        config: {
+          name: 'mock-conf'
+        }
+      },
+      rules: {
+        status: [{ required: true,  message: '渠道状态不能为空',  trigger: 'blur' }]
+      }
+    }
+  },
+  methods: {
+    open(appId, code) {
+      this.dialogVisible = true;
+      this.formLoading = true;
+      this.reset(appId, code);
+      getChannel(appId, code).then(response => {
+        if (response.data && response.data.id) {
+          this.formData = response.data;
+          this.formData.config = JSON.parse(response.data.config);
+        }
+        this.title = !this.formData.id ? '创建支付渠道' : '编辑支付渠道'
+      }).finally(() => {
+        this.formLoading = false;
+      });
+    },
+    close() {
+      this.dialogVisible = false;
+      this.reset(undefined, undefined);
+    },
+    submitForm() {
+      this.$refs['form'].validate(valid => {
+        if (!valid) {
+          return
+        }
+        const data = { ...this.formData };
+        data.config = JSON.stringify(this.formData.config);
+        if (!data.id) {
+          createChannel(data).then(response => {
+            this.$modal.msgSuccess("新增成功");
+            this.$emit('success')
+            this.close();
+          });
+        } else {
+          updateChannel(data).then(response => {
+            this.$modal.msgSuccess("修改成功");
+            this.$emit('success')
+            this.close();
+          })
+        }
+      });
+    },
+    /** 重置表单 */
+    reset(appId, code) {
+      this.formData = {
+        appId: appId,
+        code: code,
+        status: CommonStatusEnum.ENABLE,
+        remark: '',
+        feeRate: 0,
+        config: {
+          name: 'mock-conf'
+        }
+      }
+      this.resetForm('form')
+    },
+  }
+}
+</script>

+ 21 - 1
yudao-ui-admin/src/views/pay/app/index.vue

@@ -157,6 +157,19 @@
           </template>
         </el-table-column>
       </el-table-column>
+      <el-table-column label="模拟支付配置" align="center">
+        <el-table-column :label="payChannelEnum.MOCK.name" align="center">
+          <template v-slot="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="isChannelExists(scope.row.channelCodes, payChannelEnum.MOCK.code)"
+                       @click="handleChannel(scope.row, payChannelEnum.MOCK.code)">
+            </el-button>
+            <el-button v-else type="danger" icon="el-icon-close" circle
+                       @click="handleChannel(scope.row, payChannelEnum.MOCK.code)">
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template v-slot="scope">
           <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
@@ -206,6 +219,7 @@
     <!-- 对话框(支付应用的配置) -->
     <weixin-channel-form ref="weixinChannelFormRef" @success="getList" />
     <alipay-channel-form ref="alipayChannelFormRef" @success="getList" />
+    <mock-channel-form ref="mockChannelFormRef" @success="getList" />
   </div>
 </template>
 
@@ -214,12 +228,14 @@ import { createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage }
 import { PayChannelEnum, CommonStatusEnum } from "@/utils/constants";
 import weixinChannelForm from "@/views/pay/app/components/weixinChannelForm";
 import alipayChannelForm from "@/views/pay/app/components/alipayChannelForm";
+import mockChannelForm from '@/views/pay/app/components/mockChannelForm';
 
 export default {
   name: "PayApp",
   components: {
     weixinChannelForm,
-    alipayChannelForm
+    alipayChannelForm,
+    mockChannelForm
   },
   data() {
     return {
@@ -374,6 +390,10 @@ export default {
         this.$refs['weixinChannelFormRef'].open(row.id, code);
         return
       }
+      if (code === 'mock') {
+        this.$refs['mockChannelFormRef'].open(row.id, code);
+        return
+      }
     },
     /**
      * 根据渠道编码判断渠道列表中是否存在

+ 1 - 1
yudao-ui-admin/src/views/pay/cashier/index.vue

@@ -35,7 +35,7 @@
       <el-descriptions title="选择其它支付" style="margin-top: 20px;" />
       <div class="pay-channel-container">
         <div class="box" v-for="channel in channels" :key="channel.code"
-             v-if="channel.code.indexOf('alipay_') === -1 && channel.code.indexOf('wx_') === -1">
+             v-if="channel.code.indexOf('alipay_') === -1 && channel.code.indexOf('wx_') === -1" @click="submit(channel.code)">
           <img :src="channel.icon">
           <div class="title">{{ channel.name }}</div>
         </div>