Commit bc30c1d9d296e18328d96d2b4c72c7798a24ba49
Committed by
Gitee
!550 支付中心:增加模拟支付,方便开发调试
Merge pull request !550 from 芋道源码/feature/mall_product
Showing
9 changed files
with
239 additions
and
3 deletions
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
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> | ... | ... |