mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	!550 支付中心:增加模拟支付,方便开发调试
Merge pull request !550 from 芋道源码/feature/mall_product
This commit is contained in:
		@@ -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,8 @@ 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);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,66 @@
 | 
			
		||||
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("MOCK-P-" + reqDTO.getOutTradeNo(), "", LocalDateTime.now(),
 | 
			
		||||
                reqDTO.getOutTradeNo(), MOCK_RESP_SUCCESS_DATA);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected PayOrderRespDTO doGetOrder(String outTradeNo) {
 | 
			
		||||
        return PayOrderRespDTO.successOf("MOCK-P-" + outTradeNo, "", LocalDateTime.now(),
 | 
			
		||||
                outTradeNo, MOCK_RESP_SUCCESS_DATA);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
 | 
			
		||||
        return PayRefundRespDTO.successOf("MOCK-R-" + reqDTO.getOutRefundNo(), LocalDateTime.now(),
 | 
			
		||||
                reqDTO.getOutRefundNo(), MOCK_RESP_SUCCESS_DATA);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
 | 
			
		||||
        return PayRefundRespDTO.successOf("MOCK-R-" + outRefundNo, 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("模拟支付无支付回调");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
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) {
 | 
			
		||||
        // 模拟支付配置无需校验
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 编码
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
 
 | 
			
		||||
@@ -160,6 +160,10 @@ export const PayChannelEnum = {
 | 
			
		||||
    "code": "alipay_bar",
 | 
			
		||||
    "name": "支付宝条码支付"
 | 
			
		||||
  },
 | 
			
		||||
  MOCK : {
 | 
			
		||||
    "code": "mock",
 | 
			
		||||
    "name": "模拟支付"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										107
									
								
								yudao-ui-admin/src/views/pay/app/components/mockChannelForm.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								yudao-ui-admin/src/views/pay/app/components/mockChannelForm.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -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
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据渠道编码判断渠道列表中是否存在
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user