Commit bc30c1d9d296e18328d96d2b4c72c7798a24ba49

Authored by 芋道源码
Committed by Gitee
2 parents 7a49e45b ae983156

!550 支付中心:增加模拟支付,方便开发调试

Merge pull request !550 from 芋道源码/feature/mall_product
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;
5 5 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
6 6 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
7 7 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
  8 +import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient;
  9 +import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
8 10 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*;
9 11 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
10 12 import lombok.extern.slf4j.Slf4j;
... ... @@ -68,6 +70,8 @@ public class PayClientFactoryImpl implements PayClientFactory {
68 70 case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
69 71 case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
70 72 case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
  73 + // 其它支付
  74 + case MOCK: return (AbstractPayClient<Config>) new MockPayClient(channelId, (MockPayClientConfig) config);
71 75 }
72 76 // 创建失败,错误日志 + 抛出异常
73 77 log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
  2 +
  3 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
  4 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  5 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
  6 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
  7 +import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
  8 +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
  9 +
  10 +import java.time.LocalDateTime;
  11 +import java.util.Map;
  12 +
  13 +/**
  14 + * 模拟支付的 PayClient 实现类
  15 + *
  16 + * 模拟支付返回结果都是成功,方便大家日常流畅
  17 + *
  18 + * @author jason
  19 + */
  20 +public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
  21 +
  22 + private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
  23 +
  24 + public MockPayClient(Long channelId, MockPayClientConfig config) {
  25 + super(channelId, PayChannelEnum.MOCK.getCode(), config);
  26 + }
  27 +
  28 + @Override
  29 + protected void doInit() {
  30 + }
  31 +
  32 + @Override
  33 + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  34 + return PayOrderRespDTO.successOf("MOCK-P-" + reqDTO.getOutTradeNo(), "", LocalDateTime.now(),
  35 + reqDTO.getOutTradeNo(), MOCK_RESP_SUCCESS_DATA);
  36 + }
  37 +
  38 + @Override
  39 + protected PayOrderRespDTO doGetOrder(String outTradeNo) {
  40 + return PayOrderRespDTO.successOf("MOCK-P-" + outTradeNo, "", LocalDateTime.now(),
  41 + outTradeNo, MOCK_RESP_SUCCESS_DATA);
  42 + }
  43 +
  44 + @Override
  45 + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
  46 + return PayRefundRespDTO.successOf("MOCK-R-" + reqDTO.getOutRefundNo(), LocalDateTime.now(),
  47 + reqDTO.getOutRefundNo(), MOCK_RESP_SUCCESS_DATA);
  48 + }
  49 +
  50 + @Override
  51 + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
  52 + return PayRefundRespDTO.successOf("MOCK-R-" + outRefundNo, LocalDateTime.now(),
  53 + outRefundNo, MOCK_RESP_SUCCESS_DATA);
  54 + }
  55 +
  56 + @Override
  57 + protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
  58 + throw new UnsupportedOperationException("模拟支付无退款回调");
  59 + }
  60 +
  61 + @Override
  62 + protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
  63 + throw new UnsupportedOperationException("模拟支付无支付回调");
  64 + }
  65 +
  66 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClientConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
  2 +
  3 +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.Validator;
  7 +
  8 +/**
  9 + * 模拟支付的 PayClientConfig 实现类
  10 + *
  11 + * @author jason
  12 + */
  13 +@Data
  14 +public class MockPayClientConfig implements PayClientConfig {
  15 +
  16 + /**
  17 + * 配置名称
  18 + *
  19 + * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称
  20 + */
  21 + private String name;
  22 +
  23 + @Override
  24 + public void validate(Validator validator) {
  25 + // 模拟支付配置无需校验
  26 + }
  27 +
  28 +}
... ...
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;
3 3 import cn.hutool.core.util.ArrayUtil;
4 4 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
5 5 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
  6 +import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
6 7 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
7 8 import lombok.AllArgsConstructor;
8 9 import lombok.Getter;
... ... @@ -26,7 +27,9 @@ public enum PayChannelEnum {
26 27 ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
27 28 ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
28 29 ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
29   - ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class);
  30 + ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
  31 +
  32 + MOCK("mock", "模拟支付", MockPayClientConfig.class);
30 33  
31 34 /**
32 35 * 编码
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
1 1 package cn.iocoder.yudao.module.pay.controller.admin.notify;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
... ... @@ -118,6 +119,9 @@ public class PayNotifyController {
118 119 @PreAuthorize("@ss.hasPermission('pay:notify:query')")
119 120 public CommonResult<PageResult<PayNotifyTaskRespVO>> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) {
120 121 PageResult<PayNotifyTaskDO> pageResult = notifyService.getNotifyTaskPage(pageVO);
  122 + if (CollUtil.isEmpty(pageResult.getList())) {
  123 + return success(PageResult.empty());
  124 + }
121 125 // 拼接返回
122 126 Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
123 127 return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
... ...
yudao-ui-admin/src/utils/constants.js
... ... @@ -160,6 +160,10 @@ export const PayChannelEnum = {
160 160 "code": "alipay_bar",
161 161 "name": "支付宝条码支付"
162 162 },
  163 + MOCK : {
  164 + "code": "mock",
  165 + "name": "模拟支付"
  166 + }
163 167 }
164 168  
165 169 /**
... ...
yudao-ui-admin/src/views/pay/app/components/mockChannelForm.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <el-dialog :visible.sync="dialogVisible" :title="title" @closed="close" append-to-body width="800px">
  4 + <el-form ref="form" :model="formData" :rules="rules" size="medium" label-width="100px" v-loading="formLoading">
  5 + <el-form-item label-width="180px" label="渠道状态" prop="status">
  6 + <el-radio-group v-model="formData.status" size="medium">
  7 + <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value)"
  8 + :label="parseInt(dict.value)">
  9 + {{ dict.label }}
  10 + </el-radio>
  11 + </el-radio-group>
  12 + </el-form-item>
  13 + <el-form-item label-width="180px" label="备注" prop="remark">
  14 + <el-input v-model="formData.remark" :style="{width: '100%'}"></el-input>
  15 + </el-form-item>
  16 + </el-form>
  17 + <div slot="footer" class="dialog-footer">
  18 + <el-button @click="close">取消</el-button>
  19 + <el-button type="primary" @click="submitForm">确定</el-button>
  20 + </div>
  21 + </el-dialog>
  22 + </div>
  23 +</template>
  24 +<script>
  25 +import { createChannel, getChannel, updateChannel } from "@/api/pay/channel";
  26 +import { CommonStatusEnum } from "@/utils/constants";
  27 +
  28 +export default {
  29 + name: "mockChannelForm",
  30 + data() {
  31 + return {
  32 + dialogVisible: false,
  33 + formLoading: false,
  34 + title:'',
  35 + formData: {
  36 + appId: '',
  37 + code: '',
  38 + status: undefined,
  39 + feeRate: 0,
  40 + remark: '',
  41 + config: {
  42 + name: 'mock-conf'
  43 + }
  44 + },
  45 + rules: {
  46 + status: [{ required: true, message: '渠道状态不能为空', trigger: 'blur' }]
  47 + }
  48 + }
  49 + },
  50 + methods: {
  51 + open(appId, code) {
  52 + this.dialogVisible = true;
  53 + this.formLoading = true;
  54 + this.reset(appId, code);
  55 + getChannel(appId, code).then(response => {
  56 + if (response.data && response.data.id) {
  57 + this.formData = response.data;
  58 + this.formData.config = JSON.parse(response.data.config);
  59 + }
  60 + this.title = !this.formData.id ? '创建支付渠道' : '编辑支付渠道'
  61 + }).finally(() => {
  62 + this.formLoading = false;
  63 + });
  64 + },
  65 + close() {
  66 + this.dialogVisible = false;
  67 + this.reset(undefined, undefined);
  68 + },
  69 + submitForm() {
  70 + this.$refs['form'].validate(valid => {
  71 + if (!valid) {
  72 + return
  73 + }
  74 + const data = { ...this.formData };
  75 + data.config = JSON.stringify(this.formData.config);
  76 + if (!data.id) {
  77 + createChannel(data).then(response => {
  78 + this.$modal.msgSuccess("新增成功");
  79 + this.$emit('success')
  80 + this.close();
  81 + });
  82 + } else {
  83 + updateChannel(data).then(response => {
  84 + this.$modal.msgSuccess("修改成功");
  85 + this.$emit('success')
  86 + this.close();
  87 + })
  88 + }
  89 + });
  90 + },
  91 + /** 重置表单 */
  92 + reset(appId, code) {
  93 + this.formData = {
  94 + appId: appId,
  95 + code: code,
  96 + status: CommonStatusEnum.ENABLE,
  97 + remark: '',
  98 + feeRate: 0,
  99 + config: {
  100 + name: 'mock-conf'
  101 + }
  102 + }
  103 + this.resetForm('form')
  104 + },
  105 + }
  106 +}
  107 +</script>
... ...
yudao-ui-admin/src/views/pay/app/index.vue
... ... @@ -157,6 +157,19 @@
157 157 </template>
158 158 </el-table-column>
159 159 </el-table-column>
  160 + <el-table-column label="模拟支付配置" align="center">
  161 + <el-table-column :label="payChannelEnum.MOCK.name" align="center">
  162 + <template v-slot="scope">
  163 + <el-button type="success" icon="el-icon-check" circle
  164 + v-if="isChannelExists(scope.row.channelCodes, payChannelEnum.MOCK.code)"
  165 + @click="handleChannel(scope.row, payChannelEnum.MOCK.code)">
  166 + </el-button>
  167 + <el-button v-else type="danger" icon="el-icon-close" circle
  168 + @click="handleChannel(scope.row, payChannelEnum.MOCK.code)">
  169 + </el-button>
  170 + </template>
  171 + </el-table-column>
  172 + </el-table-column>
160 173 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
161 174 <template v-slot="scope">
162 175 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
... ... @@ -206,6 +219,7 @@
206 219 <!-- 对话框(支付应用的配置) -->
207 220 <weixin-channel-form ref="weixinChannelFormRef" @success="getList" />
208 221 <alipay-channel-form ref="alipayChannelFormRef" @success="getList" />
  222 + <mock-channel-form ref="mockChannelFormRef" @success="getList" />
209 223 </div>
210 224 </template>
211 225  
... ... @@ -214,12 +228,14 @@ import { createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage }
214 228 import { PayChannelEnum, CommonStatusEnum } from "@/utils/constants";
215 229 import weixinChannelForm from "@/views/pay/app/components/weixinChannelForm";
216 230 import alipayChannelForm from "@/views/pay/app/components/alipayChannelForm";
  231 +import mockChannelForm from '@/views/pay/app/components/mockChannelForm';
217 232  
218 233 export default {
219 234 name: "PayApp",
220 235 components: {
221 236 weixinChannelForm,
222   - alipayChannelForm
  237 + alipayChannelForm,
  238 + mockChannelForm
223 239 },
224 240 data() {
225 241 return {
... ... @@ -374,6 +390,10 @@ export default {
374 390 this.$refs['weixinChannelFormRef'].open(row.id, code);
375 391 return
376 392 }
  393 + if (code === 'mock') {
  394 + this.$refs['mockChannelFormRef'].open(row.id, code);
  395 + return
  396 + }
377 397 },
378 398 /**
379 399 * 根据渠道编码判断渠道列表中是否存在
... ...
yudao-ui-admin/src/views/pay/cashier/index.vue
... ... @@ -35,7 +35,7 @@
35 35 <el-descriptions title="选择其它支付" style="margin-top: 20px;" />
36 36 <div class="pay-channel-container">
37 37 <div class="box" v-for="channel in channels" :key="channel.code"
38   - v-if="channel.code.indexOf('alipay_') === -1 && channel.code.indexOf('wx_') === -1">
  38 + v-if="channel.code.indexOf('alipay_') === -1 && channel.code.indexOf('wx_') === -1" @click="submit(channel.code)">
39 39 <img :src="channel.icon">
40 40 <div class="title">{{ channel.name }}</div>
41 41 </div>
... ...